From 8b0f497f5a4474487056586e72890f5a05f9e6e2 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 3 Jan 2021 21:25:28 +0100 Subject: [PATCH] Finish implementation of requested claim access --- active.de.toml | 16 +- active.en.toml | 6 +- common/models/oidc.go | 20 +- common/services/i18n.go | 11 +- idp/handlers/consent.go | 469 ++++++++++++++++++++++++----------- idp/services/i18n.go | 20 +- templates/idp/consent.gohtml | 19 +- translate.de.toml | 10 +- 8 files changed, 389 insertions(+), 182 deletions(-) diff --git a/active.de.toml b/active.de.toml index f67719d..e670f85 100644 --- a/active.de.toml +++ b/active.de.toml @@ -6,6 +6,10 @@ other = "Du hast ein gültiges Client-Zertifikat für die folgenden E-Mail-Adres hash = "sha1-c1ad2600848ad6293ae6df6a04b0b2318bb303f9" other = "Willst du dieses Zertifikat für die Anmeldung verwenden oder möchtest du lieber ein anderes Verfahren nutzen?" +[ClaimsInformation] +hash = "sha1-4a6721995b5d87c02be77695910af642ca30b18a" +other = "Zusätzlich möchte die Anwendung Zugriff auf folgende Informationen:" + [ErrorEmail] hash = "sha1-d2306dd8970ff616631a3501791297f31475e416" other = "Bitte gib eine gültige E-Mailadresse ein." @@ -57,8 +61,8 @@ hash = "sha1-f58b8378238bd433deef3c3e6b0b70d0fd0dd59e" other = "Auf der Beschreibungsseite findest du mehr Informationen zu {{ .client }}." [IntroConsentRequested] -hash = "sha1-cb8efc74b5b726201321e0924747bf38d39629a1" -other = "Die Anwendung {{ .client }} benötigt deine Einwilligungung für die angefragten Berechtigungen." +hash = "sha1-3ac6a3583d40b5e8930c57531f0be9706f1e0194" +other = "Die Anwendung {{ .client }} hat deine Zustimmung für die Erteilung der folgenden Berechtigungen angefragt:" [LabelAcceptCertLogin] description = "Label for a button to accept certificate login" @@ -96,9 +100,9 @@ other = "Ausloggen" hash = "sha1-e50e5ea384cad8fac6f918d698be373b1362b351" other = "Zugriff auf deine primäre E-Mail-Adresse." -[Scope-offline_access-Description] +[Scope-offline-Description] hash = "sha1-732881bf998daa62cbad8615b2e6feb7a053b123" -other = "Zugriff auf deine Informationen behalten, bis du diese Zustimmung widerrufst." +other = "Zugriff auf deine Daten behalten, bis du diese Berechtigung widerrufst." [Scope-openid-Description] hash = "sha1-0ad714e7a22b97d8247b70254990256bffa2ef76" @@ -115,3 +119,7 @@ other = "Anwendung erbittet deine Zustimmung" [WrongOrLockedUserOrInvalidPassword] hash = "sha1-87e0a0ac67c6c3a06bed184e10b22aae4d075b64" other = "Du hast einen ungültigen Nutzernamen oder ein ungültiges Passwort eingegeben oder dein Benutzerkonto wurde gesperrt." + +[claim-CAcert-groups-description] +hash = "sha1-62e8788623838cfe2185e315d3a979cbb2eea3b5" +other = "Deine CAcert-Team- oder Gruppenzugehörigkeiten." diff --git a/active.en.toml b/active.en.toml index 9d7bf69..14b0b07 100644 --- a/active.en.toml +++ b/active.en.toml @@ -1,5 +1,6 @@ CertLoginIntroText = "You have presented a valid client certificate for the following email addresses:" CertLoginRequestText = "Do you want to use this certificate for authentication or do you want to use a different method?" +ClaimsInformation = "In addition the application wants access to the following information:" ErrorEmail = "Please enter a valid email address." ErrorEmailRequired = "Please enter an email address." ErrorPasswordRequired = "Please enter a password." @@ -10,16 +11,17 @@ IndexGreeting = "Hello {{ .User }}" IndexIntroductionText = "This is an authorization protected resource" IndexTitle = "Welcome to the Demo application" IntroConsentMoreInformation = "You can find more information about {{ .client }} at its description page." -IntroConsentRequested = "The {{ .client }} application wants your consent for the requested set of permissions." +IntroConsentRequested = "The {{ .client }} application requested your consent for the following set of permissions:" LabelConsent = "I hereby agree that the application may get the requested permissions." LabelSubmit = "Submit" LoginTitle = "Login" Scope-email-Description = "Access your primary email address." -Scope-offline_access-Description = "Keep access to your information until you revoke the permission." +Scope-offline-Description = "Keep access to your information until you revoke the permission." Scope-openid-Description = "Request information about your identity." Scope-profile-Description = "Access your user profile information including your name, birth date and locale." TitleRequestConsent = "Application requests your consent" WrongOrLockedUserOrInvalidPassword = "You entered an invalid username or password or your account has been locked." +claim-CAcert-groups-description = "Your CAcert team or group assignments." [FormLabelEmail] description = "Label for an email form field" diff --git a/common/models/oidc.go b/common/models/oidc.go index 9d40e76..c3892fd 100644 --- a/common/models/oidc.go +++ b/common/models/oidc.go @@ -147,12 +147,20 @@ func (i IndividualClaimRequest) AllowedValues() []string { // OpenIDConfiguration contains the parts of the OpenID discovery information // that are relevant for us. // -// Specification +// Specifications // -// See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata +// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata +// +// https://openid.net/specs/openid-connect-rpinitiated-1_0.html#OPMetadata type OpenIDConfiguration struct { - AuthorizationEndpoint string `json:"authorization_endpoint"` - TokenEndpoint string `json:"token_endpoint"` - JwksUri string `json:"jwks_uri"` - EndSessionEndpoint string `json:"end_session_endpoint"` + Issuer string `json:"issuer"` + AuthorizationEndpoint string `json:"authorization_endpoint"` + TokenEndpoint string `json:"token_endpoint"` + UserInfoEndpoint string `json:"userinfo_endpoint"` + JwksUri string `json:"jwks_uri"` + RegistrationEndpoint string `json:"registration_endpoint"` + ScopesSupported []string `json:"scopes_supported"` + EndSessionEndpoint string `json:"end_session_endpoint"` + ClaimTypesSupported []string `json:"claim_types_supported"` + ClaimsSupported []string `json:"claims_supported"` } diff --git a/common/services/i18n.go b/common/services/i18n.go index 65ef9b8..1a7e470 100644 --- a/common/services/i18n.go +++ b/common/services/i18n.go @@ -64,7 +64,16 @@ func (m *MessageCatalog) LookupMessage(id string, templateData map[string]interf TemplateData: templateData, }) if err != nil { - m.logger.Error(err) + switch err.(type) { + case *i18n.MessageNotFoundErr: + m.logger.Warnf("message %s not found: %v", id, err) + if translation != "" { + return translation + } + break + default: + m.logger.Error(err) + } return id } return translation diff --git a/idp/handlers/consent.go b/idp/handlers/consent.go index dc68e56..9889e37 100644 --- a/idp/handlers/consent.go +++ b/idp/handlers/consent.go @@ -4,7 +4,6 @@ import ( "context" "database/sql" "encoding/json" - "fmt" "html/template" "net/http" "net/url" @@ -22,7 +21,7 @@ import ( log "github.com/sirupsen/logrus" "git.cacert.org/oidc_login/common/handlers" - models2 "git.cacert.org/oidc_login/common/models" + commonModels "git.cacert.org/oidc_login/common/models" commonServices "git.cacert.org/oidc_login/common/services" "git.cacert.org/oidc_login/idp/services" ) @@ -37,7 +36,9 @@ type consentHandler struct { } type ConsentInformation struct { - ConsentChecked bool `form:"consent"` + GrantedScopes []string `form:"scope"` + SelectedClaims []string `form:"claims"` + ConsentChecked bool `form:"consent"` } type UserInfo struct { @@ -51,6 +52,55 @@ type UserInfo struct { Modified mysql.NullTime `db:"modified"` } +var supportedScopes, supportedClaims map[string]*i18n.Message + +const ( + ScopeOpenID = "openid" + ScopeOffline = "offline" + ScopeOfflineAccess = "offline_access" + ScopeProfile = "profile" + ScopeEmail = "email" + + ClaimCAcertGroups = "https://cacert.localhost/groups" +) + +func init() { + supportedScopes = make(map[string]*i18n.Message) + supportedScopes[ScopeOpenID] = &i18n.Message{ + ID: "Scope-openid-Description", + Other: "Request information about your identity.", + } + supportedScopes[ScopeOffline] = &i18n.Message{ + ID: "Scope-offline-Description", + Other: "Keep access to your information until you revoke the permission.", + } + supportedScopes[ScopeOfflineAccess] = supportedScopes[ScopeOffline] + supportedScopes[ScopeProfile] = &i18n.Message{ + ID: "Scope-profile-Description", + Other: "Access your user profile information including your name, birth date and locale.", + } + supportedScopes[ScopeEmail] = &i18n.Message{ + ID: "Scope-email-Description", + Other: "Access your primary email address.", + } + + supportedClaims = make(map[string]*i18n.Message) + supportedClaims[openid.SubjectKey] = nil + supportedClaims[openid.EmailKey] = nil + supportedClaims[openid.EmailVerifiedKey] = nil + supportedClaims[openid.GivenNameKey] = nil + supportedClaims[openid.FamilyNameKey] = nil + supportedClaims[openid.MiddleNameKey] = nil + supportedClaims[openid.NameKey] = nil + supportedClaims[openid.BirthdateKey] = nil + supportedClaims[openid.ZoneinfoKey] = nil + supportedClaims[openid.LocaleKey] = nil + supportedClaims[ClaimCAcertGroups] = &i18n.Message{ + ID: "claim-CAcert-groups-description", + Other: "Your CAcert team or group assignments.", + } +} + func (i *UserInfo) GetFullName() string { nameParts := make([]string, 0) if len(i.GivenName) > 0 { @@ -72,6 +122,78 @@ func (h *consentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { localizer := i18n.NewLocalizer(h.bundle, accept) // retrieve consent information + consentData, requestedClaims, err := h.getRequestedConsentInformation(challenge, r) + if err != nil { + // error is already handled in getRequestConsentInformation + return + } + + switch r.Method { + case http.MethodGet: + h.renderConsentForm(w, r, consentData, requestedClaims, err, localizer) + break + case http.MethodPost: + var consentInfo ConsentInformation + + // validate input + decoder := form.NewDecoder() + if err := decoder.Decode(&consentInfo, r.Form); err != nil { + h.logger.Error(err) + http.Error( + w, + http.StatusText(http.StatusInternalServerError), + http.StatusInternalServerError, + ) + return + } + + if consentInfo.ConsentChecked { + sessionData, err := h.getSessionData(consentInfo, requestedClaims, consentData.Payload, r.Context()) + if err != nil { + h.logger.Errorf("could not get session data: %v", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + consentRequest, err := h.adminClient.AcceptConsentRequest( + admin.NewAcceptConsentRequestParams().WithConsentChallenge(challenge).WithBody( + &models.AcceptConsentRequest{ + GrantAccessTokenAudience: nil, + GrantScope: consentInfo.GrantedScopes, + HandledAt: models.NullTime(time.Now()), + Remember: true, + RememberFor: 86400, + Session: sessionData, + }).WithTimeout(time.Second * 10)) + if err != nil { + h.logger.Error(err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + w.Header().Add("Location", *consentRequest.GetPayload().RedirectTo) + w.WriteHeader(http.StatusFound) + return + + } else { + consentRequest, err := h.adminClient.RejectConsentRequest( + admin.NewRejectConsentRequestParams().WithConsentChallenge(challenge).WithBody( + &models.RejectRequest{})) + if err != nil { + h.logger.Error(err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + w.Header().Add("Location", *consentRequest.GetPayload().RedirectTo) + w.WriteHeader(http.StatusFound) + } + } +} + +func (h *consentHandler) getRequestedConsentInformation(challenge string, r *http.Request) ( + *admin.GetConsentRequestOK, + *commonModels.OIDCClaimsRequest, + error, +) { consentData, err := h.adminClient.GetConsentRequest( admin.NewGetConsentRequestParams().WithConsentChallenge(challenge)) if err != nil { @@ -94,137 +216,34 @@ func (h *consentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } handlers.GetErrorBucket(r).AddError(errorDetails) - return + return nil, nil, err } - - switch r.Method { - case http.MethodGet: - h.renderConsentForm(w, r, consentData, err, localizer) - break - case http.MethodPost: - var consentInfo ConsentInformation - - // validate input - decoder := form.NewDecoder() - if err := decoder.Decode(&consentInfo, r.Form); err != nil { - h.logger.Error(err) - http.Error( - w, - http.StatusText(http.StatusInternalServerError), - http.StatusInternalServerError, - ) - return - } - - if consentInfo.ConsentChecked { - idTokenData := make(map[string]interface{}, 0) - - db := services.GetDb(h.context) - - stmt, err := db.PreparexContext( - r.Context(), - `SELECT email, verified, fname, mname, lname, dob, language, modified -FROM users -WHERE uniqueid = ? - AND locked = 0`, - ) + var requestedClaims commonModels.OIDCClaimsRequest + requestUrl, err := url.Parse(consentData.Payload.RequestURL) + if err != nil { + h.logger.Warnf("could not parse original request URL %s: %v", consentData.Payload.RequestURL, err) + } else { + claimsParameter := requestUrl.Query().Get("claims") + if claimsParameter != "" { + decoder := json.NewDecoder(strings.NewReader(claimsParameter)) + err := decoder.Decode(&requestedClaims) if err != nil { - h.logger.Errorf("error preparing user information SQL: %v", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return + h.logger.Warnf( + "ignoring claims request parameter %s that could not be decoded: %v", + claimsParameter, + err, + ) } - defer func() { _ = stmt.Close() }() - - userInfo := &UserInfo{} - - err = stmt.QueryRowxContext(r.Context(), consentData.GetPayload().Subject).StructScan(userInfo) - switch { - case err == sql.ErrNoRows: - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - case err != nil: - h.logger.Errorf("error performing user information SQL: %v", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - default: - for _, scope := range consentData.GetPayload().RequestedScope { - switch scope { - case "email": - // email - // OPTIONAL. This scope value requests access to the email and email_verified Claims. - idTokenData[openid.EmailKey] = userInfo.Email - idTokenData[openid.EmailVerifiedKey] = userInfo.EmailVerified - break - case "profile": - // profile - // OPTIONAL. This scope value requests access to the End-User's default profile Claims, which - // are: name, family_name, given_name, middle_name, nickname, preferred_username, profile, - // picture, website, gender, birthdate, zoneinfo, locale, and updated_at. - idTokenData[openid.GivenNameKey] = userInfo.GivenName - idTokenData[openid.FamilyNameKey] = userInfo.FamilyName - idTokenData[openid.MiddleNameKey] = userInfo.MiddleName - idTokenData[openid.NameKey] = userInfo.GetFullName() - if userInfo.BirthDate.Valid { - idTokenData[openid.BirthdateKey] = userInfo.BirthDate.Time.Format("2006-01-02") - } - idTokenData[openid.LocaleKey] = userInfo.Language - idTokenData["https://cacert.localhost/groups"] = []string{"admin", "user"} - if userInfo.Modified.Valid { - idTokenData[openid.UpdatedAtKey] = userInfo.Modified.Time.Unix() - } - break - case "address": - // address - // OPTIONAL. This scope value requests access to the address Claim. - break - case "phone": - // phone - // OPTIONAL. This scope value requests access to the phone_number and phone_number_verified Claims. - break - } - } - sessionData := &models.ConsentRequestSession{ - AccessToken: nil, - IDToken: idTokenData, - } - consentRequest, err := h.adminClient.AcceptConsentRequest( - admin.NewAcceptConsentRequestParams().WithConsentChallenge(challenge).WithBody( - &models.AcceptConsentRequest{ - GrantAccessTokenAudience: nil, - GrantScope: consentData.GetPayload().RequestedScope, - HandledAt: models.NullTime(time.Now()), - Remember: true, - RememberFor: 86400, - Session: sessionData, - }).WithTimeout(time.Second * 10)) - if err != nil { - h.logger.Error(err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - w.Header().Add("Location", *consentRequest.GetPayload().RedirectTo) - w.WriteHeader(http.StatusFound) - return - } - } else { - consentRequest, err := h.adminClient.RejectConsentRequest( - admin.NewRejectConsentRequestParams().WithConsentChallenge(challenge).WithBody( - &models.RejectRequest{})) - if err != nil { - h.logger.Error(err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - w.Header().Add("Location", *consentRequest.GetPayload().RedirectTo) - w.WriteHeader(http.StatusFound) } } + return consentData, &requestedClaims, nil } func (h *consentHandler) renderConsentForm( w http.ResponseWriter, r *http.Request, consentData *admin.GetConsentRequestOK, + claims *commonModels.OIDCClaimsRequest, err error, localizer *i18n.Localizer, ) { @@ -235,35 +254,22 @@ func (h *consentHandler) renderConsentForm( return h.messageCatalog.LookupMessage(id, nil, localizer) } - var requestedClaims models2.OIDCClaimsRequest - requestUrl, err := url.Parse(consentData.Payload.RequestURL) - if err != nil { - h.logger.Warnf("could not parse original request URL %s: %v", consentData.Payload.RequestURL, err) - } else { - claimsParameter := requestUrl.Query().Get("claims") - if claimsParameter != "" { - decoder := json.NewDecoder(strings.NewReader(claimsParameter)) - err := decoder.Decode(&requestedClaims) - if err != nil { - h.logger.Warnf("ignoring claims request parameter %s that could not be decoded: %v", claimsParameter, err) - } - } - } - // render consent form client := consentData.GetPayload().Client err = h.consentTemplate.Lookup("base").Execute(w, map[string]interface{}{ - "Title": trans("TitleRequestConsent"), - csrf.TemplateTag: csrf.TemplateField(r), - "errors": map[string]string{}, - "client": client, - "requestedScope": h.mapRequestedScope(consentData.GetPayload().RequestedScope, localizer), - "LabelSubmit": trans("LabelSubmit"), - "LabelConsent": trans("LabelConsent"), + "Title": trans("TitleRequestConsent"), + csrf.TemplateTag: csrf.TemplateField(r), + "errors": map[string]string{}, + "client": client, + "requestedScope": h.mapRequestedScope(consentData.GetPayload().RequestedScope, localizer), + "requestedClaims": h.mapRequestedClaims(claims, localizer), + "LabelSubmit": trans("LabelSubmit"), + "LabelConsent": trans("LabelConsent"), "IntroMoreInformation": template.HTML(trans("IntroConsentMoreInformation", map[string]interface{}{ "client": client.ClientName, "clientLink": client.ClientURI, })), + "ClaimsInformation": template.HTML(trans("ClaimsInformation", nil)), "IntroConsentRequested": template.HTML(trans("IntroConsentRequested", map[string]interface{}{ "client": client.ClientName, })), @@ -278,12 +284,181 @@ type scopeWithLabel struct { func (h *consentHandler) mapRequestedScope(scope models.StringSlicePipeDelimiter, localizer *i18n.Localizer) []*scopeWithLabel { result := make([]*scopeWithLabel, 0) for _, scopeName := range scope { - result = append(result, &scopeWithLabel{Name: scopeName, Label: h.messageCatalog.LookupMessage( - fmt.Sprintf("Scope-%s-Description", scopeName), nil, localizer)}) + if _, ok := supportedScopes[scopeName]; !ok { + h.logger.Warnf("unsupported scope %s ignored", scopeName) + continue + } + label, err := localizer.Localize(&i18n.LocalizeConfig{ + DefaultMessage: supportedScopes[scopeName], + }) + if err != nil { + h.logger.Warnf("could not localize label for scope %s: %v", scopeName, err) + label = scopeName + } + result = append(result, &scopeWithLabel{Name: scopeName, Label: label}) } return result } +type claimWithLabel struct { + Name string + Label string + Essential bool +} + +func (h *consentHandler) mapRequestedClaims(claims *commonModels.OIDCClaimsRequest, localizer *i18n.Localizer) []*claimWithLabel { + result := make([]*claimWithLabel, 0) + known := make(map[string]bool) + + for _, claimElement := range []*commonModels.ClaimElement{claims.GetUserInfo(), claims.GetIDToken()} { + if claimElement != nil { + for k, v := range *claimElement { + if _, ok := supportedClaims[k]; !ok { + h.logger.Warnf("unsupported claim %s ignored", k) + continue + } + label, err := localizer.Localize(&i18n.LocalizeConfig{ + DefaultMessage: supportedClaims[k], + }) + if err != nil { + h.logger.Warnf("could not localize label for claim %s: %v", k, err) + label = k + } + if !known[k] { + result = append(result, &claimWithLabel{ + Name: k, + Label: label, + Essential: v.IsEssential(), + }) + known[k] = true + } + } + } + } + return result +} + +func (h *consentHandler) getSessionData( + info ConsentInformation, + claims *commonModels.OIDCClaimsRequest, + payload *models.ConsentRequest, + ctx context.Context, +) (*models.ConsentRequestSession, error) { + idTokenData := make(map[string]interface{}, 0) + accessTokenData := make(map[string]interface{}, 0) + + db := services.GetDb(h.context) + stmt, err := db.PreparexContext( + ctx, + `SELECT email, verified, fname, mname, lname, dob, language, modified +FROM users +WHERE uniqueid = ? + AND locked = 0`, + ) + if err != nil { + h.logger.Errorf("error preparing user information SQL: %v", err) + return nil, err + } + defer func() { _ = stmt.Close() }() + + userInfo := &UserInfo{} + + err = stmt.QueryRowxContext(ctx, payload.Subject).StructScan(userInfo) + switch { + case err == sql.ErrNoRows: + h.logger.Errorf("could not find entry for subject %s", payload.Subject) + return nil, err + case err != nil: + h.logger.Errorf("error performing user information SQL: %v", err) + return nil, err + default: + h.fillTokenData(accessTokenData, payload.RequestedScope, claims, info, userInfo) + h.fillTokenData(idTokenData, payload.RequestedScope, claims, info, userInfo) + return &models.ConsentRequestSession{ + AccessToken: accessTokenData, + IDToken: idTokenData, + }, nil + } +} + +func (h *consentHandler) fillTokenData(m map[string]interface{}, requestedScope models.StringSlicePipeDelimiter, claimsRequest *commonModels.OIDCClaimsRequest, consentInformation ConsentInformation, userInfo *UserInfo) { + for _, scope := range requestedScope { + granted := false + for _, k := range consentInformation.GrantedScopes { + if k == scope { + granted = true + break + } + } + if !granted { + continue + } + switch scope { + case ScopeEmail: + // email + // OPTIONAL. This scope value requests access to the email and + // email_verified Claims. + m[openid.EmailKey] = userInfo.Email + m[openid.EmailVerifiedKey] = userInfo.EmailVerified + break + case ScopeProfile: + // profile + // OPTIONAL. This scope value requests access to the + // End-User's default profile Claims, which are: name, + // family_name, given_name, middle_name, nickname, + // preferred_username, profile, picture, website, gender, + // birthdate, zoneinfo, locale, and updated_at. + m[openid.GivenNameKey] = userInfo.GivenName + m[openid.FamilyNameKey] = userInfo.FamilyName + m[openid.MiddleNameKey] = userInfo.MiddleName + m[openid.NameKey] = userInfo.GetFullName() + if userInfo.BirthDate.Valid { + m[openid.BirthdateKey] = userInfo.BirthDate.Time.Format("2006-01-02") + } + m[openid.LocaleKey] = userInfo.Language + if userInfo.Modified.Valid { + m[openid.UpdatedAtKey] = userInfo.Modified.Time.Unix() + } + break + } + } + if userInfoClaims := claimsRequest.GetUserInfo(); userInfoClaims != nil { + for claimName, claim := range *userInfoClaims { + granted := false + for _, k := range consentInformation.SelectedClaims { + if k == claimName { + granted = true + break + } + } + if !granted { + continue + } + if claim.WantedValue() != nil { + m[claimName] = *claim.WantedValue() + continue + } + switch claimName { + case ClaimCAcertGroups: + m[claimName] = []string{"admin", "user"} + break + default: + if claim.IsEssential() { + h.logger.Warnf( + "handling for essential claim name %s not implemented", + claimName, + ) + } else { + h.logger.Warnf( + "handling for claim name %s not implemented", + claimName, + ) + } + } + } + } +} + func NewConsentHandler(ctx context.Context, logger *log.Logger) (*consentHandler, error) { consentTemplate, err := template.ParseFiles( "templates/idp/base.gohtml", "templates/idp/consent.gohtml") diff --git a/idp/services/i18n.go b/idp/services/i18n.go index a35eed5..7a61cd8 100644 --- a/idp/services/i18n.go +++ b/idp/services/i18n.go @@ -44,27 +44,15 @@ func AddMessages(ctx context.Context) { } messages["IntroConsentRequested"] = &i18n.Message{ ID: "IntroConsentRequested", - Other: "The {{ .client }} application wants your consent for the requested set of permissions.", + Other: "The {{ .client }} application requested your consent for the following set of permissions:", } messages["IntroConsentMoreInformation"] = &i18n.Message{ ID: "IntroConsentMoreInformation", Other: "You can find more information about {{ .client }} at its description page.", } - messages["Scope-openid-Description"] = &i18n.Message{ - ID: "Scope-openid-Description", - Other: "Request information about your identity.", - } - messages["Scope-offline_access-Description"] = &i18n.Message{ - ID: "Scope-offline_access-Description", - Other: "Keep access to your information until you revoke the permission.", - } - messages["Scope-profile-Description"] = &i18n.Message{ - ID: "Scope-profile-Description", - Other: "Access your user profile information including your name, birth date and locale.", - } - messages["Scope-email-Description"] = &i18n.Message{ - ID: "Scope-email-Description", - Other: "Access your primary email address.", + messages["ClaimsInformation"] = &i18n.Message{ + ID: "ClaimsInformation", + Other: "In addition the application wants access to the following information:", } messages["WrongOrLockedUserOrInvalidPassword"] = &i18n.Message{ ID: "WrongOrLockedUserOrInvalidPassword", diff --git a/templates/idp/consent.gohtml b/templates/idp/consent.gohtml index 285d975..8b6d052 100644 --- a/templates/idp/consent.gohtml +++ b/templates/idp/consent.gohtml @@ -2,17 +2,30 @@