feat: add saml custom attribute action and translations (#6341)

* feat: add saml custom attribute action and translations

* chore: update saml dependency

* fix: apply suggestions from code review

Co-authored-by: Livio Spring <livio.a@gmail.com>

* fix: custom attribute action with variadic parameter

* docs: add customize saml response docs

* docs: update docs/docs/apis/actions/customize-samlresponse.md

Co-authored-by: Livio Spring <livio.a@gmail.com>

* docs: update docs/docs/apis/actions/customize-samlresponse.md

Co-authored-by: Livio Spring <livio.a@gmail.com>

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Stefan Benz 2023-08-15 17:04:45 +02:00 committed by GitHub
parent d83681a928
commit 26b28ed2af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 243 additions and 26 deletions

View File

@ -0,0 +1,28 @@
---
title: Complement SAMLResponse
---
This flow is executed before the return of the SAMLResponse.
## Pre SAMLResponse creation
This trigger is called before attributes are set in the SAMLResponse.
### Parameters of Pre SAMLResponse creation
- `ctx`
The first parameter contains the following fields:
- `v1`
- `getUser()` [*User*](./objects#user)
- `user`
- `getMetadata()` [*metadataResult*](./objects#metadata-result)
- `grants` [*UserGrantList*](./objects#user-grant-list)
- `api`
The second parameter contains the following fields:
- `v1`
- `attributes`
- `setCustomAttribute(string, string, ...string)`
Sets any value as attribute in addition to the default attributes, if the key is not already present. The parameters represent the key, nameFormat and the attributeValue(s).
- `user`
- `setMetadata(string, Any)`
Key of the metadata and any value

View File

@ -566,6 +566,7 @@ module.exports = {
"apis/actions/internal-authentication", "apis/actions/internal-authentication",
"apis/actions/external-authentication", "apis/actions/external-authentication",
"apis/actions/complement-token", "apis/actions/complement-token",
"apis/actions/customize-samlresponse",
"apis/actions/objects", "apis/actions/objects",
] ]
}, },

6
go.mod
View File

@ -61,7 +61,7 @@ require (
github.com/zitadel/logging v0.3.4 github.com/zitadel/logging v0.3.4
github.com/zitadel/oidc/v2 v2.7.0 github.com/zitadel/oidc/v2 v2.7.0
github.com/zitadel/passwap v0.3.0 github.com/zitadel/passwap v0.3.0
github.com/zitadel/saml v0.0.11 github.com/zitadel/saml v0.1.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.40.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.40.0
go.opentelemetry.io/otel v1.14.0 go.opentelemetry.io/otel v1.14.0
@ -115,7 +115,7 @@ require (
cloud.google.com/go/trace v1.9.0 // indirect cloud.google.com/go/trace v1.9.0 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/amdonov/xmlsig v0.1.0 // indirect github.com/amdonov/xmlsig v0.1.0 // indirect
github.com/beevik/etree v1.1.0 // indirect github.com/beevik/etree v1.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/cenkalti/backoff/v4 v4.2.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
@ -181,7 +181,7 @@ require (
github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect
github.com/rs/xid v1.4.0 // indirect github.com/rs/xid v1.4.0 // indirect
github.com/russellhaering/goxmldsig v1.3.0 // indirect github.com/russellhaering/goxmldsig v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/afero v1.9.5 // indirect github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect

15
go.sum
View File

@ -105,8 +105,9 @@ github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6l
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/beevik/etree v1.2.0 h1:l7WETslUG/T+xOPs47dtd6jov2Ii/8/OjCldk5fYfQw=
github.com/beevik/etree v1.2.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
@ -280,6 +281,7 @@ github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXg
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@ -789,8 +791,8 @@ github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russellhaering/goxmldsig v1.3.0 h1:DllIWUgMy0cRUMfGiASiYEa35nsieyD3cigIwLonTPM= github.com/russellhaering/goxmldsig v1.4.0 h1:8UcDh/xGyQiyrW+Fq5t8f+l2DLB1+zlhYzkPUJ7Qhys=
github.com/russellhaering/goxmldsig v1.3.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@ -900,8 +902,10 @@ github.com/zitadel/oidc/v2 v2.7.0 h1:IGX4EDk6tegTjUSsZDWeTfLseFU0BdJ/Glf1tgys2lU
github.com/zitadel/oidc/v2 v2.7.0/go.mod h1:zkUkVJS0sDVy9m0UA9RgO3f8i/C0rtjvXU36UJj7T+0= github.com/zitadel/oidc/v2 v2.7.0/go.mod h1:zkUkVJS0sDVy9m0UA9RgO3f8i/C0rtjvXU36UJj7T+0=
github.com/zitadel/passwap v0.3.0 h1:kC/vzN9xQlEQjUAZs0z2P5nKrZs9AuTqprteSQ2S4Ag= github.com/zitadel/passwap v0.3.0 h1:kC/vzN9xQlEQjUAZs0z2P5nKrZs9AuTqprteSQ2S4Ag=
github.com/zitadel/passwap v0.3.0/go.mod h1:sIpG6HfmnP28qwxu8kf+ot53ERbLwU9fOITstAwZSms= github.com/zitadel/passwap v0.3.0/go.mod h1:sIpG6HfmnP28qwxu8kf+ot53ERbLwU9fOITstAwZSms=
github.com/zitadel/saml v0.0.11 h1:kObucnBrcu1PHCO7RGT0iVeuJL/5I50gUgr40S41nMs= github.com/zitadel/saml v0.0.12-0.20230809152136-3bdfd7ad13fe h1:mcdiFREwddgcJjK+KDl/J29Cikr0nymiFhYdp3DBKB4=
github.com/zitadel/saml v0.0.11/go.mod h1:YGWAvPZRv4DbEZ78Ht/2P0AWzGn+6WGhFf90PMXl0Po= github.com/zitadel/saml v0.0.12-0.20230809152136-3bdfd7ad13fe/go.mod h1:M+X+3vMUulpoLofKeH/W1/qjQQ3owitc2GuGDu3oYpM=
github.com/zitadel/saml v0.1.0 h1:FZKKFRCamoKmFH3kGOW0ObcDozaJz7NdHn+WPm8PcXc=
github.com/zitadel/saml v0.1.0/go.mod h1:M+X+3vMUulpoLofKeH/W1/qjQQ3owitc2GuGDu3oYpM=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
@ -1009,6 +1013,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=

View File

@ -19,6 +19,8 @@ func FlowTypeToDomain(flowType string) domain.FlowType {
return domain.FlowTypeCustomiseToken return domain.FlowTypeCustomiseToken
case domain.FlowTypeInternalAuthentication.ID(): case domain.FlowTypeInternalAuthentication.ID():
return domain.FlowTypeInternalAuthentication return domain.FlowTypeInternalAuthentication
case domain.FlowTypeCustomizeSAMLResponse.ID():
return domain.FlowTypeCustomizeSAMLResponse
default: default:
return domain.FlowTypeUnspecified return domain.FlowTypeUnspecified
} }
@ -47,6 +49,8 @@ func TriggerTypeToDomain(triggerType string) domain.TriggerType {
return domain.TriggerTypePreAccessTokenCreation return domain.TriggerTypePreAccessTokenCreation
case domain.TriggerTypePreUserinfoCreation.ID(): case domain.TriggerTypePreUserinfoCreation.ID():
return domain.TriggerTypePreUserinfoCreation return domain.TriggerTypePreUserinfoCreation
case domain.TriggerTypePreSAMLResponseCreation.ID():
return domain.TriggerTypePreSAMLResponseCreation
default: default:
return domain.TriggerTypeUnspecified return domain.TriggerTypeUnspecified
} }

View File

@ -18,6 +18,7 @@ func (s *Server) ListFlowTypes(ctx context.Context, _ *mgmt_pb.ListFlowTypesRequ
action_grpc.FlowTypeToPb(domain.FlowTypeExternalAuthentication), action_grpc.FlowTypeToPb(domain.FlowTypeExternalAuthentication),
action_grpc.FlowTypeToPb(domain.FlowTypeCustomiseToken), action_grpc.FlowTypeToPb(domain.FlowTypeCustomiseToken),
action_grpc.FlowTypeToPb(domain.FlowTypeInternalAuthentication), action_grpc.FlowTypeToPb(domain.FlowTypeInternalAuthentication),
action_grpc.FlowTypeToPb(domain.FlowTypeCustomizeSAMLResponse),
}, },
}, nil }, nil
} }

View File

@ -386,7 +386,7 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo *oidc.UserInfo, us
} }
o.setUserInfoRoleClaims(userInfo, projectRoles) o.setUserInfoRoleClaims(userInfo, projectRoles)
return o.userinfoFlows(ctx, user.ResourceOwner, userGrants, userInfo) return o.userinfoFlows(ctx, user, userGrants, userInfo)
} }
func (o *OPStorage) setUserInfoProfile(ctx context.Context, userInfo *oidc.UserInfo, user *query.User) { func (o *OPStorage) setUserInfoProfile(ctx context.Context, userInfo *oidc.UserInfo, user *query.User) {
@ -457,8 +457,8 @@ func (o *OPStorage) setUserInfoRoleClaims(userInfo *oidc.UserInfo, roles *projec
} }
} }
func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, userGrants *query.UserGrants, userInfo *oidc.UserInfo) error { func (o *OPStorage) userinfoFlows(ctx context.Context, user *query.User, userGrants *query.UserGrants, userInfo *oidc.UserInfo) error {
queriedActions, err := o.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreUserinfoCreation, resourceOwner, false) queriedActions, err := o.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreUserinfoCreation, user.ResourceOwner, false)
if err != nil { if err != nil {
return err return err
} }
@ -468,17 +468,13 @@ func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, use
actions.SetFields("claims", userinfoClaims(userInfo)), actions.SetFields("claims", userinfoClaims(userInfo)),
actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} { actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} {
return func(call goja.FunctionCall) goja.Value { return func(call goja.FunctionCall) goja.Value {
user, err := o.query.GetUserByID(ctx, true, userInfo.Subject, false)
if err != nil {
panic(err)
}
return object.UserFromQuery(c, user) return object.UserFromQuery(c, user)
} }
}), }),
actions.SetFields("user", actions.SetFields("user",
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} { actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
return func(goja.FunctionCall) goja.Value { return func(goja.FunctionCall) goja.Value {
resourceOwnerQuery, err := query.NewUserMetadataResourceOwnerSearchQuery(resourceOwner) resourceOwnerQuery, err := query.NewUserMetadataResourceOwnerSearchQuery(user.ResourceOwner)
if err != nil { if err != nil {
logging.WithError(err).Debug("unable to create search query") logging.WithError(err).Debug("unable to create search query")
panic(err) panic(err)
@ -552,7 +548,7 @@ func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, use
Key: key, Key: key,
Value: value, Value: value,
} }
if _, err = o.command.SetUserMetadata(ctx, metadata, userInfo.Subject, resourceOwner); err != nil { if _, err = o.command.SetUserMetadata(ctx, metadata, userInfo.Subject, user.ResourceOwner); err != nil {
logging.WithError(err).Info("unable to set md in action") logging.WithError(err).Info("unable to set md in action")
panic(err) panic(err)
} }
@ -665,10 +661,6 @@ func (o *OPStorage) privateClaimsFlows(ctx context.Context, userID string, userG
}), }),
actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} { actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} {
return func(call goja.FunctionCall) goja.Value { return func(call goja.FunctionCall) goja.Value {
user, err := o.query.GetUserByID(ctx, true, userID, false)
if err != nil {
panic(err)
}
return object.UserFromQuery(c, user) return object.UserFromQuery(c, user)
} }
}), }),
@ -807,7 +799,7 @@ func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID strin
} }
return grants, roles, nil return grants, roles, nil
} }
// now specific roles were requested, so convert any grants into roles // no specific roles were requested, so convert any grants into roles
for _, grant := range grants.UserGrants { for _, grant := range grants.UserGrants {
for _, role := range grant.Roles { for _, role := range grant.Roles {
roles.Add(grant.ProjectID, role, grant.ResourceOwner, grant.OrgPrimaryDomain, grant.ProjectID == projectID) roles.Add(grant.ProjectID, role, grant.ResourceOwner, grant.OrgPrimaryDomain, grant.ProjectID == projectID)

View File

@ -2,14 +2,19 @@ package saml
import ( import (
"context" "context"
"encoding/json"
"time" "time"
"github.com/dop251/goja"
"github.com/zitadel/logging"
"github.com/zitadel/saml/pkg/provider" "github.com/zitadel/saml/pkg/provider"
"github.com/zitadel/saml/pkg/provider/key" "github.com/zitadel/saml/pkg/provider/key"
"github.com/zitadel/saml/pkg/provider/models" "github.com/zitadel/saml/pkg/provider/models"
"github.com/zitadel/saml/pkg/provider/serviceprovider" "github.com/zitadel/saml/pkg/provider/serviceprovider"
"github.com/zitadel/saml/pkg/provider/xml/samlp" "github.com/zitadel/saml/pkg/provider/xml/samlp"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/actions/object"
"github.com/zitadel/zitadel/internal/api/http/middleware" "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/auth/repository" "github.com/zitadel/zitadel/internal/auth/repository"
"github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/command"
@ -124,7 +129,7 @@ func (p *Storage) AuthRequestByID(ctx context.Context, id string) (_ models.Auth
return AuthRequestFromBusiness(resp) return AuthRequestFromBusiness(resp)
} }
func (p *Storage) SetUserinfoWithUserID(ctx context.Context, userinfo models.AttributeSetter, userID string, attributes []int) (err error) { func (p *Storage) SetUserinfoWithUserID(ctx context.Context, applicationID string, userinfo models.AttributeSetter, userID string, attributes []int) (err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
user, err := p.query.GetUserByID(ctx, true, userID, false) user, err := p.query.GetUserByID(ctx, true, userID, false)
@ -132,7 +137,17 @@ func (p *Storage) SetUserinfoWithUserID(ctx context.Context, userinfo models.Att
return err return err
} }
setUserinfo(user, userinfo, attributes) userGrants, err := p.getGrants(ctx, userID, applicationID)
if err != nil {
return err
}
customAttributes, err := p.getCustomAttributes(ctx, user, userGrants)
if err != nil {
return err
}
setUserinfo(user, userinfo, attributes, customAttributes)
return nil return nil
} }
@ -149,11 +164,14 @@ func (p *Storage) SetUserinfoWithLoginName(ctx context.Context, userinfo models.
return err return err
} }
setUserinfo(user, userinfo, attributes) setUserinfo(user, userinfo, attributes, map[string]*customAttribute{})
return nil return nil
} }
func setUserinfo(user *query.User, userinfo models.AttributeSetter, attributes []int) { func setUserinfo(user *query.User, userinfo models.AttributeSetter, attributes []int, customAttributes map[string]*customAttribute) {
for name, attr := range customAttributes {
userinfo.SetCustomAttribute(name, "", attr.nameFormat, attr.attributeValue)
}
if len(attributes) == 0 { if len(attributes) == 0 {
userinfo.SetUsername(user.PreferredLoginName) userinfo.SetUsername(user.PreferredLoginName)
userinfo.SetUserID(user.ID) userinfo.SetUserID(user.ID)
@ -191,3 +209,139 @@ func setUserinfo(user *query.User, userinfo models.AttributeSetter, attributes [
} }
} }
} }
func (p *Storage) getCustomAttributes(ctx context.Context, user *query.User, userGrants *query.UserGrants) (map[string]*customAttribute, error) {
customAttributes := make(map[string]*customAttribute, 0)
queriedActions, err := p.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomizeSAMLResponse, domain.TriggerTypePreSAMLResponseCreation, user.ResourceOwner, false)
if err != nil {
return nil, err
}
ctxFields := actions.SetContextFields(
actions.SetFields("v1",
actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} {
return func(call goja.FunctionCall) goja.Value {
return object.UserFromQuery(c, user)
}
}),
actions.SetFields("user",
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
return func(goja.FunctionCall) goja.Value {
resourceOwnerQuery, err := query.NewUserMetadataResourceOwnerSearchQuery(user.ResourceOwner)
if err != nil {
logging.WithError(err).Debug("unable to create search query")
panic(err)
}
metadata, err := p.query.SearchUserMetadata(
ctx,
true,
user.ID,
&query.UserMetadataSearchQueries{Queries: []query.SearchQuery{resourceOwnerQuery}},
false,
)
if err != nil {
logging.WithError(err).Info("unable to get md in action")
panic(err)
}
return object.UserMetadataListFromQuery(c, metadata)
}
}),
actions.SetFields("grants", func(c *actions.FieldConfig) interface{} {
return object.UserGrantsFromQuery(c, userGrants)
}),
),
),
)
for _, action := range queriedActions {
actionCtx, cancel := context.WithTimeout(ctx, action.Timeout())
apiFields := actions.WithAPIFields(
actions.SetFields("v1",
actions.SetFields("attributes",
actions.SetFields("setCustomAttribute", func(name string, nameFormat string, attributeValue ...string) {
if _, ok := customAttributes[name]; !ok {
customAttributes = appendCustomAttribute(customAttributes, name, nameFormat, attributeValue)
return
}
}),
),
actions.SetFields("user",
actions.SetFields("setMetadata", func(call goja.FunctionCall) {
if len(call.Arguments) != 2 {
panic("exactly 2 (key, value) arguments expected")
}
key := call.Arguments[0].Export().(string)
val := call.Arguments[1].Export()
value, err := json.Marshal(val)
if err != nil {
logging.WithError(err).Debug("unable to marshal")
panic(err)
}
metadata := &domain.Metadata{
Key: key,
Value: value,
}
if _, err = p.command.SetUserMetadata(ctx, metadata, user.ID, user.ResourceOwner); err != nil {
logging.WithError(err).Info("unable to set md in action")
panic(err)
}
}),
),
),
)
err = actions.Run(
actionCtx,
ctxFields,
apiFields,
action.Script,
action.Name,
append(actions.ActionToOptions(action), actions.WithHTTP(actionCtx))...,
)
cancel()
if err != nil {
return nil, err
}
}
return customAttributes, nil
}
func (p *Storage) getGrants(ctx context.Context, userID, applicationID string) (*query.UserGrants, error) {
projectID, err := p.query.ProjectIDFromClientID(ctx, applicationID, false)
if err != nil {
return nil, err
}
projectQuery, err := query.NewUserGrantProjectIDSearchQuery(projectID)
if err != nil {
return nil, err
}
userIDQuery, err := query.NewUserGrantUserIDSearchQuery(userID)
if err != nil {
return nil, err
}
return p.query.UserGrants(ctx, &query.UserGrantsQueries{
Queries: []query.SearchQuery{
projectQuery,
userIDQuery,
},
}, true, false)
}
type customAttribute struct {
nameFormat string
attributeValue []string
}
func appendCustomAttribute(customAttributes map[string]*customAttribute, name string, nameFormat string, attributeValue []string) map[string]*customAttribute {
if customAttributes == nil {
customAttributes = make(map[string]*customAttribute)
}
customAttributes[name] = &customAttribute{
nameFormat: nameFormat,
attributeValue: attributeValue,
}
return customAttributes
}

View File

@ -21,6 +21,7 @@ const (
FlowTypeExternalAuthentication FlowTypeExternalAuthentication
FlowTypeCustomiseToken FlowTypeCustomiseToken
FlowTypeInternalAuthentication FlowTypeInternalAuthentication
FlowTypeCustomizeSAMLResponse
flowTypeCount flowTypeCount
) )
@ -56,6 +57,10 @@ func (s FlowType) TriggerTypes() []TriggerType {
TriggerTypePreCreation, TriggerTypePreCreation,
TriggerTypePostCreation, TriggerTypePostCreation,
} }
case FlowTypeCustomizeSAMLResponse:
return []TriggerType{
TriggerTypePreSAMLResponseCreation,
}
default: default:
return nil return nil
} }
@ -80,6 +85,8 @@ func (s FlowType) LocalizationKey() string {
return "Action.Flow.Type.CustomiseToken" return "Action.Flow.Type.CustomiseToken"
case FlowTypeInternalAuthentication: case FlowTypeInternalAuthentication:
return "Action.Flow.Type.InternalAuthentication" return "Action.Flow.Type.InternalAuthentication"
case FlowTypeCustomizeSAMLResponse:
return "Action.Flow.Type.CustomizeSAMLResponse"
default: default:
return "Action.Flow.Type.Unspecified" return "Action.Flow.Type.Unspecified"
} }
@ -94,6 +101,7 @@ const (
TriggerTypePostCreation TriggerTypePostCreation
TriggerTypePreUserinfoCreation TriggerTypePreUserinfoCreation
TriggerTypePreAccessTokenCreation TriggerTypePreAccessTokenCreation
TriggerTypePreSAMLResponseCreation
triggerTypeCount triggerTypeCount
) )
@ -124,6 +132,8 @@ func (s TriggerType) LocalizationKey() string {
return "Action.TriggerType.PreUserinfoCreation" return "Action.TriggerType.PreUserinfoCreation"
case TriggerTypePreAccessTokenCreation: case TriggerTypePreAccessTokenCreation:
return "Action.TriggerType.PreAccessTokenCreation" return "Action.TriggerType.PreAccessTokenCreation"
case TriggerTypePreSAMLResponseCreation:
return "Action.TriggerType.PreSAMLResponseCreation"
default: default:
return "Action.TriggerType.Unspecified" return "Action.TriggerType.Unspecified"
} }

