/* * Copyright 2017-2019 Kopano and its licensors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package identity import ( "fmt" "github.com/golang-jwt/jwt/v5" "github.com/libregraph/oidc-go" konnectoidc "github.com/libregraph/lico/oidc" "github.com/libregraph/lico/oidc/payload" ) // AuthorizeScopes uses the provided manager and user to filter the provided // scopes and returns a mapping of only the authorized scopes. func AuthorizeScopes(manager Manager, user User, scopes map[string]bool) (map[string]bool, map[string]bool) { if user == nil { return nil, nil } authorizedScopes := make(map[string]bool) unauthorizedScopes := make(map[string]bool) supportedScopes := make(map[string]bool) for _, scope := range manager.ScopesSupported(scopes) { supportedScopes[scope] = true } for scope, authorizedScope := range scopes { for { if !authorizedScope { // Incoming not authorized. break } authorizedScope = isKnownScope(scope) if !authorizedScope { if _, ok := supportedScopes[scope]; ok { authorizedScope = true } } break } if authorizedScope { authorizedScopes[scope] = true } else { unauthorizedScopes[scope] = false } } return authorizedScopes, unauthorizedScopes } // GetUserClaimsForScopes returns a mapping of user claims of the provided user // filtered by the provided scopes. func GetUserClaimsForScopes(user User, scopes map[string]bool, requestedClaimsMaps []*payload.ClaimsRequestMap) map[string]jwt.Claims { if user == nil { return nil } claims := make(map[string]jwt.Claims) if authorizedScope, _ := scopes[oidc.ScopeEmail]; authorizedScope { if userWithEmail, ok := user.(UserWithEmail); ok { claims[oidc.ScopeEmail] = &konnectoidc.EmailClaims{ Email: userWithEmail.Email(), EmailVerified: userWithEmail.EmailVerified(), } } } if authorizedScope, _ := scopes[oidc.ScopeProfile]; authorizedScope { var profileClaims *konnectoidc.ProfileClaims if userWithProfile, ok := user.(UserWithProfile); ok { profileClaims = &konnectoidc.ProfileClaims{ Name: userWithProfile.Name(), FamilyName: userWithProfile.FamilyName(), GivenName: userWithProfile.GivenName(), } } if userWithUsername, ok := user.(UserWithUsername); ok { if profileClaims == nil { profileClaims = &konnectoidc.ProfileClaims{ PreferredUsername: userWithUsername.Username(), } } else { profileClaims.PreferredUsername = userWithUsername.Username() } } if profileClaims != nil { claims[oidc.ScopeProfile] = profileClaims } } // Add additional supported values for email and profile claims. unknownRequestedClaimsWithValue := make(map[string]interface{}) for _, requestedClaimMap := range requestedClaimsMaps { for requestedClaim, requestedClaimEntry := range *requestedClaimMap { // NOTE(longsleep): We ignore the actuall value of the claim request // and always return requested scopes with standard behavior. if scope, ok := payload.GetScopeForClaim(requestedClaim); ok { if authorizedScope, _ := scopes[scope]; !authorizedScope { // Add claim values if known. switch scope { case oidc.ScopeEmail: if userWithEmail, ok := user.(UserWithEmail); ok { scopeClaims := konnectoidc.NewEmailClaims(claims[scope]) if scopeClaims == nil { scopeClaims = &konnectoidc.EmailClaims{} claims[scope] = scopeClaims } switch requestedClaim { case oidc.EmailClaim: scopeClaims.Email = userWithEmail.Email() fallthrough // Always include EmailVerified claim. case oidc.EmailVerifiedClaim: scopeClaims.EmailVerified = userWithEmail.EmailVerified() } } case oidc.ScopeProfile: if userWithProfile, ok := user.(UserWithProfile); ok { scopeClaims := konnectoidc.NewProfileClaims(claims[scope]) if scopeClaims == nil { scopeClaims = &konnectoidc.ProfileClaims{} claims[scope] = scopeClaims } switch requestedClaim { case oidc.NameClaim: scopeClaims.Name = userWithProfile.Name() case oidc.FamilyNameClaim: scopeClaims.Name = userWithProfile.FamilyName() case oidc.GivenNameClaim: scopeClaims.Name = userWithProfile.GivenName() } } } } } else { // Add claims which are unknown here to a list of unknown claims // with value if the requested claim is with value. This returns // the requested claim as is with the provided value. if requestedClaimEntry != nil && requestedClaimEntry.Value != nil { unknownRequestedClaimsWithValue[requestedClaim] = requestedClaimEntry.Value } } } } // Add extra claims. Those can either come from the backend user if it // has own scoped claims or might be defined as value by the request. var claimsWithoutScope jwt.MapClaims if userWithScopedClaims, ok := user.(UserWithScopedClaims); ok { // Inject additional scope claims. claimsWithoutScope = userWithScopedClaims.ScopedClaims(scopes) } if len(unknownRequestedClaimsWithValue) > 0 { if claimsWithoutScope == nil { claimsWithoutScope = make(jwt.MapClaims) } for claim, value := range unknownRequestedClaimsWithValue { claimsWithoutScope[claim] = value } } if claimsWithoutScope != nil { claims[""] = claimsWithoutScope } return claims } // GetSessionRef builds a per user and audience unique identifier. func GetSessionRef(label string, audience string, userID string) *string { if userID == "" { return nil } // NOTE(longsleep): For now we ignore the audience. Seems not to have any // use to keep multiple sessions from Konnect per audience. sessionRef := fmt.Sprintf("%s:-:%s", label, userID) return &sessionRef } func isKnownScope(scope string) bool { // Only authorize the scopes we know. switch scope { case oidc.ScopeOpenID: default: // Unknown scopes end up here and are not getting authorized. return false } return true }