feat: preuserinfo execution for actions v2

This commit is contained in:
Stefan Benz 2025-02-19 19:10:21 +01:00
parent afb9fe0f3d
commit 0b0e660d2d
No known key found for this signature in database
GPG Key ID: 071AA751ED4F9D31
7 changed files with 562 additions and 27 deletions

View File

@ -460,7 +460,7 @@ func startAPIs(
if err := apis.RegisterService(ctx, idp_v2.CreateServer(commands, queries, permissionCheck)); err != nil { if err := apis.RegisterService(ctx, idp_v2.CreateServer(commands, queries, permissionCheck)); err != nil {
return nil, err 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 return nil, err
} }
if err := apis.RegisterService(ctx, userschema_v3_alpha.CreateServer(config.SystemDefaults, commands, queries)); err != nil { if err := apis.RegisterService(ctx, userschema_v3_alpha.CreateServer(config.SystemDefaults, commands, queries)); err != nil {

View File

@ -106,9 +106,7 @@ func TestServer_CreateCallback(t *testing.T) {
name string name string
ctx context.Context ctx context.Context
req *oidc_pb.CreateCallbackRequest req *oidc_pb.CreateCallbackRequest
AuthError string
want *oidc_pb.CreateCallbackResponse want *oidc_pb.CreateCallbackResponse
wantURL *url.URL
wantErr bool wantErr bool
}{ }{
{ {

View File

@ -8,21 +8,45 @@ import (
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"reflect" "reflect"
"regexp"
"strings"
"testing" "testing"
"time" "time"
"github.com/brianvoe/gofakeit/v6" "github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware" "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/domain"
"github.com/zitadel/zitadel/internal/integration" "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" 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" action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/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) { func TestServer_ExecutionTarget(t *testing.T) {
@ -408,3 +432,375 @@ func testServerCall(
return server.URL, server.Close 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,
}
}

View File

@ -20,7 +20,9 @@ import (
"github.com/zitadel/zitadel/internal/actions/object" "github.com/zitadel/zitadel/internal/actions/object"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/execution"
"github.com/zitadel/zitadel/internal/query" "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/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors" "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 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
}

View File

@ -183,7 +183,7 @@ func StartCommands(
EventGroupExisting: func(group string) bool { return true }, EventGroupExisting: func(group string) bool { return true },
GrpcServiceExisting: func(service string) bool { return false }, GrpcServiceExisting: func(service string) bool { return false },
GrpcMethodExisting: func(method string) bool { return false }, GrpcMethodExisting: func(method string) bool { return false },
ActionFunctionExisting: domain.FunctionExists(), ActionFunctionExisting: domain.ActionFunctionExists(),
multifactors: domain.MultifactorConfigs{ multifactors: domain.MultifactorConfigs{
OTP: domain.OTPConfig{ OTP: domain.OTPConfig{
CryptoMFA: otpEncryption, CryptoMFA: otpEncryption,

View File

@ -1,6 +1,7 @@
package domain package domain
import ( import (
"slices"
"time" "time"
"github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/eventstore/v1/models"
@ -45,3 +46,49 @@ const (
ActionsMaxAllowed ActionsMaxAllowed
ActionsAllowedUnlimited 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)
}
}

View File

@ -1,7 +1,6 @@
package domain package domain
import ( import (
"slices"
"strconv" "strconv"
) )
@ -150,20 +149,3 @@ func (s TriggerType) LocalizationKey() string {
return "Action.TriggerType.Unspecified" 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)
}
}