View File

@ -1266,6 +1266,7 @@ Action:
ExternalAuthentication: Външно удостоверяване ExternalAuthentication: Външно удостоверяване
CustomiseToken: Токен за допълнение CustomiseToken: Токен за допълнение
InternalAuthentication: Вътрешно удостоверяване InternalAuthentication: Вътрешно удостоверяване
CustomizeSAMLResponse: Допълнение на SAMLResponse
TriggerType: TriggerType:
Unspecified: Неуточнено Unspecified: Неуточнено
PostAuthentication: Публикуване на автентификация PostAuthentication: Публикуване на автентификация
@ -1273,3 +1274,4 @@ Action:
PostCreation: Създаване на публикации PostCreation: Създаване на публикации
PreUserinfoCreation: Предварително създаване на потребителска информация PreUserinfoCreation: Предварително създаване на потребителска информация
PreAccessTokenCreation: Създаване на маркер за предварителен достъп PreAccessTokenCreation: Създаване на маркер за предварителен достъп
PreSAMLResponseCreation: Предварително създаване на SAMLResponse

View File

@ -1216,6 +1216,7 @@ Action:
ExternalAuthentication: Externe Authentifizierung ExternalAuthentication: Externe Authentifizierung
CustomiseToken: Token ergänzen CustomiseToken: Token ergänzen
InternalAuthentication: Interne Authentifizierung InternalAuthentication: Interne Authentifizierung
CustomizeSAMLResponse: SAMLResponse ergänzen
TriggerType: TriggerType:
Unspecified: Unspezifiziert Unspecified: Unspezifiziert
PostAuthentication: Nach Authentifizierung PostAuthentication: Nach Authentifizierung
@ -1223,3 +1224,4 @@ Action:
PostCreation: Nach Erstellung PostCreation: Nach Erstellung
PreUserinfoCreation: Vor Userinfo Erstellung PreUserinfoCreation: Vor Userinfo Erstellung
PreAccessTokenCreation: Vor Access Token Erstellung PreAccessTokenCreation: Vor Access Token Erstellung
PreSAMLResponseCreation: Vor SAMLResponse Erstellung

View File

@ -1216,6 +1216,7 @@ Action:
ExternalAuthentication: External Authentication ExternalAuthentication: External Authentication
CustomiseToken: Complement Token CustomiseToken: Complement Token
InternalAuthentication: Internal Authentication InternalAuthentication: Internal Authentication
CustomizeSAMLResponse: Complement SAMLResponse
TriggerType: TriggerType:
Unspecified: Unspecified Unspecified: Unspecified
PostAuthentication: Post Authentication PostAuthentication: Post Authentication
@ -1223,3 +1224,4 @@ Action:
PostCreation: Post Creation PostCreation: Post Creation
PreUserinfoCreation: Pre Userinfo creation PreUserinfoCreation: Pre Userinfo creation
PreAccessTokenCreation: Pre access token creation PreAccessTokenCreation: Pre access token creation
PreSAMLResponseCreation: Pre SAMLResponse creation

View File

@ -1216,6 +1216,7 @@ Action:
ExternalAuthentication: Autenticación externa ExternalAuthentication: Autenticación externa
CustomiseToken: Token complementario CustomiseToken: Token complementario
InternalAuthentication: Autenticación interna InternalAuthentication: Autenticación interna
CustomizeSAMLResponse: SAMLResponse complementario
TriggerType: TriggerType:
Unspecified: No especificado Unspecified: No especificado
PostAuthentication: Post Autenticación PostAuthentication: Post Autenticación
@ -1223,3 +1224,4 @@ Action:
PostCreation: Post Creación PostCreation: Post Creación
PreUserinfoCreation: Pre creación de Userinfo PreUserinfoCreation: Pre creación de Userinfo
PreAccessTokenCreation: Pre creación de token de acceso PreAccessTokenCreation: Pre creación de token de acceso
PreSAMLResponseCreation: Creación previa de SAMLResponse

View File

@ -1045,6 +1045,7 @@ Action:
ExternalAuthentication: Authentification externe ExternalAuthentication: Authentification externe
CustomiseToken: Compléter Token CustomiseToken: Compléter Token
InternalAuthentication: Authentification interne InternalAuthentication: Authentification interne
CustomizeSAMLResponse: Compléter SAMLResponse
TriggerType: TriggerType:
Unspecified: Non spécifié Unspecified: Non spécifié
PostAuthentication: Authentification postérieure PostAuthentication: Authentification postérieure
@ -1052,3 +1053,4 @@ Action:
PostCreation: Post-création PostCreation: Post-création
PreUserinfoCreation: Pré Userinfo création PreUserinfoCreation: Pré Userinfo création
PreAccessTokenCreation: Pré access token création PreAccessTokenCreation: Pré access token création
PreSAMLResponseCreation: Création préalable de la réponse SAMLResponse

View File

@ -1045,6 +1045,7 @@ Action:
ExternalAuthentication: Autenticazione esterna ExternalAuthentication: Autenticazione esterna
CustomiseToken: Completare Token CustomiseToken: Completare Token
InternalAuthentication: Autenticazione interna InternalAuthentication: Autenticazione interna
CustomizeSAMLResponse: Completare SAMLResponse
TriggerType: TriggerType:
Unspecified: Non specificato Unspecified: Non specificato
PostAuthentication: Post-autenticazione PostAuthentication: Post-autenticazione
@ -1052,3 +1053,4 @@ Action:
PostCreation: Creazione successiva PostCreation: Creazione successiva
PreUserinfoCreation: Pre userinfo creazione PreUserinfoCreation: Pre userinfo creazione
PreAccessTokenCreation: Pre access token creazione PreAccessTokenCreation: Pre access token creazione
PreSAMLResponseCreation: Pre SAMLResponse creazione

