feat: oidc scope i18n (#2799)
This adds i18n for the OIDC scope descriptsions descriptions.pull/2849/head^2
parent
26236f491e
commit
fcdd41ea2a
|
@ -502,7 +502,7 @@ does.
|
||||||
_**Important Note:** The claim `sub` is planned to be changed in the future to a randomly unique value to identify the
|
_**Important Note:** The claim `sub` is planned to be changed in the future to a randomly unique value to identify the
|
||||||
individual user. Please use the claim `preferred_username` instead._
|
individual user. Please use the claim `preferred_username` instead._
|
||||||
|
|
||||||
| JWT Field | JWT Type | Authelia Attribute | Description |
|
| Claim | JWT Type | Authelia Attribute | Description |
|
||||||
|:------------------:|:-------------:|:------------------:|:---------------------------------------------:|
|
|:------------------:|:-------------:|:------------------:|:---------------------------------------------:|
|
||||||
| sub | string | Username | The username the user used to login with |
|
| sub | string | Username | The username the user used to login with |
|
||||||
| scope | string | scopes | Granted scopes (space delimited) |
|
| scope | string | scopes | Granted scopes (space delimited) |
|
||||||
|
@ -521,15 +521,15 @@ individual user. Please use the claim `preferred_username` instead._
|
||||||
|
|
||||||
This scope includes the groups the authentication backend reports the user is a member of in the token.
|
This scope includes the groups the authentication backend reports the user is a member of in the token.
|
||||||
|
|
||||||
| JWT Field | JWT Type | Authelia Attribute | Description |
|
| Claim | JWT Type | Authelia Attribute | Description |
|
||||||
|:---------:|:-------------:|:------------------:|:----------------------:|
|
|:------:|:-------------:|:------------------:|:----------------------:|
|
||||||
| groups | array[string] | Groups | The users display name |
|
| groups | array[string] | Groups | The users display name |
|
||||||
|
|
||||||
### email
|
### email
|
||||||
|
|
||||||
This scope includes the email information the authentication backend reports about the user in the token.
|
This scope includes the email information the authentication backend reports about the user in the token.
|
||||||
|
|
||||||
| JWT Field | JWT Type | Authelia Attribute | Description |
|
| Claim | JWT Type | Authelia Attribute | Description |
|
||||||
|:--------------:|:-------------:|:------------------:|:---------------------------------------------------------:|
|
|:--------------:|:-------------:|:------------------:|:---------------------------------------------------------:|
|
||||||
| email | string | email[0] | The first email address in the list of emails |
|
| email | string | email[0] | The first email address in the list of emails |
|
||||||
| email_verified | bool | _N/A_ | If the email is verified, assumed true for the time being |
|
| email_verified | bool | _N/A_ | If the email is verified, assumed true for the time being |
|
||||||
|
@ -539,8 +539,8 @@ This scope includes the email information the authentication backend reports abo
|
||||||
|
|
||||||
This scope includes the profile information the authentication backend reports about the user in the token.
|
This scope includes the profile information the authentication backend reports about the user in the token.
|
||||||
|
|
||||||
| JWT Field | JWT Type | Authelia Attribute | Description |
|
| Claim | JWT Type | Authelia Attribute | Description |
|
||||||
|:---------:|:--------:|:------------------:|:----------------------:|
|
|:-----:|:--------:|:------------------:|:----------------------:|
|
||||||
| name | string | display_name | The users display name |
|
| name | string | display_name | The users display name |
|
||||||
|
|
||||||
## Endpoint Implementations
|
## Endpoint Implementations
|
||||||
|
|
|
@ -54,8 +54,8 @@ func (c InternalClient) GetConsentResponseBody(session *session.OIDCWorkflowSess
|
||||||
}
|
}
|
||||||
|
|
||||||
if session != nil {
|
if session != nil {
|
||||||
body.Scopes = scopeNamesToScopes(session.RequestedScopes)
|
body.Scopes = session.RequestedScopes
|
||||||
body.Audience = audienceNamesToAudience(session.RequestedAudience)
|
body.Audience = session.RequestedAudience
|
||||||
}
|
}
|
||||||
|
|
||||||
return body
|
return body
|
||||||
|
|
|
@ -73,8 +73,8 @@ func TestInternalClient_GetConsentResponseBody(t *testing.T) {
|
||||||
consentRequestBody := c.GetConsentResponseBody(nil)
|
consentRequestBody := c.GetConsentResponseBody(nil)
|
||||||
assert.Equal(t, "", consentRequestBody.ClientID)
|
assert.Equal(t, "", consentRequestBody.ClientID)
|
||||||
assert.Equal(t, "", consentRequestBody.ClientDescription)
|
assert.Equal(t, "", consentRequestBody.ClientDescription)
|
||||||
assert.Equal(t, []Scope(nil), consentRequestBody.Scopes)
|
assert.Equal(t, []string(nil), consentRequestBody.Scopes)
|
||||||
assert.Equal(t, []Audience(nil), consentRequestBody.Audience)
|
assert.Equal(t, []string(nil), consentRequestBody.Audience)
|
||||||
|
|
||||||
c.ID = "myclient"
|
c.ID = "myclient"
|
||||||
c.Description = "My Client"
|
c.Description = "My Client"
|
||||||
|
@ -83,13 +83,9 @@ func TestInternalClient_GetConsentResponseBody(t *testing.T) {
|
||||||
RequestedAudience: []string{"https://example.com"},
|
RequestedAudience: []string{"https://example.com"},
|
||||||
RequestedScopes: []string{"openid", "groups"},
|
RequestedScopes: []string{"openid", "groups"},
|
||||||
}
|
}
|
||||||
expectedScopes := []Scope{
|
|
||||||
{"openid", "Use OpenID to verify your identity"},
|
expectedScopes := []string{"openid", "groups"}
|
||||||
{"groups", "Access your group membership"},
|
expectedAudiences := []string{"https://example.com"}
|
||||||
}
|
|
||||||
expectedAudiences := []Audience{
|
|
||||||
{"https://example.com", "https://example.com"},
|
|
||||||
}
|
|
||||||
|
|
||||||
consentRequestBody = c.GetConsentResponseBody(workflow)
|
consentRequestBody = c.GetConsentResponseBody(workflow)
|
||||||
assert.Equal(t, "myclient", consentRequestBody.ClientID)
|
assert.Equal(t, "myclient", consentRequestBody.ClientID)
|
||||||
|
|
|
@ -1,14 +1,5 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
var scopeDescriptions = map[string]string{
|
|
||||||
"openid": "Use OpenID to verify your identity",
|
|
||||||
"email": "Access your email addresses",
|
|
||||||
"profile": "Access your display name",
|
|
||||||
"groups": "Access your group membership",
|
|
||||||
}
|
|
||||||
|
|
||||||
var audienceDescriptions = map[string]string{}
|
|
||||||
|
|
||||||
// Scope strings.
|
// Scope strings.
|
||||||
const (
|
const (
|
||||||
ScopeOpenID = "openid"
|
ScopeOpenID = "openid"
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
package oidc
|
|
||||||
|
|
||||||
func scopeNamesToScopes(scopeSlice []string) (scopes []Scope) {
|
|
||||||
for _, name := range scopeSlice {
|
|
||||||
if val, ok := scopeDescriptions[name]; ok {
|
|
||||||
scopes = append(scopes, Scope{name, val})
|
|
||||||
} else {
|
|
||||||
scopes = append(scopes, Scope{name, name})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return scopes
|
|
||||||
}
|
|
||||||
|
|
||||||
func audienceNamesToAudience(scopeSlice []string) (audience []Audience) {
|
|
||||||
for _, name := range scopeSlice {
|
|
||||||
if val, ok := audienceDescriptions[name]; ok {
|
|
||||||
audience = append(audience, Audience{name, val})
|
|
||||||
} else {
|
|
||||||
audience = append(audience, Audience{name, name})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return audience
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
package oidc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestScopeNamesToScopes(t *testing.T) {
|
|
||||||
scopeNames := []string{"openid"}
|
|
||||||
|
|
||||||
scopes := scopeNamesToScopes(scopeNames)
|
|
||||||
assert.Equal(t, "openid", scopes[0].Name)
|
|
||||||
assert.Equal(t, "Use OpenID to verify your identity", scopes[0].Description)
|
|
||||||
|
|
||||||
scopeNames = []string{"groups"}
|
|
||||||
|
|
||||||
scopes = scopeNamesToScopes(scopeNames)
|
|
||||||
assert.Equal(t, "groups", scopes[0].Name)
|
|
||||||
assert.Equal(t, "Access your group membership", scopes[0].Description)
|
|
||||||
|
|
||||||
scopeNames = []string{"profile"}
|
|
||||||
|
|
||||||
scopes = scopeNamesToScopes(scopeNames)
|
|
||||||
assert.Equal(t, "profile", scopes[0].Name)
|
|
||||||
assert.Equal(t, "Access your display name", scopes[0].Description)
|
|
||||||
|
|
||||||
scopeNames = []string{"email"}
|
|
||||||
|
|
||||||
scopes = scopeNamesToScopes(scopeNames)
|
|
||||||
assert.Equal(t, "email", scopes[0].Name)
|
|
||||||
assert.Equal(t, "Access your email addresses", scopes[0].Description)
|
|
||||||
|
|
||||||
scopeNames = []string{"another"}
|
|
||||||
|
|
||||||
scopes = scopeNamesToScopes(scopeNames)
|
|
||||||
assert.Equal(t, "another", scopes[0].Name)
|
|
||||||
assert.Equal(t, "another", scopes[0].Description)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAudienceNamesToScopes(t *testing.T) {
|
|
||||||
audienceNames := []string{"audience", "another_aud"}
|
|
||||||
|
|
||||||
audiences := audienceNamesToAudience(audienceNames)
|
|
||||||
assert.Equal(t, "audience", audiences[0].Name)
|
|
||||||
assert.Equal(t, "audience", audiences[0].Description)
|
|
||||||
assert.Equal(t, "another_aud", audiences[1].Name)
|
|
||||||
assert.Equal(t, "another_aud", audiences[1].Description)
|
|
||||||
}
|
|
|
@ -66,20 +66,8 @@ type AutheliaHasher struct{}
|
||||||
type ConsentGetResponseBody struct {
|
type ConsentGetResponseBody struct {
|
||||||
ClientID string `json:"client_id"`
|
ClientID string `json:"client_id"`
|
||||||
ClientDescription string `json:"client_description"`
|
ClientDescription string `json:"client_description"`
|
||||||
Scopes []Scope `json:"scopes"`
|
Scopes []string `json:"scopes"`
|
||||||
Audience []Audience `json:"audience"`
|
Audience []string `json:"audience"`
|
||||||
}
|
|
||||||
|
|
||||||
// Scope represents the scope information.
|
|
||||||
type Scope struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Audience represents the audience information.
|
|
||||||
type Audience struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WellKnownConfiguration is the OIDC well known config struct.
|
// WellKnownConfiguration is the OIDC well known config struct.
|
||||||
|
|
|
@ -48,6 +48,13 @@
|
||||||
"Username": "Username",
|
"Username": "Username",
|
||||||
"You must open the link from the same device and browser that initiated the registration process": "You must open the link from the same device and browser that initiated the registration process",
|
"You must open the link from the same device and browser that initiated the registration process": "You must open the link from the same device and browser that initiated the registration process",
|
||||||
"You're being signed out and redirected": "You're being signed out and redirected",
|
"You're being signed out and redirected": "You're being signed out and redirected",
|
||||||
"Your supplied password does not meet the password policy requirements": "Your supplied password does not meet the password policy requirements."
|
"Your supplied password does not meet the password policy requirements": "Your supplied password does not meet the password policy requirements.",
|
||||||
|
"Use OpenID to verify your identity": "Use OpenID to verify your identity",
|
||||||
|
"Access your display name": "Access your display name",
|
||||||
|
"Access your group membership": "Access your group membership",
|
||||||
|
"Access your email addresses": "Access your email addresses",
|
||||||
|
"Accept": "Accept",
|
||||||
|
"Deny": "Deny",
|
||||||
|
"The above application is requesting the following permissions": "The above application is requesting the following permissions"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,13 @@
|
||||||
"Username": "Usuario",
|
"Username": "Usuario",
|
||||||
"You must open the link from the same device and browser that initiated the registration process": "Debe abrir el link desde el mismo dispositivo y navegador desde el que inició el proceso de registración",
|
"You must open the link from the same device and browser that initiated the registration process": "Debe abrir el link desde el mismo dispositivo y navegador desde el que inició el proceso de registración",
|
||||||
"You're being signed out and redirected": "Cerrando Sesión y redirigiendo",
|
"You're being signed out and redirected": "Cerrando Sesión y redirigiendo",
|
||||||
"Your supplied password does not meet the password policy requirements": "La contraseña suministrada no cumple con los requerimientos de la política de contraseñas"
|
"Your supplied password does not meet the password policy requirements": "La contraseña suministrada no cumple con los requerimientos de la política de contraseñas",
|
||||||
|
"Use OpenID to verify your identity": "Utilizar OpenID para verificar su identidad",
|
||||||
|
"Access your display name": "Acceso a su nombre",
|
||||||
|
"Access your group membership": "Acceso a su(s) grupo(s)",
|
||||||
|
"Access your email addresses": "Acceso a su dirección de correo",
|
||||||
|
"Accept": "Aceptar",
|
||||||
|
"Deny": "Denegar",
|
||||||
|
"The above application is requesting the following permissions": "La aplicación solicita los siguientes permisos"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,18 +13,8 @@ interface ConsentPostResponseBody {
|
||||||
interface ConsentGetResponseBody {
|
interface ConsentGetResponseBody {
|
||||||
client_id: string;
|
client_id: string;
|
||||||
client_description: string;
|
client_description: string;
|
||||||
scopes: Scope[];
|
scopes: string[];
|
||||||
audience: Audience[];
|
audience: string[];
|
||||||
}
|
|
||||||
|
|
||||||
interface Scope {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Audience {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRequestedScopes() {
|
export function getRequestedScopes() {
|
||||||
|
|
|
@ -1,7 +1,18 @@
|
||||||
import React, { useEffect, Fragment, ReactNode } from "react";
|
import React, { useEffect, Fragment, ReactNode } from "react";
|
||||||
|
|
||||||
import { Button, Grid, List, ListItem, ListItemIcon, ListItemText, Tooltip, makeStyles } from "@material-ui/core";
|
import {
|
||||||
|
Button,
|
||||||
|
Grid,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
makeStyles,
|
||||||
|
} from "@material-ui/core";
|
||||||
import { AccountBox, CheckBox, Contacts, Drafts, Group } from "@material-ui/icons";
|
import { AccountBox, CheckBox, Contacts, Drafts, Group } from "@material-ui/icons";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { FirstFactorRoute } from "@constants/Routes";
|
import { FirstFactorRoute } from "@constants/Routes";
|
||||||
|
@ -14,7 +25,7 @@ import LoadingPage from "@views/LoadingPage/LoadingPage";
|
||||||
|
|
||||||
export interface Props {}
|
export interface Props {}
|
||||||
|
|
||||||
function showListItemAvatar(id: string) {
|
function scopeNameToAvatar(id: string) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case "openid":
|
case "openid":
|
||||||
return <AccountBox />;
|
return <AccountBox />;
|
||||||
|
@ -35,6 +46,7 @@ const ConsentView = function (props: Props) {
|
||||||
const redirect = useRedirector();
|
const redirect = useRedirector();
|
||||||
const { createErrorNotification, resetNotification } = useNotifications();
|
const { createErrorNotification, resetNotification } = useNotifications();
|
||||||
const [resp, fetch, , err] = useRequestedScopes();
|
const [resp, fetch, , err] = useRequestedScopes();
|
||||||
|
const { t: translate } = useTranslation("Portal");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -47,6 +59,21 @@ const ConsentView = function (props: Props) {
|
||||||
fetch();
|
fetch();
|
||||||
}, [fetch]);
|
}, [fetch]);
|
||||||
|
|
||||||
|
const translateScopeNameToDescription = (id: string): string => {
|
||||||
|
switch (id) {
|
||||||
|
case "openid":
|
||||||
|
return translate("Use OpenID to verify your identity");
|
||||||
|
case "profile":
|
||||||
|
return translate("Access your display name");
|
||||||
|
case "groups":
|
||||||
|
return translate("Access your group membership");
|
||||||
|
case "email":
|
||||||
|
return translate("Access your email addresses");
|
||||||
|
default:
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleAcceptConsent = async () => {
|
const handleAcceptConsent = async () => {
|
||||||
// This case should not happen in theory because the buttons are disabled when response is undefined.
|
// This case should not happen in theory because the buttons are disabled when response is undefined.
|
||||||
if (!resp) {
|
if (!resp) {
|
||||||
|
@ -77,20 +104,31 @@ const ConsentView = function (props: Props) {
|
||||||
<LoginLayout id="consent-stage" title={`Permissions Request`} showBrand>
|
<LoginLayout id="consent-stage" title={`Permissions Request`} showBrand>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<div style={{ textAlign: "left" }}>
|
<div>
|
||||||
The application
|
{resp !== undefined && resp.client_description !== "" ? (
|
||||||
<b>{` ${resp?.client_description} (${resp?.client_id}) `}</b>
|
<Tooltip title={"Client ID: " + resp.client_id}>
|
||||||
is requesting the following permissions
|
<Typography className={classes.clientDescription}>
|
||||||
|
{resp.client_description}
|
||||||
|
</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Tooltip title={"Client ID: " + resp?.client_id}>
|
||||||
|
<Typography className={classes.clientDescription}>{resp?.client_id}</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<div>{translate("The above application is requesting the following permissions")}:</div>
|
||||||
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<div className={classes.scopesListContainer}>
|
<div className={classes.scopesListContainer}>
|
||||||
<List className={classes.scopesList}>
|
<List className={classes.scopesList}>
|
||||||
{resp?.scopes.map((s) => (
|
{resp?.scopes.map((scope: string) => (
|
||||||
<Tooltip title={"Scope " + s.name}>
|
<Tooltip title={"Scope " + scope}>
|
||||||
<ListItem id={"scope-" + s.name} dense>
|
<ListItem id={"scope-" + scope} dense>
|
||||||
<ListItemIcon>{showListItemAvatar(s.name)}</ListItemIcon>
|
<ListItemIcon>{scopeNameToAvatar(scope)}</ListItemIcon>
|
||||||
<ListItemText primary={s.description} />
|
<ListItemText primary={translateScopeNameToDescription(scope)} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
|
@ -108,7 +146,7 @@ const ConsentView = function (props: Props) {
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
>
|
>
|
||||||
Accept
|
{translate("Accept")}
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={6}>
|
<Grid item xs={6}>
|
||||||
|
@ -120,7 +158,7 @@ const ConsentView = function (props: Props) {
|
||||||
color="secondary"
|
color="secondary"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
>
|
>
|
||||||
Deny
|
{translate("Deny")}
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -138,6 +176,9 @@ const useStyles = makeStyles((theme) => ({
|
||||||
display: "block",
|
display: "block",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
|
clientDescription: {
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
scopesListContainer: {
|
scopesListContainer: {
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue