feat: add user metadata scope and claim (#2268)

* feat: add user metadata scope and claime

* docs: scope

* docs: metadata base 64 encoded

* docs: metadata base 64 encoded

* docs: metadata base 64 encoded
This commit is contained in:
Fabi 2021-08-26 13:38:13 +02:00 committed by GitHub
parent 1da68420bc
commit 5b4c64d740
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 49 additions and 9 deletions

View File

@ -28,5 +28,6 @@ In addition to the standard compliant scopes we utilize the following scopes.
| urn:zitadel:iam:org:domain:primary:{domainname} | `urn:zitadel:iam:org:domain:primary:acme.ch` | When requesting this scope **ZITADEL** will enforce that the user is a member of the selected organization. If the organization does not exist a failure is displayed | | urn:zitadel:iam:org:domain:primary:{domainname} | `urn:zitadel:iam:org:domain:primary:acme.ch` | When requesting this scope **ZITADEL** will enforce that the user is a member of the selected organization. If the organization does not exist a failure is displayed |
| urn:zitadel:iam:role:{rolename} | | | | urn:zitadel:iam:role:{rolename} | | |
| `urn:zitadel:iam:org:project:id:{projectid}:aud` | ZITADEL's Project id is `urn:zitadel:iam:org:project:id:69234237810729019:aud` | By adding this scope, the requested projectid will be added to the audience of the access and id token | | `urn:zitadel:iam:org:project:id:{projectid}:aud` | ZITADEL's Project id is `urn:zitadel:iam:org:project:id:69234237810729019:aud` | By adding this scope, the requested projectid will be added to the audience of the access and id token |
| urn:zitadel:iam:user:metadata | `urn:zitadel:iam:user:metadata` | By adding this scope, the metadata of the user will be included in the token. The values are base64 encoded. |
> If access to ZITADEL's API's is needed with a service user the scope `urn:zitadel:iam:org:project:id:69234237810729019:aud` needs to be used with the JWT Profile request > If access to ZITADEL's API's is needed with a service user the scope `urn:zitadel:iam:org:project:id:69234237810729019:aud` needs to be used with the JWT Profile request

View File

@ -2,6 +2,7 @@ package oidc
import ( import (
"context" "context"
"encoding/base64"
"strings" "strings"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/oidc"
@ -22,14 +23,10 @@ import (
) )
const ( const (
scopeOpenID = "openid"
scopeProfile = "profile"
scopeEmail = "email"
scopePhone = "phone"
scopeAddress = "address"
ScopeProjectRolePrefix = "urn:zitadel:iam:org:project:role:" ScopeProjectRolePrefix = "urn:zitadel:iam:org:project:role:"
ClaimProjectRoles = "urn:zitadel:iam:org:project:roles" ClaimProjectRoles = "urn:zitadel:iam:org:project:roles"
ScopeUserMetaData = "urn:zitadel:iam:user:metadata"
ClaimUserMetaData = ScopeUserMetaData
oidcCtx = "oidc" oidcCtx = "oidc"
) )
@ -186,6 +183,13 @@ func (o *OPStorage) SetUserinfoFromScopes(ctx context.Context, userInfo oidc.Use
} }
} }
} }
userMetaData, err := o.assertUserMetaData(ctx, userID)
if err != nil {
return err
}
if len(userMetaData) > 0 {
userInfo.AppendClaims(ClaimUserMetaData, userMetaData)
}
if len(roles) == 0 || applicationID == "" { if len(roles) == 0 || applicationID == "" {
return nil return nil
@ -197,7 +201,6 @@ func (o *OPStorage) SetUserinfoFromScopes(ctx context.Context, userInfo oidc.Use
if len(projectRoles) > 0 { if len(projectRoles) > 0 {
userInfo.AppendClaims(ClaimProjectRoles, projectRoles) userInfo.AppendClaims(ClaimProjectRoles, projectRoles)
} }
return nil return nil
} }
@ -243,6 +246,13 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
if len(projectRoles) > 0 { if len(projectRoles) > 0 {
claims = appendClaim(claims, ClaimProjectRoles, projectRoles) claims = appendClaim(claims, ClaimProjectRoles, projectRoles)
} }
userMetaData, err := o.assertUserMetaData(ctx, userID)
if err != nil {
return nil, err
}
if len(userMetaData) > 0 {
claims = appendClaim(claims, ClaimUserMetaData, userMetaData)
}
return claims, err return claims, err
} }
@ -264,6 +274,19 @@ func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID strin
return projectRoles, nil return projectRoles, nil
} }
func (o *OPStorage) assertUserMetaData(ctx context.Context, userID string) (map[string]string, error) {
metaData, err := o.repo.SearchUserMetadata(ctx, userID)
if err != nil {
return nil, err
}
userMetaData := make(map[string]string)
for _, md := range metaData.Result {
userMetaData[md.Key] = base64.RawURLEncoding.EncodeToString(md.Value)
}
return userMetaData, nil
}
func checkGrantedRoles(roles map[string]map[string]string, grant *grant_model.UserGrantView, requestedRole string) { func checkGrantedRoles(roles map[string]map[string]string, grant *grant_model.UserGrantView, requestedRole string) {
for _, grantedRole := range grant.RoleKeys { for _, grantedRole := range grant.RoleKeys {
if requestedRole == grantedRole { if requestedRole == grantedRole {

View File

@ -106,6 +106,9 @@ func (c *Client) IsScopeAllowed(scope string) bool {
if strings.HasPrefix(scope, authreq_model.ProjectIDScope) { if strings.HasPrefix(scope, authreq_model.ProjectIDScope) {
return true return true
} }
if strings.HasPrefix(scope, ScopeUserMetaData) {
return true
}
for _, allowedScope := range c.allowedScopes { for _, allowedScope := range c.allowedScopes {
if scope == allowedScope { if scope == allowedScope {
return true return true

View File

@ -307,16 +307,27 @@ func (repo *UserRepo) GetMyMetadataByKey(ctx context.Context, key string) (*doma
return iam_model.MetadataViewToDomain(data), nil return iam_model.MetadataViewToDomain(data), nil
} }
func (repo *UserRepo) SearchUserMetadata(ctx context.Context, userID string) (*domain.MetadataSearchResponse, error) {
req := new(domain.MetadataSearchRequest)
return repo.searchUserMetadata(userID, "", req)
}
func (repo *UserRepo) SearchMyMetadata(ctx context.Context, req *domain.MetadataSearchRequest) (*domain.MetadataSearchResponse, error) { func (repo *UserRepo) SearchMyMetadata(ctx context.Context, req *domain.MetadataSearchRequest) (*domain.MetadataSearchResponse, error) {
ctxData := authz.GetCtxData(ctx) ctxData := authz.GetCtxData(ctx)
err := req.EnsureLimit(repo.SearchLimit) err := req.EnsureLimit(repo.SearchLimit)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return repo.searchUserMetadata(ctxData.UserID, ctxData.ResourceOwner, req)
}
func (repo *UserRepo) searchUserMetadata(userID, resourceOwner string, req *domain.MetadataSearchRequest) (*domain.MetadataSearchResponse, error) {
sequence, sequenceErr := repo.View.GetLatestUserSequence() sequence, sequenceErr := repo.View.GetLatestUserSequence()
logging.Log("EVENT-N9fsd").OnError(sequenceErr).Warn("could not read latest user sequence") logging.Log("EVENT-N9fsd").OnError(sequenceErr).Warn("could not read latest user sequence")
req.AppendAggregateIDQuery(ctxData.UserID) req.AppendAggregateIDQuery(userID)
req.AppendResourceOwnerQuery(ctxData.ResourceOwner) if resourceOwner != "" {
req.AppendResourceOwnerQuery(resourceOwner)
}
metadata, count, err := repo.View.SearchMetadata(req) metadata, count, err := repo.View.SearchMetadata(req)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -21,6 +21,8 @@ type UserRepository interface {
MachineKeyByID(ctx context.Context, keyID string) (*key_model.AuthNKeyView, error) MachineKeyByID(ctx context.Context, keyID string) (*key_model.AuthNKeyView, error)
SearchUsers(ctx context.Context, request *model.UserSearchRequest) (*model.UserSearchResponse, error) SearchUsers(ctx context.Context, request *model.UserSearchRequest) (*model.UserSearchResponse, error)
SearchUserMetadata(ctx context.Context, userID string) (*domain.MetadataSearchResponse, error)
} }
type myUserRepo interface { type myUserRepo interface {