View File

@ -1205,6 +1205,7 @@ Action:
ExternalAuthentication: 外部認証 ExternalAuthentication: 外部認証
CustomiseToken: トークンを補完 CustomiseToken: トークンを補完
InternalAuthentication: 内部認証 InternalAuthentication: 内部認証
CustomizeSAMLResponse: SAMLResponse の補完
TriggerType: TriggerType:
Unspecified: 未定義 Unspecified: 未定義
PostAuthentication: 認証後 PostAuthentication: 認証後
@ -1212,3 +1213,4 @@ Action:
PostCreation: 作成後 PostCreation: 作成後
PreUserinfoCreation: ユーザー情報作成前 PreUserinfoCreation: ユーザー情報作成前
PreAccessTokenCreation: アクセストークン作成前 PreAccessTokenCreation: アクセストークン作成前
PreSAMLResponseCreation: SAMLResponse の作成前

View File

@ -1215,6 +1215,7 @@ Action:
ExternalAuthentication: Надворешна автентикација ExternalAuthentication: Надворешна автентикација
CustomiseToken: Комплемент на токенот CustomiseToken: Комплемент на токенот
InternalAuthentication: Внатрешна автентикација InternalAuthentication: Внатрешна автентикација
CustomizeSAMLResponse: Дополнете го SAMLResponse
TriggerType: TriggerType:
Unspecified: Неодредено Unspecified: Неодредено
PostAuthentication: По автентикација PostAuthentication: По автентикација
@ -1222,3 +1223,4 @@ Action:
PostCreation: По креирање PostCreation: По креирање
PreUserinfoCreation: Пред креирање на кориснички информации PreUserinfoCreation: Пред креирање на кориснички информации
PreAccessTokenCreation: Пред креирање на токен за пристап PreAccessTokenCreation: Пред креирање на токен за пристап
PreSAMLResponseCreation: Пред создавање на SAMLResponse

