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
|
||||
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 |
|
||||
| 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.
|
||||
|
||||
| JWT Field | JWT Type | Authelia Attribute | Description |
|
||||
|:---------:|:-------------:|:------------------:|:----------------------:|
|
||||
| Claim | JWT Type | Authelia Attribute | Description |
|
||||
|:------:|:-------------:|:------------------:|:----------------------:|
|
||||
| groups | array[string] | Groups | The users display name |
|
||||
|
||||
### email
|
||||
|
||||
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_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.
|
||||
|
||||
| JWT Field | JWT Type | Authelia Attribute | Description |
|
||||
|:---------:|:--------:|:------------------:|:----------------------:|
|
||||
| Claim | JWT Type | Authelia Attribute | Description |
|
||||
|:-----:|:--------:|:------------------:|:----------------------:|
|
||||
| name | string | display_name | The users display name |
|
||||
|
||||
## Endpoint Implementations
|
||||
|
|
|
@ -54,8 +54,8 @@ func (c InternalClient) GetConsentResponseBody(session *session.OIDCWorkflowSess
|
|||
}
|
||||
|
||||
if session != nil {
|
||||
body.Scopes = scopeNamesToScopes(session.RequestedScopes)
|
||||
body.Audience = audienceNamesToAudience(session.RequestedAudience)
|
||||
body.Scopes = session.RequestedScopes
|
||||
body.Audience = session.RequestedAudience
|
||||
}
|
||||
|
||||
return body
|
||||
|
|
|
@ -73,8 +73,8 @@ func TestInternalClient_GetConsentResponseBody(t *testing.T) {
|
|||
consentRequestBody := c.GetConsentResponseBody(nil)
|
||||
assert.Equal(t, "", consentRequestBody.ClientID)
|
||||
assert.Equal(t, "", consentRequestBody.ClientDescription)
|
||||
assert.Equal(t, []Scope(nil), consentRequestBody.Scopes)
|
||||
assert.Equal(t, []Audience(nil), consentRequestBody.Audience)
|
||||
assert.Equal(t, []string(nil), consentRequestBody.Scopes)
|
||||
assert.Equal(t, []string(nil), consentRequestBody.Audience)
|
||||
|
||||
c.ID = "myclient"
|
||||
c.Description = "My Client"
|
||||
|
@ -83,13 +83,9 @@ func TestInternalClient_GetConsentResponseBody(t *testing.T) {
|
|||
RequestedAudience: []string{"https://example.com"},
|
||||
RequestedScopes: []string{"openid", "groups"},
|
||||
}
|
||||
expectedScopes := []Scope{
|
||||
{"openid", "Use OpenID to verify your identity"},
|
||||
{"groups", "Access your group membership"},
|
||||
}
|
||||
expectedAudiences := []Audience{
|
||||
{"https://example.com", "https://example.com"},
|
||||
}
|
||||
|
||||
expectedScopes := []string{"openid", "groups"}
|
||||
expectedAudiences := []string{"https://example.com"}
|
||||
|
||||
consentRequestBody = c.GetConsentResponseBody(workflow)
|
||||
assert.Equal(t, "myclient", consentRequestBody.ClientID)
|
||||
|
|
|
@ -1,14 +1,5 @@
|
|||
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.
|
||||
const (
|
||||
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 {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientDescription string `json:"client_description"`
|
||||
Scopes []Scope `json:"scopes"`
|
||||
Audience []Audience `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"`
|
||||
Scopes []string `json:"scopes"`
|
||||
Audience []string `json:"audience"`
|
||||
}
|
||||
|
||||
// WellKnownConfiguration is the OIDC well known config struct.
|
||||
|
|
|
@ -48,6 +48,13 @@
|
|||
"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'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",
|
||||
"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",
|
||||
"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 {
|
||||
client_id: string;
|
||||
client_description: string;
|
||||
scopes: Scope[];
|
||||
audience: Audience[];
|
||||
}
|
||||
|
||||
interface Scope {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface Audience {
|
||||
name: string;
|
||||
description: string;
|
||||
scopes: string[];
|
||||
audience: string[];
|
||||
}
|
||||
|
||||
export function getRequestedScopes() {
|
||||
|
|
|
@ -1,7 +1,18 @@
|
|||
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 { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { FirstFactorRoute } from "@constants/Routes";
|
||||
|
@ -14,7 +25,7 @@ import LoadingPage from "@views/LoadingPage/LoadingPage";
|
|||
|
||||
export interface Props {}
|
||||
|
||||
function showListItemAvatar(id: string) {
|
||||
function scopeNameToAvatar(id: string) {
|
||||
switch (id) {
|
||||
case "openid":
|
||||
return <AccountBox />;
|
||||
|
@ -35,6 +46,7 @@ const ConsentView = function (props: Props) {
|
|||
const redirect = useRedirector();
|
||||
const { createErrorNotification, resetNotification } = useNotifications();
|
||||
const [resp, fetch, , err] = useRequestedScopes();
|
||||
const { t: translate } = useTranslation("Portal");
|
||||
|
||||
useEffect(() => {
|
||||
if (err) {
|
||||
|
@ -47,6 +59,21 @@ const ConsentView = function (props: Props) {
|
|||
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 () => {
|
||||
// This case should not happen in theory because the buttons are disabled when response is undefined.
|
||||
if (!resp) {
|
||||
|
@ -77,20 +104,31 @@ const ConsentView = function (props: Props) {
|
|||
<LoginLayout id="consent-stage" title={`Permissions Request`} showBrand>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<div style={{ textAlign: "left" }}>
|
||||
The application
|
||||
<b>{` ${resp?.client_description} (${resp?.client_id}) `}</b>
|
||||
is requesting the following permissions
|
||||
<div>
|
||||
{resp !== undefined && resp.client_description !== "" ? (
|
||||
<Tooltip title={"Client ID: " + resp.client_id}>
|
||||
<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>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<div>{translate("The above application is requesting the following permissions")}:</div>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.scopesListContainer}>
|
||||
<List className={classes.scopesList}>
|
||||
{resp?.scopes.map((s) => (
|
||||
<Tooltip title={"Scope " + s.name}>
|
||||
<ListItem id={"scope-" + s.name} dense>
|
||||
<ListItemIcon>{showListItemAvatar(s.name)}</ListItemIcon>
|
||||
<ListItemText primary={s.description} />
|
||||
{resp?.scopes.map((scope: string) => (
|
||||
<Tooltip title={"Scope " + scope}>
|
||||
<ListItem id={"scope-" + scope} dense>
|
||||
<ListItemIcon>{scopeNameToAvatar(scope)}</ListItemIcon>
|
||||
<ListItemText primary={translateScopeNameToDescription(scope)} />
|
||||
</ListItem>
|
||||
</Tooltip>
|
||||
))}
|
||||
|
@ -108,7 +146,7 @@ const ConsentView = function (props: Props) {
|
|||
color="primary"
|
||||
variant="contained"
|
||||
>
|
||||
Accept
|
||||
{translate("Accept")}
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
|
@ -120,7 +158,7 @@ const ConsentView = function (props: Props) {
|
|||
color="secondary"
|
||||
variant="contained"
|
||||
>
|
||||
Deny
|
||||
{translate("Deny")}
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
@ -138,6 +176,9 @@ const useStyles = makeStyles((theme) => ({
|
|||
display: "block",
|
||||
justifyContent: "center",
|
||||
},
|
||||
clientDescription: {
|
||||
fontWeight: 600,
|
||||
},
|
||||
scopesListContainer: {
|
||||
textAlign: "center",
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue