feat(actions): add token customization flow and extend functionally with modules (#4337)

* fix: potential memory leak

* feat(actions): possibility to parse json
feat(actions): possibility to perform http calls

* add query call

* feat(api): list flow and trigger types
fix(api): switch flow and trigger types to dynamic objects

* fix(translations): add action translations

* use `domain.FlowType`

* localizers

* localization

* trigger types

* options on `query.Action`

* add functions for actions

* feat: management api: add list flow and trigger  (#4352)

* console changes

* cleanup

* fix: wrong localization

Co-authored-by: Max Peintner <max@caos.ch>

* id token works

* check if claims not nil

* feat(actions): metadata api

* refactor(actions): modules

* fix: allow prerelease

* fix: test

* feat(actions): deny list for http hosts

* feat(actions): deny list for http hosts

* refactor: actions

* fix: different error ids

* fix: rename statusCode to status

* Actions objects as options (#4418)

* fix: rename statusCode to status

* fix(actions): objects as options

* fix(actions): objects as options

* fix(actions): set fields

* add http client to old actions

* fix(actions): add log module

* fix(actions): add user to context where possible

* fix(actions): add user to ctx in external authorization/pre creation

* fix(actions): query correct flow in claims

* test: actions

* fix(id-generator): panic if no machine id

* tests

* maybe this?

* fix linting

* refactor: improve code

* fix: metadata and usergrant usage in actions

* fix: appendUserGrant

* fix: allowedToFail and timeout in action execution

* fix: allowed to fail in token complement flow

* docs: add action log claim

* Update defaults.yaml

* fix log claim

* remove prerelease build

Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Silvan
2022-10-06 14:23:59 +02:00
committed by GitHub
parent bffb10a4b4
commit 43fb3fd1a6
62 changed files with 2806 additions and 636 deletions

View File

@@ -7,30 +7,66 @@ import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
action_pb "github.com/zitadel/zitadel/pkg/grpc/action"
message_pb "github.com/zitadel/zitadel/pkg/grpc/message"
)
func FlowTypeToDomain(flowType action_pb.FlowType) domain.FlowType {
// for backward compatability: old enum identifiers are mapped as well
func FlowTypeToDomain(flowType string) domain.FlowType {
switch flowType {
case action_pb.FlowType_FLOW_TYPE_EXTERNAL_AUTHENTICATION:
case "FLOW_TYPE_EXTERNAL_AUTHENTICATION", domain.FlowTypeExternalAuthentication.ID():
return domain.FlowTypeExternalAuthentication
case domain.FlowTypeCustomiseToken.ID():
return domain.FlowTypeCustomiseToken
default:
return domain.FlowTypeUnspecified
}
}
func TriggerTypeToDomain(triggerType action_pb.TriggerType) domain.TriggerType {
func FlowTypeToPb(typ domain.FlowType) *action_pb.FlowType {
return &action_pb.FlowType{
Id: typ.ID(),
Name: &message_pb.LocalizedMessage{
Key: typ.LocalizationKey(),
},
}
}
// TriggerTypeToDomain maps the pb type to domain
// for backward compatability: old enum identifiers are mapped as well
func TriggerTypeToDomain(triggerType string) domain.TriggerType {
switch triggerType {
case action_pb.TriggerType_TRIGGER_TYPE_POST_AUTHENTICATION:
case "TRIGGER_TYPE_POST_AUTHENTICATION", domain.TriggerTypePostAuthentication.ID():
return domain.TriggerTypePostAuthentication
case action_pb.TriggerType_TRIGGER_TYPE_PRE_CREATION:
case "TRIGGER_TYPE_PRE_CREATION", domain.TriggerTypePreCreation.ID():
return domain.TriggerTypePreCreation
case action_pb.TriggerType_TRIGGER_TYPE_POST_CREATION:
case "TRIGGER_TYPE_POST_CREATION", domain.TriggerTypePostCreation.ID():
return domain.TriggerTypePostCreation
case domain.TriggerTypePreAccessTokenCreation.ID():
return domain.TriggerTypePreAccessTokenCreation
case domain.TriggerTypePreUserinfoCreation.ID():
return domain.TriggerTypePreUserinfoCreation
default:
return domain.TriggerTypeUnspecified
}
}
func TriggerTypesToPb(types []domain.TriggerType) []*action_pb.TriggerType {
list := make([]*action_pb.TriggerType, len(types))
for i, typ := range types {
list[i] = TriggerTypeToPb(typ)
}
return list
}
func TriggerTypeToPb(typ domain.TriggerType) *action_pb.TriggerType {
return &action_pb.TriggerType{
Id: typ.ID(),
Name: &message_pb.LocalizedMessage{
Key: typ.LocalizationKey(),
},
}
}
func FlowToPb(flow *query.Flow) *action_pb.Flow {
return &action_pb.Flow{
Type: FlowTypeToPb(flow.Type),
@@ -47,28 +83,6 @@ func TriggerActionToPb(trigger domain.TriggerType, actions []*query.Action) *act
}
}
func FlowTypeToPb(flowType domain.FlowType) action_pb.FlowType {
switch flowType {
case domain.FlowTypeExternalAuthentication:
return action_pb.FlowType_FLOW_TYPE_EXTERNAL_AUTHENTICATION
default:
return action_pb.FlowType_FLOW_TYPE_UNSPECIFIED
}
}
func TriggerTypeToPb(triggerType domain.TriggerType) action_pb.TriggerType {
switch triggerType {
case domain.TriggerTypePostAuthentication:
return action_pb.TriggerType_TRIGGER_TYPE_POST_AUTHENTICATION
case domain.TriggerTypePreCreation:
return action_pb.TriggerType_TRIGGER_TYPE_PRE_CREATION
case domain.TriggerTypePostCreation:
return action_pb.TriggerType_TRIGGER_TYPE_POST_CREATION
default:
return action_pb.TriggerType_TRIGGER_TYPE_UNSPECIFIED
}
}
func TriggerActionsToPb(triggers map[domain.TriggerType][]*query.Action) []*action_pb.TriggerAction {
list := make([]*action_pb.TriggerAction, 0)
for trigger, actions := range triggers {
@@ -92,7 +106,7 @@ func ActionToPb(action *query.Action) *action_pb.Action {
State: ActionStateToPb(action.State),
Name: action.Name,
Script: action.Script,
Timeout: durationpb.New(action.Timeout),
Timeout: durationpb.New(action.Timeout()),
AllowedToFail: action.AllowedToFail,
}
}

View File

@@ -2,12 +2,14 @@ package admin
import (
"context"
"google.golang.org/protobuf/types/known/durationpb"
text_grpc "github.com/zitadel/zitadel/internal/api/grpc/text"
"github.com/zitadel/zitadel/internal/domain"
caos_errors "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
action_pb "github.com/zitadel/zitadel/pkg/grpc/action"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
app_pb "github.com/zitadel/zitadel/pkg/grpc/app"
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp"
@@ -17,7 +19,6 @@ import (
project_pb "github.com/zitadel/zitadel/pkg/grpc/project"
user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
v1_pb "github.com/zitadel/zitadel/pkg/grpc/v1"
"google.golang.org/protobuf/types/known/durationpb"
)
func (s *Server) ExportData(ctx context.Context, req *admin_pb.ExportDataRequest) (_ *admin_pb.ExportDataResponse, err error) {
@@ -639,8 +640,8 @@ func (s *Server) getTriggerActions(ctx context.Context, org string, processedAct
}
triggerActions = append(triggerActions, &management_pb.SetTriggerActionsRequest{
FlowType: action_pb.FlowType(flowType),
TriggerType: action_pb.TriggerType(triggerType),
FlowType: flowType.ID(),
TriggerType: triggerType.ID(),
ActionIds: actions,
})
}
@@ -662,7 +663,7 @@ func (s *Server) getActions(ctx context.Context, org string) ([]*v1_pb.DataActio
return actions, nil
}
for i, action := range queriedActions.Actions {
timeout := durationpb.New(action.Timeout)
timeout := durationpb.New(action.Timeout())
actions[i] = &v1_pb.DataAction{
ActionId: action.ID,

View File

@@ -17,6 +17,7 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
action_grpc "github.com/zitadel/zitadel/internal/api/grpc/action"
"github.com/zitadel/zitadel/internal/api/grpc/management"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
@@ -693,9 +694,9 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm
if org.TriggerActions != nil {
for _, triggerAction := range org.GetTriggerActions() {
_, err := s.command.SetTriggerActions(ctx, domain.FlowType(triggerAction.FlowType), domain.TriggerType(triggerAction.TriggerType), triggerAction.ActionIds, org.GetOrgId())
_, err := s.command.SetTriggerActions(ctx, action_grpc.FlowTypeToDomain(triggerAction.FlowType), action_grpc.TriggerTypeToDomain(triggerAction.TriggerType), triggerAction.ActionIds, org.GetOrgId())
if err != nil {
errors = append(errors, &admin_pb.ImportDataError{Type: "trigger_action", Id: triggerAction.FlowType.String() + "_" + triggerAction.TriggerType.String(), Message: err.Error()})
errors = append(errors, &admin_pb.ImportDataError{Type: "trigger_action", Id: triggerAction.FlowType + "_" + triggerAction.TriggerType, Message: err.Error()})
continue
}
successOrg.TriggerActions = append(successOrg.TriggerActions, &management_pb.SetTriggerActionsRequest{FlowType: triggerAction.FlowType, TriggerType: triggerAction.TriggerType, ActionIds: triggerAction.GetActionIds()})

View File

@@ -6,9 +6,31 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
action_grpc "github.com/zitadel/zitadel/internal/api/grpc/action"
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
action_pb "github.com/zitadel/zitadel/pkg/grpc/action"
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management"
)
func (s *Server) ListFlowTypes(ctx context.Context, _ *mgmt_pb.ListFlowTypesRequest) (*mgmt_pb.ListFlowTypesResponse, error) {
return &mgmt_pb.ListFlowTypesResponse{
Result: []*action_pb.FlowType{
action_grpc.FlowTypeToPb(domain.FlowTypeExternalAuthentication),
action_grpc.FlowTypeToPb(domain.FlowTypeCustomiseToken),
},
}, nil
}
func (s *Server) ListFlowTriggerTypes(ctx context.Context, req *mgmt_pb.ListFlowTriggerTypesRequest) (*mgmt_pb.ListFlowTriggerTypesResponse, error) {
triggerTypes := action_grpc.FlowTypeToDomain(req.Type).TriggerTypes()
if len(triggerTypes) == 0 {
return nil, errors.ThrowNotFound(nil, "MANAG-P2OBk", "Errors.NotFound")
}
return &mgmt_pb.ListFlowTriggerTypesResponse{
Result: action_grpc.TriggerTypesToPb(triggerTypes),
}, nil
}
func (s *Server) GetFlow(ctx context.Context, req *mgmt_pb.GetFlowRequest) (*mgmt_pb.GetFlowResponse, error) {
flow, err := s.query.GetFlow(ctx, action_grpc.FlowTypeToDomain(req.Type), authz.GetCtxData(ctx).OrgID)
if err != nil {

View File

@@ -3,14 +3,20 @@ package oidc
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"github.com/dop251/goja"
"github.com/zitadel/logging"
"github.com/zitadel/oidc/v2/pkg/oidc"
"github.com/zitadel/oidc/v2/pkg/op"
"gopkg.in/square/go-jose.v2"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/actions/object"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/http"
api_http "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
@@ -25,6 +31,7 @@ const (
ClaimUserMetaData = ScopeUserMetaData
ScopeResourceOwner = "urn:zitadel:iam:user:resourceowner"
ClaimResourceOwner = ScopeResourceOwner + ":"
ClaimActionLogFormat = "urn:zitadel:iam:action:%s:log"
oidcCtx = "oidc"
)
@@ -141,7 +148,7 @@ func (o *OPStorage) SetUserinfoFromToken(ctx context.Context, userInfo oidc.User
if err != nil {
return err
}
if origin != "" && !http.IsOriginAllowed(app.OIDCConfig.AllowedOrigins, origin) {
if origin != "" && !api_http.IsOriginAllowed(app.OIDCConfig.AllowedOrigins, origin) {
return errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed")
}
}
@@ -276,8 +283,9 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
}
}
}
if len(roles) == 0 || applicationID == "" {
return nil
return o.userinfoFlows(ctx, user.ResourceOwner, userInfo)
}
projectRoles, err := o.assertRoles(ctx, userID, applicationID, roles)
if err != nil {
@@ -286,6 +294,106 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
if len(projectRoles) > 0 {
userInfo.AppendClaims(ClaimProjectRoles, projectRoles)
}
return o.userinfoFlows(ctx, user.ResourceOwner, userInfo)
}
func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, userInfo oidc.UserInfoSetter) error {
queriedActions, err := o.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreUserinfoCreation, resourceOwner)
if err != nil {
return err
}
ctxFields := actions.SetContextFields(
actions.SetFields("v1",
actions.SetFields("user",
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
return func(goja.FunctionCall) goja.Value {
resourceOwnerQuery, err := query.NewUserMetadataResourceOwnerSearchQuery(resourceOwner)
if err != nil {
logging.WithError(err).Debug("unable to create search query")
panic(err)
}
metadata, err := o.query.SearchUserMetadata(
ctx,
true,
userInfo.GetSubject(),
&query.UserMetadataSearchQueries{Queries: []query.SearchQuery{resourceOwnerQuery}},
)
if err != nil {
logging.WithError(err).Info("unable to get md in action")
panic(err)
}
return object.UserMetadataListFromQuery(c, metadata)
}
}),
),
),
)
for _, action := range queriedActions {
actionCtx, cancel := context.WithTimeout(ctx, action.Timeout())
claimLogs := []string{}
apiFields := actions.WithAPIFields(
actions.SetFields("v1",
actions.SetFields("userinfo",
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 {
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 = o.command.SetUserMetadata(ctx, metadata, userInfo.GetSubject(), resourceOwner); err != nil {
logging.WithError(err).Info("unable to set md in action")
panic(err)
}
return nil
}),
),
),
)
err = actions.Run(
actionCtx,
ctxFields,
apiFields,
action.Script,
action.Name,
append(actions.ActionToOptions(action), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
)
cancel()
if err != nil {
return err
}
if len(claimLogs) > 0 {
userInfo.AppendClaims(fmt.Sprintf(ClaimActionLogFormat, action.Name), claimLogs)
}
}
return nil
}
@@ -327,8 +435,9 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
}
}
}
if len(roles) == 0 || clientID == "" {
return claims, nil
return o.privateClaimsFlows(ctx, userID, claims)
}
projectRoles, err := o.assertRoles(ctx, userID, clientID, roles)
if err != nil {
@@ -337,7 +446,111 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
if len(projectRoles) > 0 {
claims = appendClaim(claims, ClaimProjectRoles, projectRoles)
}
return claims, err
return o.privateClaimsFlows(ctx, userID, claims)
}
func (o *OPStorage) privateClaimsFlows(ctx context.Context, userID string, claims map[string]interface{}) (map[string]interface{}, error) {
user, err := o.query.GetUserByID(ctx, true, userID)
if err != nil {
return nil, err
}
queriedActions, err := o.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreAccessTokenCreation, user.ResourceOwner)
if err != nil {
return nil, err
}
ctxFields := actions.SetContextFields(
actions.SetFields("v1",
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 := o.query.SearchUserMetadata(
ctx,
true,
userID,
&query.UserMetadataSearchQueries{Queries: []query.SearchQuery{resourceOwnerQuery}},
)
if err != nil {
logging.WithError(err).Info("unable to get md in action")
panic(err)
}
return object.UserMetadataListFromQuery(c, metadata)
}
}),
),
),
)
for _, action := range queriedActions {
claimLogs := []string{}
actionCtx, cancel := context.WithTimeout(ctx, action.Timeout())
apiFields := actions.WithAPIFields(
actions.SetFields("v1",
actions.SetFields("claims",
actions.SetFields("setClaim", func(key string, value interface{}) {
if _, ok := claims[key]; !ok {
claims[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) {
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 = o.command.SetUserMetadata(ctx, metadata, userID, 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), actions.WithLogger(actions.ServerLog))...,
)
cancel()
if err != nil {
return nil, err
}
if len(claimLogs) > 0 {
claims = appendClaim(claims, fmt.Sprintf(ClaimActionLogFormat, action.Name), claimLogs)
claimLogs = nil
}
}
return claims, nil
}
func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID string, requestedRoles []string) (map[string]map[string]string, error) {

View File

@@ -2,10 +2,15 @@ package login
import (
"context"
"encoding/json"
"github.com/dop251/goja"
"github.com/zitadel/logging"
"github.com/zitadel/oidc/v2/pkg/oidc"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/actions/object"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
iam_model "github.com/zitadel/zitadel/internal/iam/model"
@@ -24,10 +29,95 @@ func (l *Login) customExternalUserMapping(ctx context.Context, user *domain.Exte
if err != nil {
return nil, err
}
actionCtx := (&actions.Context{}).SetToken(tokens)
api := (&actions.API{}).SetExternalUser(user).SetMetadata(&user.Metadatas)
ctxFields := actions.SetContextFields(
actions.SetFields("accessToken", tokens.AccessToken),
actions.SetFields("idToken", tokens.IDToken),
actions.SetFields("getClaim", func(claim string) interface{} {
return tokens.IDTokenClaims.GetClaim(claim)
}),
actions.SetFields("claimsJSON", func() (string, error) {
c, err := json.Marshal(tokens.IDTokenClaims)
if err != nil {
return "", err
}
return string(c), nil
}),
actions.SetFields("v1",
actions.SetFields("externalUser", func(c *actions.FieldConfig) interface{} {
return object.UserFromExternalUser(c, user)
}),
),
)
apiFields := actions.WithAPIFields(
actions.SetFields("setFirstName", func(firstName string) {
user.FirstName = firstName
}),
actions.SetFields("setLastName", func(lastName string) {
user.LastName = lastName
}),
actions.SetFields("setNickName", func(nickName string) {
user.NickName = nickName
}),
actions.SetFields("setDisplayName", func(displayName string) {
user.DisplayName = displayName
}),
actions.SetFields("setPreferredLanguage", func(preferredLanguage string) {
user.PreferredLanguage = language.Make(preferredLanguage)
}),
actions.SetFields("setPreferredUsername", func(username string) {
user.PreferredUsername = username
}),
actions.SetFields("setEmail", func(email string) {
user.Email = email
}),
actions.SetFields("setEmailVerified", func(verified bool) {
user.IsEmailVerified = verified
}),
actions.SetFields("setPhone", func(phone string) {
user.Phone = phone
}),
actions.SetFields("setPhoneVerified", func(verified bool) {
user.IsPhoneVerified = verified
}),
actions.SetFields("metadata", &user.Metadatas),
actions.SetFields("v1",
actions.SetFields("user",
actions.SetFields("appendMetadata", func(call goja.FunctionCall) goja.Value {
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)
}
user.Metadatas = append(user.Metadatas,
&domain.Metadata{
Key: key,
Value: value,
})
return nil
}),
),
),
)
for _, a := range triggerActions {
err = actions.Run(actionCtx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail)
actionCtx, cancel := context.WithTimeout(ctx, a.Timeout())
err = actions.Run(
actionCtx,
ctxFields,
apiFields,
a.Script,
a.Name,
append(actions.ActionToOptions(a), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
)
cancel()
if err != nil {
return nil, err
}
@@ -40,10 +130,98 @@ func (l *Login) customExternalUserToLoginUserMapping(ctx context.Context, user *
if err != nil {
return nil, nil, err
}
actionCtx := (&actions.Context{}).SetToken(tokens)
api := (&actions.API{}).SetHuman(user).SetMetadata(&metadata)
ctxOpts := actions.SetContextFields(
actions.SetFields("v1",
actions.SetFields("user", func(c *actions.FieldConfig) interface{} {
return object.UserFromHuman(c, user)
}),
),
)
apiFields := actions.WithAPIFields(
actions.SetFields("setFirstName", func(firstName string) {
user.FirstName = firstName
}),
actions.SetFields("setLastName", func(lastName string) {
user.LastName = lastName
}),
actions.SetFields("setNickName", func(nickName string) {
user.NickName = nickName
}),
actions.SetFields("setDisplayName", func(displayName string) {
user.DisplayName = displayName
}),
actions.SetFields("setPreferredLanguage", func(preferredLanguage string) {
user.PreferredLanguage = language.Make(preferredLanguage)
}),
actions.SetFields("setGender", func(gender domain.Gender) {
user.Gender = gender
}),
actions.SetFields("setUsername", func(username string) {
user.Username = username
}),
actions.SetFields("setEmail", func(email string) {
if user.Email == nil {
user.Email = &domain.Email{}
}
user.Email.EmailAddress = email
}),
actions.SetFields("setEmailVerified", func(verified bool) {
if user.Email == nil {
return
}
user.Email.IsEmailVerified = verified
}),
actions.SetFields("setPhone", func(email string) {
if user.Phone == nil {
user.Phone = &domain.Phone{}
}
user.Phone.PhoneNumber = email
}),
actions.SetFields("setPhoneVerified", func(verified bool) {
if user.Phone == nil {
return
}
user.Phone.IsPhoneVerified = verified
}),
actions.SetFields("metadata", metadata),
actions.SetFields("v1",
actions.SetFields("user",
actions.SetFields("appendMetadata", func(call goja.FunctionCall) goja.Value {
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 = append(metadata,
&domain.Metadata{
Key: key,
Value: value,
})
return nil
}),
),
),
)
for _, a := range triggerActions {
err = actions.Run(actionCtx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail)
actionCtx, cancel := context.WithTimeout(ctx, a.Timeout())
err = actions.Run(
actionCtx,
ctxOpts,
apiFields,
a.Script,
a.Name,
append(actions.ActionToOptions(a), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
)
cancel()
if err != nil {
return nil, nil, err
}
@@ -56,11 +234,78 @@ func (l *Login) customGrants(ctx context.Context, userID string, tokens *oidc.To
if err != nil {
return nil, err
}
actionCtx := (&actions.Context{}).SetToken(tokens)
actionUserGrants := make([]actions.UserGrant, 0)
api := (&actions.API{}).SetUserGrants(&actionUserGrants)
apiFields := actions.WithAPIFields(
actions.SetFields("userGrants", &actionUserGrants),
actions.SetFields("v1",
actions.SetFields("appendUserGrant", func(c *actions.FieldConfig) interface{} {
return func(call goja.FunctionCall) goja.Value {
if len(call.Arguments) != 1 {
panic("exactly one argument expected")
}
object := call.Arguments[0].ToObject(c.Runtime)
if object == nil {
panic("unable to unmarshal arg")
}
grant := actions.UserGrant{}
for _, key := range object.Keys() {
switch key {
case "projectId":
grant.ProjectID = object.Get(key).String()
case "projectGrantId":
grant.ProjectGrantID = object.Get(key).String()
case "roles":
if roles, ok := object.Get(key).Export().([]interface{}); ok {
for _, role := range roles {
if r, ok := role.(string); ok {
grant.Roles = append(grant.Roles, r)
}
}
}
}
}
if grant.ProjectID == "" {
panic("projectId not set")
}
actionUserGrants = append(actionUserGrants, grant)
return nil
}
}),
),
)
for _, a := range triggerActions {
err = actions.Run(actionCtx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail)
actionCtx, cancel := context.WithTimeout(ctx, a.Timeout())
ctxFields := actions.SetContextFields(
actions.SetFields("v1",
actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} {
return func(call goja.FunctionCall) goja.Value {
user, err := l.query.GetUserByID(actionCtx, true, userID)
if err != nil {
panic(err)
}
return object.UserFromQuery(c, user)
}
}),
),
)
err = actions.Run(
actionCtx,
ctxFields,
apiFields,
a.Script,
a.Name,
append(actions.ActionToOptions(a), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
)
cancel()
if err != nil {
return nil, err
}

View File

@@ -17,7 +17,6 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
caos_errors "github.com/zitadel/zitadel/internal/errors"
iam_model "github.com/zitadel/zitadel/internal/iam/model"
"github.com/zitadel/zitadel/internal/query"
)
@@ -121,7 +120,7 @@ func (l *Login) handleJWTAuthorize(w http.ResponseWriter, r *http.Request, authR
q.Set(QueryAuthRequestID, authReq.ID)
userAgentID, ok := http_mw.UserAgentIDFromCtx(r.Context())
if !ok {
l.renderLogin(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "LOGIN-dsgg3", "Errors.AuthRequest.UserAgentNotFound"))
l.renderLogin(w, r, authReq, errors.ThrowPreconditionFailed(nil, "LOGIN-dsgg3", "Errors.AuthRequest.UserAgentNotFound"))
return
}
nonce, err := l.idpConfigAlg.Encrypt([]byte(userAgentID))
@@ -166,7 +165,7 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
l.handleExternalUserAuthenticated(w, r, authReq, idpConfig, userAgentID, tokens)
return
}
l.renderError(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "RP-asff2", "Errors.ExternalIDP.IDPTypeNotImplemented"))
l.renderError(w, r, authReq, errors.ThrowPreconditionFailed(nil, "RP-asff2", "Errors.ExternalIDP.IDPTypeNotImplemented"))
}
func (l *Login) getRPConfig(ctx context.Context, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) (rp.RelyingParty, error) {
@@ -178,7 +177,7 @@ func (l *Login) getRPConfig(ctx context.Context, idpConfig *iam_model.IDPConfigV
return rp.NewRelyingPartyOIDC(idpConfig.OIDCIssuer, idpConfig.OIDCClientID, oidcClientSecret, l.baseURL(ctx)+callbackEndpoint, idpConfig.OIDCScopes, rp.WithVerifierOpts(rp.WithIssuedAtOffset(3*time.Second)))
}
if idpConfig.OAuthAuthorizationEndpoint == "" || idpConfig.OAuthTokenEndpoint == "" {
return nil, caos_errors.ThrowPreconditionFailed(nil, "RP-4n0fs", "Errors.IdentityProvider.InvalidConfig")
return nil, errors.ThrowPreconditionFailed(nil, "RP-4n0fs", "Errors.IdentityProvider.InvalidConfig")
}
oauth2Config := &oauth2.Config{
ClientID: idpConfig.OIDCClientID,
@@ -361,7 +360,7 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
if len(authReq.LinkingUsers) == 0 {
l.renderError(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "LOGIN-asfg3", "Errors.ExternalIDP.NoExternalUserData"))
l.renderError(w, r, authReq, errors.ThrowPreconditionFailed(nil, "LOGIN-asfg3", "Errors.ExternalIDP.NoExternalUserData"))
return
}
@@ -407,19 +406,19 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
}
func (l *Login) mapExternalNotFoundOptionFormDataToLoginUser(formData *externalNotFoundOptionFormData) *domain.ExternalUser {
isEmailVerified := formData.externalRegisterFormData.ExternalEmailVerified && formData.externalRegisterFormData.Email == formData.externalRegisterFormData.ExternalEmail
isPhoneVerified := formData.externalRegisterFormData.ExternalPhoneVerified && formData.externalRegisterFormData.Phone == formData.externalRegisterFormData.ExternalPhone
isEmailVerified := formData.ExternalEmailVerified && formData.Email == formData.ExternalEmail
isPhoneVerified := formData.ExternalPhoneVerified && formData.Phone == formData.ExternalPhone
return &domain.ExternalUser{
IDPConfigID: formData.externalRegisterFormData.ExternalIDPConfigID,
ExternalUserID: formData.externalRegisterFormData.ExternalIDPExtUserID,
PreferredUsername: formData.externalRegisterFormData.Username,
DisplayName: formData.externalRegisterFormData.Email,
FirstName: formData.externalRegisterFormData.Firstname,
LastName: formData.externalRegisterFormData.Lastname,
NickName: formData.externalRegisterFormData.Nickname,
Email: formData.externalRegisterFormData.Email,
IDPConfigID: formData.ExternalIDPConfigID,
ExternalUserID: formData.ExternalIDPExtUserID,
PreferredUsername: formData.Username,
DisplayName: formData.Email,
FirstName: formData.Firstname,
LastName: formData.Lastname,
NickName: formData.Nickname,
Email: formData.Email,
IsEmailVerified: isEmailVerified,
Phone: formData.externalRegisterFormData.Phone,
Phone: formData.Phone,
IsPhoneVerified: isPhoneVerified,
}
}