diff --git a/config/authelia/config.yml b/config/authelia/config.yml index 5ad6e4f5..5cf32394 100644 --- a/config/authelia/config.yml +++ b/config/authelia/config.yml @@ -785,6 +785,16 @@ identity_providers: allowed_origins: - 'https://{{ .Domain }}' allowed_origins_from_client_redirect_uris: false + claims_policies: + syncloud: + id_token: + - 'email' + - 'email_verified' + - 'name' + - 'given_name' + - 'family_name' + - 'preferred_username' + - 'groups' clients: - client_id: 'syncloud' public: true @@ -814,6 +824,7 @@ identity_providers: - client_id: '{{ .ID }}' client_secret: '{{ .Secret }}' public: false + claims_policy: 'syncloud' authorization_policy: '{{ if $.TwoFactorEnabled }}two_factor{{ else }}one_factor{{ end }}' redirect_uris: {{- range .RedirectURIs }} diff --git a/test/testapp/cli/cmd/backend/main.go b/test/testapp/cli/cmd/backend/main.go index ab23199b..3455f61f 100644 --- a/test/testapp/cli/cmd/backend/main.go +++ b/test/testapp/cli/cmd/backend/main.go @@ -80,7 +80,7 @@ func main() { "client_id": {"testapp"}, "response_type": {"code"}, "redirect_uri": {redirectURI}, - "scope": {"openid profile email"}, + "scope": {"openid profile email groups"}, "state": {state}, "code_challenge": {challenge}, "code_challenge_method": {"S256"}, @@ -131,60 +131,47 @@ func main() { } var tokenResp struct { - IDToken string `json:"id_token"` - AccessToken string `json:"access_token"` + IDToken string `json:"id_token"` } json.Unmarshal(body, &tokenResp) - var username string - if tokenResp.AccessToken != "" { - userinfoURL := authUrl + "/api/oidc/userinfo" - uReq, _ := http.NewRequest("GET", userinfoURL, nil) - uReq.Header.Set("Authorization", "Bearer "+tokenResp.AccessToken) - uResp, uErr := newHTTPClient().Do(uReq) - if uErr == nil { - defer uResp.Body.Close() - uBody, _ := io.ReadAll(uResp.Body) - var userInfo struct { - PreferredUsername string `json:"preferred_username"` - } - json.Unmarshal(uBody, &userInfo) - username = userInfo.PreferredUsername - } - } - if username == "" { - username, err = extractUsername(tokenResp.IDToken) - if err != nil { - http.Error(w, fmt.Sprintf("extract username: %v", err), http.StatusInternalServerError) - return - } + claims, err := extractClaims(tokenResp.IDToken) + if err != nil { + http.Error(w, fmt.Sprintf("extract claims: %v", err), http.StatusInternalServerError) + return } w.Header().Set("Content-Type", "text/plain") - fmt.Fprintf(w, "OK %s", username) + fmt.Fprintf(w, "OK %s email=%s groups=%s", claims.PreferredUsername, claims.Email, strings.Join(claims.Groups, ",")) }) http.Serve(listener, mux) } -func extractUsername(idToken string) (string, error) { +type idTokenClaims struct { + PreferredUsername string `json:"preferred_username"` + Email string `json:"email"` + Groups []string `json:"groups"` + Subject string `json:"sub"` +} + +func extractClaims(idToken string) (idTokenClaims, error) { + var claims idTokenClaims parts := strings.Split(idToken, ".") if len(parts) != 3 { - return "", fmt.Errorf("invalid id_token") + return claims, fmt.Errorf("invalid id_token") } payload, err := base64.RawURLEncoding.DecodeString(parts[1]) if err != nil { - return "", err + return claims, err } - var claims struct { - PreferredUsername string `json:"preferred_username"` - Subject string `json:"sub"` + if err := json.Unmarshal(payload, &claims); err != nil { + return claims, err } - json.Unmarshal(payload, &claims) - if claims.PreferredUsername != "" { - return claims.PreferredUsername, nil + if claims.PreferredUsername == "" { + claims.PreferredUsername = claims.Subject } - return claims.Subject, nil + return claims, nil } func randomString(length int) (string, error) { diff --git a/web/e2e/specs/08-oidc.spec.ts b/web/e2e/specs/08-oidc.spec.ts index 8a96b6f5..4f89b1df 100644 --- a/web/e2e/specs/08-oidc.spec.ts +++ b/web/e2e/specs/08-oidc.spec.ts @@ -27,6 +27,10 @@ test('testapp OIDC login', async ({}, testInfo) => { await shoot(page, testInfo, 'testapp-oidc-login') await page.locator('#sign-in-button').click() await expect(page.locator(`xpath=//*[contains(text(),'OK ${deviceUser}')]`)).toBeVisible() + const claims = await page.locator('body').innerText() + expect(claims).toContain(`OK ${deviceUser}`) + expect(claims).toMatch(/email=\S+@\S+/) + expect(claims).toContain('groups=syncloud') await shoot(page, testInfo, 'testapp-oidc-callback') })