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: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: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

View File

@ -2,6 +2,7 @@ package oidc
import (
"context"
"encoding/base64"
"strings"
"github.com/caos/oidc/pkg/oidc"
@ -22,14 +23,10 @@ import (
)
const (
scopeOpenID = "openid"
scopeProfile = "profile"
scopeEmail = "email"
scopePhone = "phone"
scopeAddress = "address"
ScopeProjectRolePrefix = "urn:zitadel:iam:org:project:role:"
ClaimProjectRoles = "urn:zitadel:iam:org:project:roles"
ScopeUserMetaData = "urn:zitadel:iam:user:metadata"
ClaimUserMetaData = ScopeUserMetaData
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 == "" {
return nil
@ -197,7 +201,6 @@ func (o *OPStorage) SetUserinfoFromScopes(ctx context.Context, userInfo oidc.Use
if len(projectRoles) > 0 {
userInfo.AppendClaims(ClaimProjectRoles, projectRoles)
}
return nil
}
@ -243,6 +246,13 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
if len(projectRoles) > 0 {
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
}
@ -264,6 +274,19 @@ func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID strin
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) {
for _, grantedRole := range grant.RoleKeys {
if requestedRole == grantedRole {

View File

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

View File

@ -307,16 +307,27 @@ func (repo *UserRepo) GetMyMetadataByKey(ctx context.Context, key string) (*doma
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) {
ctxData := authz.GetCtxData(ctx)
err := req.EnsureLimit(repo.SearchLimit)
if err != nil {
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()
logging.Log("EVENT-N9fsd").OnError(sequenceErr).Warn("could not read latest user sequence")
req.AppendAggregateIDQuery(ctxData.UserID)
req.AppendResourceOwnerQuery(ctxData.ResourceOwner)
req.AppendAggregateIDQuery(userID)
if resourceOwner != "" {
req.AppendResourceOwnerQuery(resourceOwner)
}
metadata, count, err := repo.View.SearchMetadata(req)
if err != nil {
return nil, err

View File

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