View File

@ -1216,6 +1216,7 @@ Action:
ExternalAuthentication: Autentykacja zewnętrzna ExternalAuthentication: Autentykacja zewnętrzna
CustomiseToken: Uzupełnienie tokenu CustomiseToken: Uzupełnienie tokenu
InternalAuthentication: Autentykacja wewnętrzna InternalAuthentication: Autentykacja wewnętrzna
CustomizeSAMLResponse: Uzupełnienie SAMLResponse
TriggerType: TriggerType:
Unspecified: Nieokreślony Unspecified: Nieokreślony
PostAuthentication: Po autentykacji PostAuthentication: Po autentykacji
@ -1223,3 +1224,4 @@ Action:
PostCreation: Po utworzeniu PostCreation: Po utworzeniu
PreUserinfoCreation: Przed tworzeniem informacji o użytkowniku PreUserinfoCreation: Przed tworzeniem informacji o użytkowniku
PreAccessTokenCreation: Przed tworzeniem tokenu dostępu PreAccessTokenCreation: Przed tworzeniem tokenu dostępu
PreSAMLResponseCreation: Wstępne tworzenie odpowiedzi SAMLResponse

View File

@ -1211,6 +1211,7 @@ Action:
ExternalAuthentication: Autenticação externa ExternalAuthentication: Autenticação externa
CustomiseToken: Complementar Token CustomiseToken: Complementar Token
InternalAuthentication: Autenticação interna InternalAuthentication: Autenticação interna
CustomizeSAMLResponse: Complementar SAMLResponse
TriggerType: TriggerType:
Unspecified: Não especificado Unspecified: Não especificado
PostAuthentication: Pós-autenticação PostAuthentication: Pós-autenticação
@ -1218,3 +1219,4 @@ Action:
PostCreation: Póscriação PostCreation: Póscriação
PreUserinfoCreation: Pré-criação de informações do usuário PreUserinfoCreation: Pré-criação de informações do usuário
PreAccessTokenCreation: Pré-criação de access token PreAccessTokenCreation: Pré-criação de access token
PreSAMLResponseCreation: Pré-criação de SAMLResponse

View File

@ -1045,6 +1045,7 @@ Action:
ExternalAuthentication: 外部认证 ExternalAuthentication: 外部认证
CustomiseToken: 自定义令牌 CustomiseToken: 自定义令牌
InternalAuthentication: 内部认证 InternalAuthentication: 内部认证
CustomizeSAMLResponse: 补充 SAMLResponse
TriggerType: TriggerType:
Unspecified: 未指定的 Unspecified: 未指定的
PostAuthentication: 后期认证 PostAuthentication: 后期认证
@ -1052,3 +1053,4 @@ Action:
PostCreation: 创建后 PostCreation: 创建后
PreUserinfoCreation: 用户信息创建前 PreUserinfoCreation: 用户信息创建前
PreAccessTokenCreation: access 令牌创建前 PreAccessTokenCreation: access 令牌创建前
PreSAMLResponseCreation: 创建 SAMLResponse 前