diff --git a/docs/docs/apis/actions/code-examples.mdx b/docs/docs/apis/actions/code-examples.mdx index 7af3bc99f1..f4fbf75de2 100644 --- a/docs/docs/apis/actions/code-examples.mdx +++ b/docs/docs/apis/actions/code-examples.mdx @@ -68,6 +68,19 @@ https://github.com/zitadel/actions/blob/main/examples/custom_roles.js +### Custom role mapping including org metadata in claims + +There's even a possibility to use the metadata of organizations the user is granted to + +
+Code example + +```js reference +https://github.com/zitadel/actions/blob/main/examples/custom_roles_org_metadata.js +``` + +
+ ## Customize SAML response Append attributes returned on SAML requests. diff --git a/docs/docs/apis/actions/objects.md b/docs/docs/apis/actions/objects.md index 63f5cb5619..41307ee580 100644 --- a/docs/docs/apis/actions/objects.md +++ b/docs/docs/apis/actions/objects.md @@ -210,3 +210,5 @@ This object represents a list of user grant stored in ZITADEL. The name of the organization, where the user was granted - `projectId` *string* - `projectName` *string* + - `getOrgMetadata()` [*metadataResult*](#metadata-result) + Get the metadata of the organization where the user was granted \ No newline at end of file diff --git a/internal/actions/object/metadata.go b/internal/actions/object/metadata.go index 87ad69737a..b768b15808 100644 --- a/internal/actions/object/metadata.go +++ b/internal/actions/object/metadata.go @@ -1,6 +1,7 @@ package object import ( + "context" "encoding/json" "time" @@ -77,6 +78,21 @@ func UserMetadataListFromSlice(c *actions.FieldConfig, metadata []query.UserMeta return c.Runtime.ToValue(result) } +func GetOrganizationMetadata(ctx context.Context, queries *query.Queries, c *actions.FieldConfig, organizationID string) goja.Value { + metadata, err := queries.SearchOrgMetadata( + ctx, + true, + organizationID, + &query.OrgMetadataSearchQueries{}, + false, + ) + if err != nil { + logging.WithError(err).Info("unable to get org metadata in action") + panic(err) + } + return OrgMetadataListFromQuery(c, metadata) +} + func metadataByteArrayToValue(val []byte, runtime *goja.Runtime) goja.Value { var value interface{} if !json.Valid(val) { diff --git a/internal/actions/object/user_grant.go b/internal/actions/object/user_grant.go index 21267a611c..efa8f9cc66 100644 --- a/internal/actions/object/user_grant.go +++ b/internal/actions/object/user_grant.go @@ -1,6 +1,7 @@ package object import ( + "context" "time" "github.com/dop251/goja" @@ -44,6 +45,8 @@ type userGrant struct { ProjectId string ProjectName string + + GetOrgMetadata func(goja.FunctionCall) goja.Value } func AppendGrantFunc(userGrants *UserGrants) func(c *actions.FieldConfig) func(call goja.FunctionCall) goja.Value { @@ -58,10 +61,11 @@ func AppendGrantFunc(userGrants *UserGrants) func(c *actions.FieldConfig) func(c } } -func UserGrantsFromQuery(c *actions.FieldConfig, userGrants *query.UserGrants) goja.Value { +func UserGrantsFromQuery(ctx context.Context, queries *query.Queries, c *actions.FieldConfig, userGrants *query.UserGrants) goja.Value { if userGrants == nil { return c.Runtime.ToValue(nil) } + orgMetadata := make(map[string]goja.Value) grantList := &userGrantList{ Count: userGrants.Count, Sequence: userGrants.Sequence, @@ -84,16 +88,24 @@ func UserGrantsFromQuery(c *actions.FieldConfig, userGrants *query.UserGrants) g UserGrantResourceOwnerName: grant.OrgName, ProjectId: grant.ProjectID, ProjectName: grant.ProjectName, + GetOrgMetadata: func(call goja.FunctionCall) goja.Value { + if md, ok := orgMetadata[grant.ResourceOwner]; ok { + return md + } + orgMetadata[grant.ResourceOwner] = GetOrganizationMetadata(ctx, queries, c, grant.ResourceOwner) + return orgMetadata[grant.ResourceOwner] + }, } } return c.Runtime.ToValue(grantList) } -func UserGrantsFromSlice(c *actions.FieldConfig, userGrants []query.UserGrant) goja.Value { +func UserGrantsFromSlice(ctx context.Context, queries *query.Queries, c *actions.FieldConfig, userGrants []query.UserGrant) goja.Value { if userGrants == nil { return c.Runtime.ToValue(nil) } + orgMetadata := make(map[string]goja.Value) grantList := &userGrantList{ Count: uint64(len(userGrants)), Grants: make([]*userGrant, len(userGrants)), @@ -114,6 +126,13 @@ func UserGrantsFromSlice(c *actions.FieldConfig, userGrants []query.UserGrant) g UserGrantResourceOwnerName: grant.OrgName, ProjectId: grant.ProjectID, ProjectName: grant.ProjectName, + GetOrgMetadata: func(goja.FunctionCall) goja.Value { + if md, ok := orgMetadata[grant.ResourceOwner]; ok { + return md + } + orgMetadata[grant.ResourceOwner] = GetOrganizationMetadata(ctx, queries, c, grant.ResourceOwner) + return orgMetadata[grant.ResourceOwner] + }, } } diff --git a/internal/api/oidc/client.go b/internal/api/oidc/client.go index bdbf6bd67b..a80d4ad95e 100644 --- a/internal/api/oidc/client.go +++ b/internal/api/oidc/client.go @@ -490,25 +490,16 @@ func (o *OPStorage) userinfoFlows(ctx context.Context, user *query.User, userGra return object.UserMetadataListFromQuery(c, metadata) } }), - actions.SetFields("grants", func(c *actions.FieldConfig) interface{} { - return object.UserGrantsFromQuery(c, userGrants) - }), + actions.SetFields("grants", + func(c *actions.FieldConfig) interface{} { + return object.UserGrantsFromQuery(ctx, o.query, c, userGrants) + }, + ), ), actions.SetFields("org", actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} { return func(goja.FunctionCall) goja.Value { - metadata, err := o.query.SearchOrgMetadata( - ctx, - true, - user.ResourceOwner, - &query.OrgMetadataSearchQueries{}, - false, - ) - if err != nil { - logging.WithError(err).Info("unable to get org metadata in action") - panic(err) - } - return object.OrgMetadataListFromQuery(c, metadata) + return object.GetOrganizationMetadata(ctx, o.query, c, user.ResourceOwner) } }), ), @@ -714,24 +705,13 @@ func (o *OPStorage) privateClaimsFlows(ctx context.Context, userID string, userG } }), actions.SetFields("grants", func(c *actions.FieldConfig) interface{} { - return object.UserGrantsFromQuery(c, userGrants) + return object.UserGrantsFromQuery(ctx, o.query, c, userGrants) }), ), actions.SetFields("org", actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} { return func(goja.FunctionCall) goja.Value { - metadata, err := o.query.SearchOrgMetadata( - ctx, - true, - user.ResourceOwner, - &query.OrgMetadataSearchQueries{}, - false, - ) - if err != nil { - logging.WithError(err).Info("unable to get org metadata in action") - panic(err) - } - return object.OrgMetadataListFromQuery(c, metadata) + return object.GetOrganizationMetadata(ctx, o.query, c, user.ResourceOwner) } }), ), diff --git a/internal/api/oidc/userinfo.go b/internal/api/oidc/userinfo.go index c59e9c0952..6d01e6bf9c 100644 --- a/internal/api/oidc/userinfo.go +++ b/internal/api/oidc/userinfo.go @@ -252,24 +252,13 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user } }), actions.SetFields("grants", func(c *actions.FieldConfig) interface{} { - return object.UserGrantsFromSlice(c, qu.UserGrants) + return object.UserGrantsFromSlice(ctx, s.query, c, qu.UserGrants) }), ), actions.SetFields("org", actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} { return func(goja.FunctionCall) goja.Value { - metadata, err := s.query.SearchOrgMetadata( - ctx, - true, - qu.User.ResourceOwner, - &query.OrgMetadataSearchQueries{}, - false, - ) - if err != nil { - logging.WithError(err).Info("unable to get org metadata in action") - panic(err) - } - return object.OrgMetadataListFromQuery(c, metadata) + return object.GetOrganizationMetadata(ctx, s.query, c, qu.User.ResourceOwner) } }), ), diff --git a/internal/api/saml/storage.go b/internal/api/saml/storage.go index 542be9d6bc..bd8afffe54 100644 --- a/internal/api/saml/storage.go +++ b/internal/api/saml/storage.go @@ -246,24 +246,13 @@ func (p *Storage) getCustomAttributes(ctx context.Context, user *query.User, use } }), actions.SetFields("grants", func(c *actions.FieldConfig) interface{} { - return object.UserGrantsFromQuery(c, userGrants) + return object.UserGrantsFromQuery(ctx, p.query, c, userGrants) }), ), actions.SetFields("org", actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} { return func(goja.FunctionCall) goja.Value { - metadata, err := p.query.SearchOrgMetadata( - ctx, - true, - user.ResourceOwner, - &query.OrgMetadataSearchQueries{}, - false, - ) - if err != nil { - logging.WithError(err).Info("unable to get org metadata in action") - panic(err) - } - return object.OrgMetadataListFromQuery(c, metadata) + return object.GetOrganizationMetadata(ctx, p.query, c, user.ResourceOwner) } }), ),