feat(actions): add fields to complement token flow (#5336)

* deprecated `ctx.v1.userinfo`-field in "pre userinfo creation" trigger in favour of `ctx.v1.claims`. The trigger now behaves the same as "pre access token creation"
* added `ctx.v1.claims` to "complement tokens" flow
* added `ctx.v1.grants` to "complement tokens" flow
* document `ctx.v1.getUser()` in "complement tokens" flow

* feat(actions): add getUser() and grant

* map user grants

* map claims

* feat(actions): claims in complement token ctx

* docs(actions): add new fields of complement token

* docs(actions): additions to complement token

* docs(actions): correct field names
This commit is contained in:
Silvan
2023-03-08 15:26:28 +01:00
committed by GitHub
parent 3042d7ef5c
commit 20e4f1ce57
9 changed files with 216 additions and 46 deletions

View File

@@ -1,10 +1,13 @@
package object
import (
"time"
"github.com/dop251/goja"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
)
type UserGrants struct {
@@ -17,6 +20,32 @@ type UserGrant struct {
Roles []string
}
type userGrantList struct {
Count uint64
Sequence uint64
Timestamp time.Time
Grants []*userGrant
}
type userGrant struct {
Id string
ProjectGrantId string
State domain.UserGrantState
UserGrantResourceOwner string
UserGrantResourceOwnerName string
CreationDate time.Time
ChangeDate time.Time
Sequence uint64
UserId string
UserResourceOwner string
Roles []string
ProjectId string
ProjectName string
}
func AppendGrantFunc(userGrants *UserGrants) func(c *actions.FieldConfig) func(call goja.FunctionCall) goja.Value {
return func(c *actions.FieldConfig) func(call goja.FunctionCall) goja.Value {
return func(call goja.FunctionCall) goja.Value {
@@ -29,6 +58,51 @@ func AppendGrantFunc(userGrants *UserGrants) func(c *actions.FieldConfig) func(c
}
}
func UserGrantsFromQuery(c *actions.FieldConfig, userGrants *query.UserGrants) goja.Value {
grantList := &userGrantList{
Count: userGrants.Count,
Sequence: userGrants.Sequence,
Timestamp: userGrants.Timestamp,
Grants: make([]*userGrant, len(userGrants.UserGrants)),
}
for i, grant := range userGrants.UserGrants {
grantList.Grants[i] = &userGrant{
Id: grant.ID,
ProjectGrantId: grant.GrantID,
State: grant.State,
CreationDate: grant.CreationDate,
ChangeDate: grant.ChangeDate,
Sequence: grant.Sequence,
UserId: grant.UserID,
Roles: grant.Roles,
UserResourceOwner: grant.UserResourceOwner,
UserGrantResourceOwner: grant.ResourceOwner,
UserGrantResourceOwnerName: grant.OrgName,
ProjectId: grant.ProjectID,
ProjectName: grant.ProjectName,
}
}
return c.Runtime.ToValue(grantList)
}
func UserGrantsToDomain(userID string, actionUserGrants []UserGrant) []*domain.UserGrant {
if actionUserGrants == nil {
return nil
}
userGrants := make([]*domain.UserGrant, len(actionUserGrants))
for i, grant := range actionUserGrants {
userGrants[i] = &domain.UserGrant{
UserID: userID,
ProjectID: grant.ProjectID,
ProjectGrantID: grant.ProjectGrantID,
RoleKeys: grant.Roles,
}
}
return userGrants
}
func mapObjectToGrant(object *goja.Object, grant *UserGrant) {
for _, key := range object.Keys() {
switch key {
@@ -50,19 +124,3 @@ func mapObjectToGrant(object *goja.Object, grant *UserGrant) {
panic("projectId not set")
}
}
func UserGrantsToDomain(userID string, actionUserGrants []UserGrant) []*domain.UserGrant {
if actionUserGrants == nil {
return nil
}
userGrants := make([]*domain.UserGrant, len(actionUserGrants))
for i, grant := range actionUserGrants {
userGrants[i] = &domain.UserGrant{
UserID: userID,
ProjectID: grant.ProjectID,
ProjectGrantID: grant.ProjectGrantID,
RoleKeys: grant.Roles,
}
}
return userGrants
}

View File

@@ -327,21 +327,19 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
}
}
if len(roles) == 0 || applicationID == "" {
return o.userinfoFlows(ctx, user.ResourceOwner, userInfo)
}
projectRoles, err := o.assertRoles(ctx, userID, applicationID, roles)
userGrants, projectRoles, err := o.assertRoles(ctx, userID, applicationID, roles)
if err != nil {
return err
}
if len(projectRoles) > 0 {
userInfo.AppendClaims(ClaimProjectRoles, projectRoles)
}
return o.userinfoFlows(ctx, user.ResourceOwner, userInfo)
return o.userinfoFlows(ctx, user.ResourceOwner, userGrants, userInfo)
}
func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, userInfo oidc.UserInfoSetter) error {
func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, userGrants *query.UserGrants, userInfo oidc.UserInfoSetter) error {
queriedActions, err := o.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreUserinfoCreation, resourceOwner, false)
if err != nil {
return err
@@ -349,6 +347,16 @@ func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, use
ctxFields := actions.SetContextFields(
actions.SetFields("v1",
actions.SetFields("claims", userinfoClaims(userInfo)),
actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} {
return func(call goja.FunctionCall) goja.Value {
user, err := o.query.GetUserByID(ctx, true, userInfo.GetSubject(), false)
if err != nil {
panic(err)
}
return object.UserFromQuery(c, user)
}
}),
actions.SetFields("user",
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
return func(goja.FunctionCall) goja.Value {
@@ -371,6 +379,9 @@ func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, use
return object.UserMetadataListFromQuery(c, metadata)
}
}),
actions.SetFields("grants", func(c *actions.FieldConfig) interface{} {
return object.UserGrantsFromQuery(c, userGrants)
}),
),
),
)
@@ -393,6 +404,18 @@ func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, use
claimLogs = append(claimLogs, entry)
}),
),
actions.SetFields("claims",
actions.SetFields("setClaim", func(key string, value interface{}) {
if userInfo.GetClaim(key) == nil {
userInfo.AppendClaims(key, value)
return
}
claimLogs = append(claimLogs, fmt.Sprintf("key %q already exists", key))
}),
actions.SetFields("appendLogIntoClaims", func(entry string) {
claimLogs = append(claimLogs, entry)
}),
),
actions.SetFields("user",
actions.SetFields("setMetadata", func(call goja.FunctionCall) goja.Value {
if len(call.Arguments) != 2 {
@@ -480,21 +503,19 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
}
}
if len(roles) == 0 || clientID == "" {
return o.privateClaimsFlows(ctx, userID, claims)
}
projectRoles, err := o.assertRoles(ctx, userID, clientID, roles)
userGrants, projectRoles, err := o.assertRoles(ctx, userID, clientID, roles)
if err != nil {
return nil, err
}
if len(projectRoles) > 0 {
claims = appendClaim(claims, ClaimProjectRoles, projectRoles)
}
return o.privateClaimsFlows(ctx, userID, claims)
return o.privateClaimsFlows(ctx, userID, userGrants, claims)
}
func (o *OPStorage) privateClaimsFlows(ctx context.Context, userID string, claims map[string]interface{}) (map[string]interface{}, error) {
func (o *OPStorage) privateClaimsFlows(ctx context.Context, userID string, userGrants *query.UserGrants, claims map[string]interface{}) (map[string]interface{}, error) {
user, err := o.query.GetUserByID(ctx, true, userID, false)
if err != nil {
return nil, err
@@ -506,6 +527,18 @@ func (o *OPStorage) privateClaimsFlows(ctx context.Context, userID string, claim
ctxFields := actions.SetContextFields(
actions.SetFields("v1",
actions.SetFields("claims", func(c *actions.FieldConfig) interface{} {
return c.Runtime.ToValue(claims)
}),
actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} {
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)
}
}),
actions.SetFields("user",
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
return func(goja.FunctionCall) goja.Value {
@@ -528,6 +561,9 @@ func (o *OPStorage) privateClaimsFlows(ctx context.Context, userID string, claim
return object.UserMetadataListFromQuery(c, metadata)
}
}),
actions.SetFields("grants", func(c *actions.FieldConfig) interface{} {
return object.UserGrantsFromQuery(c, userGrants)
}),
),
),
)
@@ -598,24 +634,24 @@ func (o *OPStorage) privateClaimsFlows(ctx context.Context, userID string, claim
return claims, nil
}
func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID string, requestedRoles []string) (map[string]map[string]string, error) {
func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID string, requestedRoles []string) (*query.UserGrants, map[string]map[string]string, error) {
projectID, err := o.query.ProjectIDFromClientID(ctx, applicationID, false)
if err != nil {
return nil, err
return nil, nil, err
}
projectQuery, err := query.NewUserGrantProjectIDSearchQuery(projectID)
if err != nil {
return nil, err
return nil, nil, err
}
userIDQuery, err := query.NewUserGrantUserIDSearchQuery(userID)
if err != nil {
return nil, err
return nil, nil, err
}
grants, err := o.query.UserGrants(ctx, &query.UserGrantsQueries{
Queries: []query.SearchQuery{projectQuery, userIDQuery},
}, false)
if err != nil {
return nil, err
return nil, nil, err
}
projectRoles := make(map[string]map[string]string)
for _, requestedRole := range requestedRoles {
@@ -623,7 +659,7 @@ func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID strin
checkGrantedRoles(projectRoles, grant, requestedRole)
}
}
return projectRoles, nil
return grants, projectRoles, nil
}
func (o *OPStorage) assertUserMetaData(ctx context.Context, userID string) (map[string]string, error) {
@@ -689,3 +725,18 @@ func appendClaim(claims map[string]interface{}, claim string, value interface{})
claims[claim] = value
return claims
}
func userinfoClaims(userInfo oidc.UserInfoSetter) func(c *actions.FieldConfig) interface{} {
return func(c *actions.FieldConfig) interface{} {
marshalled, err := json.Marshal(userInfo)
if err != nil {
panic(err)
}
claims := make(map[string]interface{}, 10)
if err = json.Unmarshal(marshalled, &claims); err != nil {
panic(err)
}
return c.Runtime.ToValue(claims)
}
}

View File

@@ -18,13 +18,15 @@ import (
)
type UserGrant struct {
// ID represents the aggregate id (id of the user grant)
ID string
CreationDate time.Time
ChangeDate time.Time
Sequence uint64
Roles database.StringArray
GrantID string
State domain.UserGrantState
// GrantID represents the project grant id
GrantID string
State domain.UserGrantState
UserID string
Username string