diff --git a/cmd/start/start.go b/cmd/start/start.go index 4091213d2d..c8c2d1fb55 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -460,7 +460,7 @@ func startAPIs( if err := apis.RegisterService(ctx, idp_v2.CreateServer(commands, queries, permissionCheck)); err != nil { return nil, err } - if err := apis.RegisterService(ctx, action_v3_alpha.CreateServer(config.SystemDefaults, commands, queries, domain.AllFunctions, apis.ListGrpcMethods, apis.ListGrpcServices)); err != nil { + if err := apis.RegisterService(ctx, action_v3_alpha.CreateServer(config.SystemDefaults, commands, queries, domain.AllActionFunctions, apis.ListGrpcMethods, apis.ListGrpcServices)); err != nil { return nil, err } if err := apis.RegisterService(ctx, userschema_v3_alpha.CreateServer(config.SystemDefaults, commands, queries)); err != nil { diff --git a/internal/api/grpc/oidc/v2/integration_test/oidc_test.go b/internal/api/grpc/oidc/v2/integration_test/oidc_test.go index d6b5c7b8cf..e6a73f10c8 100644 --- a/internal/api/grpc/oidc/v2/integration_test/oidc_test.go +++ b/internal/api/grpc/oidc/v2/integration_test/oidc_test.go @@ -103,13 +103,11 @@ func TestServer_CreateCallback(t *testing.T) { sessionResp := createSession(t, CTX, Instance.Users[integration.UserTypeOrgOwner].ID) tests := []struct { - name string - ctx context.Context - req *oidc_pb.CreateCallbackRequest - AuthError string - want *oidc_pb.CreateCallbackResponse - wantURL *url.URL - wantErr bool + name string + ctx context.Context + req *oidc_pb.CreateCallbackRequest + want *oidc_pb.CreateCallbackResponse + wantErr bool }{ { name: "Not found", diff --git a/internal/api/grpc/resources/action/v3alpha/integration_test/execution_target_test.go b/internal/api/grpc/resources/action/v3alpha/integration_test/execution_target_test.go index 7aff6afb3f..ebdfb604d9 100644 --- a/internal/api/grpc/resources/action/v3alpha/integration_test/execution_target_test.go +++ b/internal/api/grpc/resources/action/v3alpha/integration_test/execution_target_test.go @@ -8,21 +8,45 @@ import ( "io" "net/http" "net/http/httptest" + "net/url" "reflect" + "regexp" + "strings" "testing" "time" "github.com/brianvoe/gofakeit/v6" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zitadel/oidc/v3/pkg/client/rp" + "github.com/zitadel/oidc/v3/pkg/oidc" + "golang.org/x/text/language" "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/zitadel/zitadel/internal/api/grpc/server/middleware" + oidc_api "github.com/zitadel/zitadel/internal/api/oidc" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/integration" + "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/pkg/grpc/app" + "github.com/zitadel/zitadel/pkg/grpc/management" + "github.com/zitadel/zitadel/pkg/grpc/metadata" + object_v2 "github.com/zitadel/zitadel/pkg/grpc/object/v2" object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha" + oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2" action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha" resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha" + "github.com/zitadel/zitadel/pkg/grpc/session/v2" + "github.com/zitadel/zitadel/pkg/grpc/user/v2" +) + +const ( + redirectURIImplicit = "http://localhost:9999/callback" +) + +var ( + loginV2 = &app.LoginVersion{Version: &app.LoginVersion_LoginV2{LoginV2: &app.LoginV2{BaseUri: nil}}} ) func TestServer_ExecutionTarget(t *testing.T) { @@ -408,3 +432,375 @@ func testServerCall( return server.URL, server.Close } + +func conditionFunction(function string) *action.Condition { + return &action.Condition{ + ConditionType: &action.Condition_Function{ + Function: &action.FunctionExecution{ + Name: function, + }, + }, + } +} + +func TestServer_ExecutionTargetPreUserinfo(t *testing.T) { + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) + ctxLoginClient := instance.WithAuthorization(CTX, integration.UserTypeLogin) + + client, err := instance.CreateOIDCImplicitFlowClient(isolatedIAMCtx, redirectURIImplicit, loginV2) + require.NoError(t, err) + + userEmail := gofakeit.Email() + userPhone := "+41" + gofakeit.Phone() + userResp := instance.CreateHumanUserVerified(isolatedIAMCtx, instance.DefaultOrg.Id, userEmail, userPhone) + sessionResp, err := instance.Client.SessionV2.CreateSession(ctxLoginClient, &session.CreateSessionRequest{ + Checks: &session.Checks{ + User: &session.CheckUser{ + Search: &session.CheckUser_UserId{ + UserId: userResp.GetUserId(), + }, + }, + }, + }) + require.NoError(t, err) + + type want struct { + resp *oidc_pb.CreateCallbackResponse + addedClaims map[string]any + addedLogClaims map[string][]string + setUserMetadata []*metadata.Metadata + } + tests := []struct { + name string + ctx context.Context + dep func(ctx context.Context, t *testing.T) func() + req *oidc_pb.CreateCallbackRequest + want want + wantErr bool + }{ + { + name: "append claim", + ctx: ctxLoginClient, + dep: func(ctx context.Context, t *testing.T) func() { + changedRequest := &oidc_api.ContextInfoPreUserinfoResponse{ + AppendClaims: []*oidc_api.AppendClaim{ + {Key: "added", Value: "value"}, + }, + } + expectedContextInfo := contextInfoPreUserinfoForUser(instance, userResp, userEmail, userPhone) + + targetURL, closeF := testServerCall(expectedContextInfo, 0, http.StatusOK, changedRequest) + + targetResp := waitForTarget(ctx, t, instance, targetURL, domain.TargetTypeCall, true) + waitForExecutionOnCondition(ctx, t, instance, conditionFunction("preuserinfo"), executionTargetsSingleTarget(targetResp.GetDetails().GetId())) + + return closeF + }, + req: &oidc_pb.CreateCallbackRequest{ + AuthRequestId: func() string { + authRequestID, err := instance.CreateOIDCAuthRequestImplicitWithoutLoginClientHeader(isolatedIAMCtx, client.GetClientId(), redirectURIImplicit) + require.NoError(t, err) + return authRequestID + }(), + CallbackKind: &oidc_pb.CreateCallbackRequest_Session{ + Session: &oidc_pb.Session{ + SessionId: sessionResp.GetSessionId(), + SessionToken: sessionResp.GetSessionToken(), + }, + }, + }, + want: want{ + resp: &oidc_pb.CreateCallbackResponse{ + CallbackUrl: `http:\/\/localhost:9999\/callback#access_token=(.*)&expires_in=(.*)&id_token=(.*)&state=state&token_type=Bearer`, + Details: &object_v2.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: instance.ID(), + }, + }, + addedClaims: map[string]any{ + "added": "value", + }, + }, + wantErr: false, + }, + { + name: "append log claim", + ctx: ctxLoginClient, + dep: func(ctx context.Context, t *testing.T) func() { + changedRequest := &oidc_api.ContextInfoPreUserinfoResponse{ + AppendLogClaims: []string{ + "addedLog", + }, + } + expectedContextInfo := contextInfoPreUserinfoForUser(instance, userResp, userEmail, userPhone) + + targetURL, closeF := testServerCall(expectedContextInfo, 0, http.StatusOK, changedRequest) + + targetResp := waitForTarget(ctx, t, instance, targetURL, domain.TargetTypeCall, true) + waitForExecutionOnCondition(ctx, t, instance, conditionFunction("preuserinfo"), executionTargetsSingleTarget(targetResp.GetDetails().GetId())) + + return closeF + }, + req: &oidc_pb.CreateCallbackRequest{ + AuthRequestId: func() string { + authRequestID, err := instance.CreateOIDCAuthRequestImplicitWithoutLoginClientHeader(isolatedIAMCtx, client.GetClientId(), redirectURIImplicit) + require.NoError(t, err) + return authRequestID + }(), + CallbackKind: &oidc_pb.CreateCallbackRequest_Session{ + Session: &oidc_pb.Session{ + SessionId: sessionResp.GetSessionId(), + SessionToken: sessionResp.GetSessionToken(), + }, + }, + }, + want: want{ + resp: &oidc_pb.CreateCallbackResponse{ + CallbackUrl: `http:\/\/localhost:9999\/callback#access_token=(.*)&expires_in=(.*)&id_token=(.*)&state=state&token_type=Bearer`, + Details: &object_v2.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: instance.ID(), + }, + }, + addedLogClaims: map[string][]string{ + "urn:zitadel:iam:action:function/preuserinfo:log": {"addedLog"}, + }, + }, + wantErr: false, + }, + { + name: "set user metadata", + ctx: ctxLoginClient, + dep: func(ctx context.Context, t *testing.T) func() { + changedRequest := &oidc_api.ContextInfoPreUserinfoResponse{ + SetUserMetadata: []*domain.Metadata{ + {Key: "key", Value: []byte("value")}, + }, + } + expectedContextInfo := contextInfoPreUserinfoForUser(instance, userResp, userEmail, userPhone) + + targetURL, closeF := testServerCall(expectedContextInfo, 0, http.StatusOK, changedRequest) + + targetResp := waitForTarget(ctx, t, instance, targetURL, domain.TargetTypeCall, true) + waitForExecutionOnCondition(ctx, t, instance, conditionFunction("preuserinfo"), executionTargetsSingleTarget(targetResp.GetDetails().GetId())) + + return closeF + }, + req: &oidc_pb.CreateCallbackRequest{ + AuthRequestId: func() string { + authRequestID, err := instance.CreateOIDCAuthRequestImplicitWithoutLoginClientHeader(isolatedIAMCtx, client.GetClientId(), redirectURIImplicit) + require.NoError(t, err) + return authRequestID + }(), + CallbackKind: &oidc_pb.CreateCallbackRequest_Session{ + Session: &oidc_pb.Session{ + SessionId: sessionResp.GetSessionId(), + SessionToken: sessionResp.GetSessionToken(), + }, + }, + }, + want: want{ + resp: &oidc_pb.CreateCallbackResponse{ + CallbackUrl: `http:\/\/localhost:9999\/callback#access_token=(.*)&expires_in=(.*)&id_token=(.*)&state=state&token_type=Bearer`, + Details: &object_v2.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: instance.ID(), + }, + }, + setUserMetadata: []*metadata.Metadata{ + {Key: "key", Value: []byte("value")}, + }, + }, + wantErr: false, + }, + { + name: "full usage", + ctx: ctxLoginClient, + dep: func(ctx context.Context, t *testing.T) func() { + changedRequest := &oidc_api.ContextInfoPreUserinfoResponse{ + SetUserMetadata: []*domain.Metadata{ + {Key: "key1", Value: []byte("value1")}, + {Key: "key2", Value: []byte("value2")}, + {Key: "key3", Value: []byte("value3")}, + }, + AppendLogClaims: []string{ + "addedLog1", + "addedLog2", + "addedLog3", + }, + AppendClaims: []*oidc_api.AppendClaim{ + {Key: "added1", Value: "value1"}, + {Key: "added2", Value: "value2"}, + {Key: "added3", Value: "value3"}, + }, + } + expectedContextInfo := contextInfoPreUserinfoForUser(instance, userResp, userEmail, userPhone) + + targetURL, closeF := testServerCall(expectedContextInfo, 0, http.StatusOK, changedRequest) + + targetResp := waitForTarget(ctx, t, instance, targetURL, domain.TargetTypeCall, true) + waitForExecutionOnCondition(ctx, t, instance, conditionFunction("preuserinfo"), executionTargetsSingleTarget(targetResp.GetDetails().GetId())) + + return closeF + }, + req: &oidc_pb.CreateCallbackRequest{ + AuthRequestId: func() string { + authRequestID, err := instance.CreateOIDCAuthRequestImplicitWithoutLoginClientHeader(isolatedIAMCtx, client.GetClientId(), redirectURIImplicit) + require.NoError(t, err) + return authRequestID + }(), + CallbackKind: &oidc_pb.CreateCallbackRequest_Session{ + Session: &oidc_pb.Session{ + SessionId: sessionResp.GetSessionId(), + SessionToken: sessionResp.GetSessionToken(), + }, + }, + }, + want: want{ + resp: &oidc_pb.CreateCallbackResponse{ + CallbackUrl: `http:\/\/localhost:9999\/callback#access_token=(.*)&expires_in=(.*)&id_token=(.*)&state=state&token_type=Bearer`, + Details: &object_v2.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: instance.ID(), + }, + }, + addedClaims: map[string]any{ + "added1": "value1", + "added2": "value2", + "added3": "value3", + }, + setUserMetadata: []*metadata.Metadata{ + {Key: "key1", Value: []byte("value1")}, + {Key: "key2", Value: []byte("value2")}, + {Key: "key3", Value: []byte("value3")}, + }, + addedLogClaims: map[string][]string{ + "urn:zitadel:iam:action:function/preuserinfo:log": {"addedLog1", "addedLog2", "addedLog3"}, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + closeF := tt.dep(isolatedIAMCtx, t) + defer closeF() + + got, err := instance.Client.OIDCv2.CreateCallback(tt.ctx, tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + if tt.want.resp != nil { + if !assert.Regexp(t, regexp.MustCompile(tt.want.resp.CallbackUrl), got.GetCallbackUrl()) { + return + } + + callbackUrl, err := url.Parse(strings.Replace(got.GetCallbackUrl(), "#", "?", 1)) + require.NoError(t, err) + claims := getClaimsFromCallbackURL(tt.ctx, t, instance, client.GetClientId(), callbackUrl) + + for k, v := range tt.want.addedClaims { + value, ok := claims.Claims[k] + if !assert.True(t, ok) { + return + } + assert.Equal(t, v, value) + } + for k, v := range tt.want.addedLogClaims { + value, ok := claims.Claims[k] + if !assert.True(t, ok) { + return + } + assert.ElementsMatch(t, v, value) + } + if len(tt.want.setUserMetadata) > 0 { + checkForSetMetadata(isolatedIAMCtx, t, instance, userResp.GetUserId(), tt.want.setUserMetadata) + } + } + }) + } +} + +func checkForSetMetadata(ctx context.Context, t *testing.T, instance *integration.Instance, userID string, metadataExpected []*metadata.Metadata) { + integration.WaitForAndTickWithMaxDuration(ctx, time.Minute) + + retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute) + assert.EventuallyWithT(t, func(ct *assert.CollectT) { + metadataResp, err := instance.Client.Mgmt.ListUserMetadata(ctx, &management.ListUserMetadataRequest{Id: userID}) + if !assert.NoError(ct, err) { + return + } + for _, dataExpected := range metadataExpected { + found := false + for _, dataCheck := range metadataResp.GetResult() { + if dataExpected.Key == dataCheck.Key { + found = true + if !assert.Equal(ct, dataExpected.Value, dataCheck.Value) { + return + } + } + } + if !assert.True(ct, found) { + return + } + } + }, retryDuration, tick) +} + +func getClaimsFromCallbackURL(ctx context.Context, t *testing.T, instance *integration.Instance, clientID string, callbackURL *url.URL) *oidc.IDTokenClaims { + accessToken := callbackURL.Query().Get("access_token") + idToken := callbackURL.Query().Get("id_token") + + provider, err := instance.CreateRelyingParty(ctx, clientID, redirectURIImplicit, oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopePhone) + require.NoError(t, err) + claims, err := rp.VerifyTokens[*oidc.IDTokenClaims](context.Background(), accessToken, idToken, provider.IDTokenVerifier()) + require.NoError(t, err) + return claims +} + +func contextInfoPreUserinfoForUser(instance *integration.Instance, userResp *user.AddHumanUserResponse, email, phone string) *oidc_api.ContextInfoPreUserinfo { + return &oidc_api.ContextInfoPreUserinfo{ + Function: "function/preuserinfo", + UserInfo: &oidc.UserInfo{ + Subject: userResp.GetUserId(), + }, + User: &query.User{ + ID: userResp.GetUserId(), + CreationDate: userResp.Details.ChangeDate.AsTime(), + ChangeDate: userResp.Details.ChangeDate.AsTime(), + ResourceOwner: instance.DefaultOrg.GetId(), + Sequence: userResp.Details.Sequence, + State: 1, + Username: email, + PreferredLoginName: email, + Human: &query.Human{ + FirstName: "Mickey", + LastName: "Mouse", + NickName: "Mickey", + DisplayName: "Mickey Mouse", + AvatarKey: "", + PreferredLanguage: language.Dutch, + Gender: 2, + Email: domain.EmailAddress(email), + IsEmailVerified: true, + Phone: domain.PhoneNumber(phone), + IsPhoneVerified: true, + PasswordChangeRequired: false, + PasswordChanged: time.Time{}, + MFAInitSkipped: time.Time{}, + }, + }, + UserMetadata: nil, + Org: &query.UserInfoOrg{ + ID: instance.DefaultOrg.GetId(), + Name: instance.DefaultOrg.GetName(), + PrimaryDomain: instance.DefaultOrg.GetPrimaryDomain(), + }, + UserGrants: nil, + Response: nil, + } +} diff --git a/internal/api/oidc/userinfo.go b/internal/api/oidc/userinfo.go index b2121a73a2..090ebda313 100644 --- a/internal/api/oidc/userinfo.go +++ b/internal/api/oidc/userinfo.go @@ -20,7 +20,9 @@ import ( "github.com/zitadel/zitadel/internal/actions/object" "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/execution" "github.com/zitadel/zitadel/internal/query" + exec_repo "github.com/zitadel/zitadel/internal/repository/execution" "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -410,5 +412,115 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user } } + function := exec_repo.ID(domain.ExecutionTypeFunction, domain.ActionFunctionPreUserinfo.LocalizationKey()) + executionTargets, err := queryExecutionTargets(ctx, s.query, function) + if err != nil { + return err + } + info := &ContextInfoPreUserinfo{ + Function: function, + UserInfo: userInfo, + User: qu.User, + UserMetadata: qu.Metadata, + Org: qu.Org, + UserGrants: qu.UserGrants, + } + + resp, err := execution.CallTargets(ctx, executionTargets, info) + if err != nil { + return err + } + contextInfoResponse := resp.(*ContextInfoPreUserinfoResponse) + claimLogs := make([]string, 0) + for _, metadata := range contextInfoResponse.SetUserMetadata { + if _, err = s.command.SetUserMetadata(ctx, metadata, userInfo.Subject, qu.User.ResourceOwner); err != nil { + claimLogs = append(claimLogs, fmt.Sprintf("failed to set user metadata key %q", metadata.Key)) + } + } + for _, claim := range contextInfoResponse.AppendClaims { + if strings.HasPrefix(claim.Key, ClaimPrefix) { + continue + } + if userInfo.Claims[claim.Key] == nil { + userInfo.AppendClaims(claim.Key, claim.Value) + continue + } + claimLogs = append(claimLogs, fmt.Sprintf("key %q already exists", claim.Key)) + } + for _, log := range contextInfoResponse.AppendLogClaims { + claimLogs = append(claimLogs, log) + } + if len(claimLogs) > 0 { + userInfo.AppendClaims(fmt.Sprintf(ClaimActionLogFormat, function), claimLogs) + } return nil } + +type ContextInfoPreUserinfo struct { + Function string `json:"function,omitempty"` + UserInfo *oidc.UserInfo `json:"userinfo,omitempty"` + User *query.User `json:"user,omitempty"` + UserMetadata []query.UserMetadata `json:"user_metadata,omitempty"` + Org *query.UserInfoOrg `json:"org,omitempty"` + UserGrants []query.UserGrant `json:"user_grants,omitempty"` + Response *ContextInfoPreUserinfoResponse `json:"response,omitempty"` +} + +type ContextInfoPreUserinfoResponse struct { + SetUserMetadata []*domain.Metadata `json:"set_user_metadata,omitempty"` + AppendClaims []*AppendClaim `json:"append_claims,omitempty"` + AppendLogClaims []string `json:"append_log_claims,omitempty"` +} + +type AppendClaim struct { + Key string `json:"key"` + Value any `json:"value"` +} + +func (c *ContextInfoPreUserinfo) GetHTTPRequestBody() []byte { + data, err := json.Marshal(c) + if err != nil { + return nil + } + return data +} + +func (c *ContextInfoPreUserinfo) SetHTTPResponseBody(resp []byte) error { + if !json.Valid(resp) { + return zerrors.ThrowPreconditionFailed(nil, "ACTION-4m9s2", "Errors.Execution.ResponseIsNotValidJSON") + } + if c.Response == nil { + c.Response = &ContextInfoPreUserinfoResponse{} + } + return json.Unmarshal(resp, c.Response) +} + +func (c *ContextInfoPreUserinfo) GetContent() interface{} { + return c.Response +} + +func queryExecutionTargets(ctx context.Context, query *query.Queries, function string) ([]execution.Target, error) { + queriedActionsV2, err := query.TargetsByExecutionID(ctx, []string{function}) + if err != nil { + return nil, err + } + executionTargets := make([]execution.Target, len(queriedActionsV2)) + for i, action := range queriedActionsV2 { + executionTargets[i] = action + } + return executionTargets, nil +} + +func (s *Server) queryOrgMetadata(ctx context.Context, organizationID string) ([]*query.OrgMetadata, error) { + metadata, err := s.query.SearchOrgMetadata( + ctx, + true, + organizationID, + &query.OrgMetadataSearchQueries{}, + false, + ) + if err != nil { + return nil, err + } + return metadata.Metadata, nil +} diff --git a/internal/command/command.go b/internal/command/command.go index ab047fccdb..486e044a41 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -183,7 +183,7 @@ func StartCommands( EventGroupExisting: func(group string) bool { return true }, GrpcServiceExisting: func(service string) bool { return false }, GrpcMethodExisting: func(method string) bool { return false }, - ActionFunctionExisting: domain.FunctionExists(), + ActionFunctionExisting: domain.ActionFunctionExists(), multifactors: domain.MultifactorConfigs{ OTP: domain.OTPConfig{ CryptoMFA: otpEncryption, diff --git a/internal/domain/action.go b/internal/domain/action.go index 18dd23e8c5..90eb3a37d7 100644 --- a/internal/domain/action.go +++ b/internal/domain/action.go @@ -1,6 +1,7 @@ package domain import ( + "slices" "time" "github.com/zitadel/zitadel/internal/eventstore/v1/models" @@ -45,3 +46,49 @@ const ( ActionsMaxAllowed ActionsAllowedUnlimited ) + +type ActionFunction int32 + +const ( + ActionFunctionUnspecified ActionFunction = iota + ActionFunctionPreUserinfo + ActionFunctionPreAccessToken + ActionFunctionPreSAMLResponse + actionFunctionCount +) + +func (s ActionFunction) Valid() bool { + return s >= 0 && s < actionFunctionCount +} + +func (s ActionFunction) LocalizationKey() string { + if !s.Valid() { + return ActionFunctionUnspecified.LocalizationKey() + } + + switch s { + case ActionFunctionPreUserinfo: + return "preuserinfo" + case ActionFunctionPreAccessToken: + return "preaccesstoken" + case ActionFunctionPreSAMLResponse: + return "presamlresponse" + default: + return "unspecified" + } +} + +func AllActionFunctions() []string { + return []string{ + ActionFunctionPreUserinfo.LocalizationKey(), + ActionFunctionPreAccessToken.LocalizationKey(), + ActionFunctionPreSAMLResponse.LocalizationKey(), + } +} + +func ActionFunctionExists() func(string) bool { + functions := AllActionFunctions() + return func(s string) bool { + return slices.Contains(functions, s) + } +} diff --git a/internal/domain/flow.go b/internal/domain/flow.go index 143ce6bd0b..39cb13fc1e 100644 --- a/internal/domain/flow.go +++ b/internal/domain/flow.go @@ -1,7 +1,6 @@ package domain import ( - "slices" "strconv" ) @@ -150,20 +149,3 @@ func (s TriggerType) LocalizationKey() string { return "Action.TriggerType.Unspecified" } } - -func AllFunctions() []string { - functions := make([]string, 0) - for _, flowType := range AllFlowTypes() { - for _, triggerType := range flowType.TriggerTypes() { - functions = append(functions, flowType.LocalizationKey()+"."+triggerType.LocalizationKey()) - } - } - return functions -} - -func FunctionExists() func(string) bool { - functions := AllFunctions() - return func(s string) bool { - return slices.Contains(functions, s) - } -}