From 870e3b1b26d3006890048219902b3bc7436fff37 Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:19:05 +0100 Subject: [PATCH] feat: add exclusion of criteria for active idp query (#9040) # Which Problems Are Solved To list IDPs for potential linking, we need to filter them. The GetActiveIdentityProviderResponse should therefore be extended to provide the IDPConfig or information about whether the IDP is allowed to be linked or created. # How the Problems Are Solved Add parameters to the request to exclude CreationDisallowed and/or LinkingDisallowed in the query. # Additional Changes Added integration tests for the GetGetActiveIdentityProvider endpoint. # Additional Context Closes #8981 --------- Co-authored-by: Livio Spring --- internal/api/grpc/idp/v2/query.go | 4 +- .../v2/integration_test/settings_test.go | 282 ++++++++++++++++++ internal/api/grpc/settings/v2/settings.go | 44 ++- .../grpc/settings/v2/settings_converter.go | 9 + .../settings/v2/settings_converter_test.go | 37 ++- internal/integration/client.go | 19 +- internal/query/idp_login_policy_link.go | 61 +++- internal/query/idp_login_policy_link_test.go | 44 ++- internal/query/idp_template.go | 16 + .../zitadel/settings/v2/login_settings.proto | 2 + .../settings/v2/settings_service.proto | 8 +- 11 files changed, 494 insertions(+), 32 deletions(-) diff --git a/internal/api/grpc/idp/v2/query.go b/internal/api/grpc/idp/v2/query.go index 476ac3dcd..0b66175c1 100644 --- a/internal/api/grpc/idp/v2/query.go +++ b/internal/api/grpc/idp/v2/query.go @@ -96,7 +96,7 @@ func configToPb(config *query.IDPTemplate) *idp_pb.IDPConfig { IsCreationAllowed: config.IsCreationAllowed, IsAutoCreation: config.IsAutoCreation, IsAutoUpdate: config.IsAutoUpdate, - AutoLinking: autoLinkingOptionToPb(config.AutoLinking), + AutoLinking: AutoLinkingOptionToPb(config.AutoLinking), }, } if config.OAuthIDPTemplate != nil { @@ -150,7 +150,7 @@ func configToPb(config *query.IDPTemplate) *idp_pb.IDPConfig { return idpConfig } -func autoLinkingOptionToPb(linking domain.AutoLinkingOption) idp_pb.AutoLinkingOption { +func AutoLinkingOptionToPb(linking domain.AutoLinkingOption) idp_pb.AutoLinkingOption { switch linking { case domain.AutoLinkingOptionUnspecified: return idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_UNSPECIFIED diff --git a/internal/api/grpc/settings/v2/integration_test/settings_test.go b/internal/api/grpc/settings/v2/integration_test/settings_test.go index 16942137c..daa800b0b 100644 --- a/internal/api/grpc/settings/v2/integration_test/settings_test.go +++ b/internal/api/grpc/settings/v2/integration_test/settings_test.go @@ -7,11 +7,15 @@ import ( "testing" "time" + "github.com/brianvoe/gofakeit/v6" + "github.com/muhlemmer/gu" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/timestamppb" "github.com/zitadel/zitadel/internal/integration" + "github.com/zitadel/zitadel/pkg/grpc/idp" + idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp/v2" object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2" "github.com/zitadel/zitadel/pkg/grpc/settings/v2" ) @@ -178,3 +182,281 @@ func TestServer_SetSecuritySettings(t *testing.T) { }) } } + +func idpResponse(id, name string, linking, creation, autoCreation, autoUpdate bool, autoLinking idp_pb.AutoLinkingOption) *settings.IdentityProvider { + return &settings.IdentityProvider{ + Id: id, + Name: name, + Type: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_OAUTH, + Options: &idp_pb.Options{ + IsLinkingAllowed: linking, + IsCreationAllowed: creation, + IsAutoCreation: autoCreation, + IsAutoUpdate: autoUpdate, + AutoLinking: autoLinking, + }, + } +} + +func TestServer_GetActiveIdentityProviders(t *testing.T) { + instance := integration.NewInstance(CTX) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) + + instance.AddGenericOAuthProvider(isolatedIAMOwnerCTX, gofakeit.AppName()) // inactive + idpActiveName := gofakeit.AppName() + idpActiveResp := instance.AddGenericOAuthProvider(isolatedIAMOwnerCTX, idpActiveName) + instance.AddProviderToDefaultLoginPolicy(isolatedIAMOwnerCTX, idpActiveResp.GetId()) + idpActiveResponse := idpResponse(idpActiveResp.GetId(), idpActiveName, true, true, true, true, idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME) + idpLinkingDisallowedName := gofakeit.AppName() + idpLinkingDisallowedResp := instance.AddGenericOAuthProviderWithOptions(isolatedIAMOwnerCTX, idpLinkingDisallowedName, false, true, true, idp.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME) + instance.AddProviderToDefaultLoginPolicy(isolatedIAMOwnerCTX, idpLinkingDisallowedResp.GetId()) + idpLinkingDisallowedResponse := idpResponse(idpLinkingDisallowedResp.GetId(), idpLinkingDisallowedName, false, true, true, true, idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME) + idpCreationDisallowedName := gofakeit.AppName() + idpCreationDisallowedResp := instance.AddGenericOAuthProviderWithOptions(isolatedIAMOwnerCTX, idpCreationDisallowedName, true, false, true, idp.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME) + instance.AddProviderToDefaultLoginPolicy(isolatedIAMOwnerCTX, idpCreationDisallowedResp.GetId()) + idpCreationDisallowedResponse := idpResponse(idpCreationDisallowedResp.GetId(), idpCreationDisallowedName, true, false, true, true, idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME) + idpNoAutoCreationName := gofakeit.AppName() + idpNoAutoCreationResp := instance.AddGenericOAuthProviderWithOptions(isolatedIAMOwnerCTX, idpNoAutoCreationName, true, true, false, idp.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME) + instance.AddProviderToDefaultLoginPolicy(isolatedIAMOwnerCTX, idpNoAutoCreationResp.GetId()) + idpNoAutoCreationResponse := idpResponse(idpNoAutoCreationResp.GetId(), idpNoAutoCreationName, true, true, false, true, idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME) + idpNoAutoLinkingName := gofakeit.AppName() + idpNoAutoLinkingResp := instance.AddGenericOAuthProviderWithOptions(isolatedIAMOwnerCTX, idpNoAutoLinkingName, true, true, true, idp.AutoLinkingOption_AUTO_LINKING_OPTION_UNSPECIFIED) + instance.AddProviderToDefaultLoginPolicy(isolatedIAMOwnerCTX, idpNoAutoLinkingResp.GetId()) + idpNoAutoLinkingResponse := idpResponse(idpNoAutoLinkingResp.GetId(), idpNoAutoLinkingName, true, true, true, true, idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_UNSPECIFIED) + + type args struct { + ctx context.Context + req *settings.GetActiveIdentityProvidersRequest + } + tests := []struct { + name string + args args + want *settings.GetActiveIdentityProvidersResponse + wantErr bool + }{ + { + name: "permission error", + args: args{ + ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin), + req: &settings.GetActiveIdentityProvidersRequest{}, + }, + wantErr: true, + }, + { + name: "success, all", + args: args{ + ctx: isolatedIAMOwnerCTX, + req: &settings.GetActiveIdentityProvidersRequest{}, + }, + want: &settings.GetActiveIdentityProvidersResponse{ + Details: &object_pb.ListDetails{ + TotalResult: 5, + Timestamp: timestamppb.Now(), + }, + IdentityProviders: []*settings.IdentityProvider{ + idpActiveResponse, + idpLinkingDisallowedResponse, + idpCreationDisallowedResponse, + idpNoAutoCreationResponse, + idpNoAutoLinkingResponse, + }, + }, + }, + { + name: "success, exclude linking disallowed", + args: args{ + ctx: isolatedIAMOwnerCTX, + req: &settings.GetActiveIdentityProvidersRequest{ + LinkingAllowed: gu.Ptr(true), + }, + }, + want: &settings.GetActiveIdentityProvidersResponse{ + Details: &object_pb.ListDetails{ + TotalResult: 4, + Timestamp: timestamppb.Now(), + }, + IdentityProviders: []*settings.IdentityProvider{ + idpActiveResponse, + idpCreationDisallowedResponse, + idpNoAutoCreationResponse, + idpNoAutoLinkingResponse, + }, + }, + }, + { + name: "success, only linking disallowed", + args: args{ + ctx: isolatedIAMOwnerCTX, + req: &settings.GetActiveIdentityProvidersRequest{ + LinkingAllowed: gu.Ptr(false), + }, + }, + want: &settings.GetActiveIdentityProvidersResponse{ + Details: &object_pb.ListDetails{ + TotalResult: 1, + Timestamp: timestamppb.Now(), + }, + IdentityProviders: []*settings.IdentityProvider{ + idpLinkingDisallowedResponse, + }, + }, + }, + { + name: "success, exclude creation disallowed", + args: args{ + ctx: isolatedIAMOwnerCTX, + req: &settings.GetActiveIdentityProvidersRequest{ + CreationAllowed: gu.Ptr(true), + }, + }, + want: &settings.GetActiveIdentityProvidersResponse{ + Details: &object_pb.ListDetails{ + TotalResult: 4, + Timestamp: timestamppb.Now(), + }, + IdentityProviders: []*settings.IdentityProvider{ + idpActiveResponse, + idpLinkingDisallowedResponse, + idpNoAutoCreationResponse, + idpNoAutoLinkingResponse, + }, + }, + }, + { + name: "success, only creation disallowed", + args: args{ + ctx: isolatedIAMOwnerCTX, + req: &settings.GetActiveIdentityProvidersRequest{ + CreationAllowed: gu.Ptr(false), + }, + }, + want: &settings.GetActiveIdentityProvidersResponse{ + Details: &object_pb.ListDetails{ + TotalResult: 1, + Timestamp: timestamppb.Now(), + }, + IdentityProviders: []*settings.IdentityProvider{ + idpCreationDisallowedResponse, + }, + }, + }, + { + name: "success, auto creation", + args: args{ + ctx: isolatedIAMOwnerCTX, + req: &settings.GetActiveIdentityProvidersRequest{ + AutoCreation: gu.Ptr(true), + }, + }, + want: &settings.GetActiveIdentityProvidersResponse{ + Details: &object_pb.ListDetails{ + TotalResult: 4, + Timestamp: timestamppb.Now(), + }, + IdentityProviders: []*settings.IdentityProvider{ + idpActiveResponse, + idpLinkingDisallowedResponse, + idpCreationDisallowedResponse, + idpNoAutoLinkingResponse, + }, + }, + }, + { + name: "success, no auto creation", + args: args{ + ctx: isolatedIAMOwnerCTX, + req: &settings.GetActiveIdentityProvidersRequest{ + AutoCreation: gu.Ptr(false), + }, + }, + want: &settings.GetActiveIdentityProvidersResponse{ + Details: &object_pb.ListDetails{ + TotalResult: 1, + Timestamp: timestamppb.Now(), + }, + IdentityProviders: []*settings.IdentityProvider{ + idpNoAutoCreationResponse, + }, + }, + }, + { + name: "success, auto linking", + args: args{ + ctx: isolatedIAMOwnerCTX, + req: &settings.GetActiveIdentityProvidersRequest{ + AutoLinking: gu.Ptr(true), + }, + }, + want: &settings.GetActiveIdentityProvidersResponse{ + Details: &object_pb.ListDetails{ + TotalResult: 4, + Timestamp: timestamppb.Now(), + }, + IdentityProviders: []*settings.IdentityProvider{ + idpActiveResponse, + idpLinkingDisallowedResponse, + idpCreationDisallowedResponse, + idpNoAutoCreationResponse, + }, + }, + }, + { + name: "success, no auto linking", + args: args{ + ctx: isolatedIAMOwnerCTX, + req: &settings.GetActiveIdentityProvidersRequest{ + AutoLinking: gu.Ptr(false), + }, + }, + want: &settings.GetActiveIdentityProvidersResponse{ + Details: &object_pb.ListDetails{ + TotalResult: 1, + Timestamp: timestamppb.Now(), + }, + IdentityProviders: []*settings.IdentityProvider{ + idpNoAutoLinkingResponse, + }, + }, + }, + { + name: "success, exclude all", + args: args{ + ctx: isolatedIAMOwnerCTX, + req: &settings.GetActiveIdentityProvidersRequest{ + LinkingAllowed: gu.Ptr(true), + CreationAllowed: gu.Ptr(true), + AutoCreation: gu.Ptr(true), + AutoLinking: gu.Ptr(true), + }, + }, + want: &settings.GetActiveIdentityProvidersResponse{ + Details: &object_pb.ListDetails{ + TotalResult: 1, + Timestamp: timestamppb.Now(), + }, + IdentityProviders: []*settings.IdentityProvider{ + idpActiveResponse, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.args.ctx, time.Minute) + assert.EventuallyWithT(t, func(ct *assert.CollectT) { + got, err := instance.Client.SettingsV2.GetActiveIdentityProviders(tt.args.ctx, tt.args.req) + if tt.wantErr { + assert.Error(ct, err) + return + } + if !assert.NoError(ct, err) { + return + } + for i, result := range tt.want.GetIdentityProviders() { + assert.EqualExportedValues(ct, result, got.GetIdentityProviders()[i]) + } + integration.AssertListDetails(ct, tt.want, got) + }, retryDuration, tick) + }) + } +} diff --git a/internal/api/grpc/settings/v2/settings.go b/internal/api/grpc/settings/v2/settings.go index 3e48ab0c0..8b9ab9b84 100644 --- a/internal/api/grpc/settings/v2/settings.go +++ b/internal/api/grpc/settings/v2/settings.go @@ -120,7 +120,12 @@ func (s *Server) GetLockoutSettings(ctx context.Context, req *settings.GetLockou } func (s *Server) GetActiveIdentityProviders(ctx context.Context, req *settings.GetActiveIdentityProvidersRequest) (*settings.GetActiveIdentityProvidersResponse, error) { - links, err := s.query.IDPLoginPolicyLinks(ctx, object.ResourceOwnerFromReq(ctx, req.GetCtx()), &query.IDPLoginPolicyLinksSearchQuery{}, false) + queries, err := activeIdentityProvidersToQuery(req) + if err != nil { + return nil, err + } + + links, err := s.query.IDPLoginPolicyLinks(ctx, object.ResourceOwnerFromReq(ctx, req.GetCtx()), &query.IDPLoginPolicyLinksSearchQuery{Queries: queries}, false) if err != nil { return nil, err } @@ -131,6 +136,43 @@ func (s *Server) GetActiveIdentityProviders(ctx context.Context, req *settings.G }, nil } +func activeIdentityProvidersToQuery(req *settings.GetActiveIdentityProvidersRequest) (_ []query.SearchQuery, err error) { + q := make([]query.SearchQuery, 0, 4) + if req.CreationAllowed != nil { + creationQuery, err := query.NewIDPTemplateIsCreationAllowedSearchQuery(*req.CreationAllowed) + if err != nil { + return nil, err + } + q = append(q, creationQuery) + } + if req.LinkingAllowed != nil { + creationQuery, err := query.NewIDPTemplateIsLinkingAllowedSearchQuery(*req.LinkingAllowed) + if err != nil { + return nil, err + } + q = append(q, creationQuery) + } + if req.AutoCreation != nil { + creationQuery, err := query.NewIDPTemplateIsAutoCreationSearchQuery(*req.AutoCreation) + if err != nil { + return nil, err + } + q = append(q, creationQuery) + } + if req.AutoLinking != nil { + compare := query.NumberEquals + if *req.AutoLinking { + compare = query.NumberNotEquals + } + creationQuery, err := query.NewIDPTemplateAutoLinkingSearchQuery(0, compare) + if err != nil { + return nil, err + } + q = append(q, creationQuery) + } + return q, nil +} + func (s *Server) GetGeneralSettings(ctx context.Context, _ *settings.GetGeneralSettingsRequest) (*settings.GetGeneralSettingsResponse, error) { instance := authz.GetInstance(ctx) return &settings.GetGeneralSettingsResponse{ diff --git a/internal/api/grpc/settings/v2/settings_converter.go b/internal/api/grpc/settings/v2/settings_converter.go index 222e548d1..b1329a597 100644 --- a/internal/api/grpc/settings/v2/settings_converter.go +++ b/internal/api/grpc/settings/v2/settings_converter.go @@ -5,9 +5,11 @@ import ( "google.golang.org/protobuf/types/known/durationpb" + idp_api "github.com/zitadel/zitadel/internal/api/grpc/idp/v2" "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query" + idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp/v2" "github.com/zitadel/zitadel/pkg/grpc/settings/v2" ) @@ -189,6 +191,13 @@ func identityProviderToPb(idp *query.IDPLoginPolicyLink) *settings.IdentityProvi Id: idp.IDPID, Name: domain.IDPName(idp.IDPName, idp.IDPType), Type: idpTypeToPb(idp.IDPType), + Options: &idp_pb.Options{ + IsLinkingAllowed: idp.IsLinkingAllowed, + IsCreationAllowed: idp.IsCreationAllowed, + IsAutoCreation: idp.IsAutoCreation, + IsAutoUpdate: idp.IsAutoUpdate, + AutoLinking: idp_api.AutoLinkingOptionToPb(idp.AutoLinking), + }, } } diff --git a/internal/api/grpc/settings/v2/settings_converter_test.go b/internal/api/grpc/settings/v2/settings_converter_test.go index 40c381986..937daf671 100644 --- a/internal/api/grpc/settings/v2/settings_converter_test.go +++ b/internal/api/grpc/settings/v2/settings_converter_test.go @@ -16,6 +16,7 @@ import ( "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/pkg/grpc/idp/v2" "github.com/zitadel/zitadel/pkg/grpc/settings/v2" ) @@ -382,14 +383,24 @@ func Test_lockoutSettingsToPb(t *testing.T) { func Test_identityProvidersToPb(t *testing.T) { arg := []*query.IDPLoginPolicyLink{ { - IDPID: "1", - IDPName: "foo", - IDPType: domain.IDPTypeOIDC, + IDPID: "1", + IDPName: "foo", + IDPType: domain.IDPTypeOIDC, + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, }, { - IDPID: "2", - IDPName: "bar", - IDPType: domain.IDPTypeGitHub, + IDPID: "2", + IDPName: "bar", + IDPType: domain.IDPTypeGitHub, + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionEmail, }, } want := []*settings.IdentityProvider{ @@ -397,11 +408,25 @@ func Test_identityProvidersToPb(t *testing.T) { Id: "1", Name: "foo", Type: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_OIDC, + Options: &idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME, + }, }, { Id: "2", Name: "bar", Type: settings.IdentityProviderType_IDENTITY_PROVIDER_TYPE_GITHUB, + Options: &idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL, + }, }, } got := identityProvidersToPb(arg) diff --git a/internal/integration/client.go b/internal/integration/client.go index dde8822ac..e16bb1166 100644 --- a/internal/integration/client.go +++ b/internal/integration/client.go @@ -379,7 +379,18 @@ func (i *Instance) SetUserPassword(ctx context.Context, userID, password string, return resp.GetDetails() } +func (i *Instance) AddProviderToDefaultLoginPolicy(ctx context.Context, id string) { + _, err := i.Client.Admin.AddIDPToLoginPolicy(ctx, &admin.AddIDPToLoginPolicyRequest{ + IdpId: id, + }) + logging.OnError(err).Panic("add provider to default login policy") +} + func (i *Instance) AddGenericOAuthProvider(ctx context.Context, name string) *admin.AddGenericOAuthProviderResponse { + return i.AddGenericOAuthProviderWithOptions(ctx, name, true, true, true, idp.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME) +} + +func (i *Instance) AddGenericOAuthProviderWithOptions(ctx context.Context, name string, isLinkingAllowed, isCreationAllowed, isAutoCreation bool, autoLinking idp.AutoLinkingOption) *admin.AddGenericOAuthProviderResponse { resp, err := i.Client.Admin.AddGenericOAuthProvider(ctx, &admin.AddGenericOAuthProviderRequest{ Name: name, ClientId: "clientID", @@ -390,11 +401,11 @@ func (i *Instance) AddGenericOAuthProvider(ctx context.Context, name string) *ad Scopes: []string{"openid", "profile", "email"}, IdAttribute: "id", ProviderOptions: &idp.Options{ - IsLinkingAllowed: true, - IsCreationAllowed: true, - IsAutoCreation: true, + IsLinkingAllowed: isLinkingAllowed, + IsCreationAllowed: isCreationAllowed, + IsAutoCreation: isAutoCreation, IsAutoUpdate: true, - AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME, + AutoLinking: autoLinking, }, }) logging.OnError(err).Panic("create generic OAuth idp") diff --git a/internal/query/idp_login_policy_link.go b/internal/query/idp_login_policy_link.go index f257e88ad..bdc2ef15b 100644 --- a/internal/query/idp_login_policy_link.go +++ b/internal/query/idp_login_policy_link.go @@ -15,10 +15,15 @@ import ( ) type IDPLoginPolicyLink struct { - IDPID string - IDPName string - IDPType domain.IDPType - OwnerType domain.IdentityProviderType + IDPID string + IDPName string + IDPType domain.IDPType + OwnerType domain.IdentityProviderType + IsCreationAllowed bool + IsLinkingAllowed bool + IsAutoCreation bool + IsAutoUpdate bool + AutoLinking domain.AutoLinkingOption } type IDPLoginPolicyLinks struct { @@ -127,6 +132,11 @@ func prepareIDPLoginPolicyLinksQuery(ctx context.Context, db prepareDatabase, re IDPTemplateNameCol.identifier(), IDPTemplateTypeCol.identifier(), IDPTemplateOwnerTypeCol.identifier(), + IDPTemplateIsCreationAllowedCol.identifier(), + IDPTemplateIsLinkingAllowedCol.identifier(), + IDPTemplateIsAutoCreationCol.identifier(), + IDPTemplateIsAutoUpdateCol.identifier(), + IDPTemplateAutoLinkingCol.identifier(), countColumn.identifier()). From(idpLoginPolicyLinkTable.identifier()). LeftJoin(join(IDPTemplateIDCol, IDPLoginPolicyLinkIDPIDCol)). @@ -141,29 +151,60 @@ func prepareIDPLoginPolicyLinksQuery(ctx context.Context, db prepareDatabase, re var count uint64 for rows.Next() { var ( - idpName = sql.NullString{} - idpType = sql.NullInt16{} - idpOwnerType = sql.NullInt16{} - link = new(IDPLoginPolicyLink) + idpName = sql.NullString{} + idpType = sql.NullInt16{} + idpOwnerType = sql.NullInt16{} + link = new(IDPLoginPolicyLink) + isCreationAllowed = sql.NullBool{} + isLinkingAllowed = sql.NullBool{} + isAutoCreation = sql.NullBool{} + isAutoUpdate = sql.NullBool{} + autoLinking = sql.NullInt16{} ) err := rows.Scan( &link.IDPID, &idpName, &idpType, &idpOwnerType, + &isCreationAllowed, + &isLinkingAllowed, + &isAutoCreation, + &isAutoUpdate, + &autoLinking, &count, ) if err != nil { return nil, err } - link.IDPName = idpName.String + if idpName.Valid { + link.IDPName = idpName.String + } //IDPType 0 is oidc so we have to set unspecified manually if idpType.Valid { link.IDPType = domain.IDPType(idpType.Int16) } else { link.IDPType = domain.IDPTypeUnspecified } - link.OwnerType = domain.IdentityProviderType(idpOwnerType.Int16) + if idpOwnerType.Valid { + link.OwnerType = domain.IdentityProviderType(idpOwnerType.Int16) + } + if isCreationAllowed.Valid { + link.IsCreationAllowed = isCreationAllowed.Bool + } + if isLinkingAllowed.Valid { + link.IsLinkingAllowed = isLinkingAllowed.Bool + } + if isAutoCreation.Valid { + link.IsAutoCreation = isAutoCreation.Bool + } + if isAutoUpdate.Valid { + link.IsAutoUpdate = isAutoUpdate.Bool + } + if autoLinking.Valid { + link.AutoLinking = domain.AutoLinkingOption(autoLinking.Int16) + } else { + link.AutoLinking = domain.AutoLinkingOptionUnspecified + } links = append(links, link) } diff --git a/internal/query/idp_login_policy_link_test.go b/internal/query/idp_login_policy_link_test.go index 45cb8e594..245eb22cc 100644 --- a/internal/query/idp_login_policy_link_test.go +++ b/internal/query/idp_login_policy_link_test.go @@ -19,6 +19,11 @@ var ( ` projections.idp_templates6.name,` + ` projections.idp_templates6.type,` + ` projections.idp_templates6.owner_type,` + + ` projections.idp_templates6.is_creation_allowed,` + + ` projections.idp_templates6.is_linking_allowed,` + + ` projections.idp_templates6.is_auto_creation,` + + ` projections.idp_templates6.is_auto_update,` + + ` projections.idp_templates6.auto_linking,` + ` COUNT(*) OVER ()` + ` FROM projections.idp_login_policy_links5` + ` LEFT JOIN projections.idp_templates6 ON projections.idp_login_policy_links5.idp_id = projections.idp_templates6.id AND projections.idp_login_policy_links5.instance_id = projections.idp_templates6.instance_id` + @@ -31,6 +36,11 @@ var ( "name", "type", "owner_type", + "is_creation_allowed", + "is_linking_allowed", + "is_auto_creation", + "is_auto_update", + "auto_linking", "count", } ) @@ -61,6 +71,11 @@ func Test_IDPLoginPolicyLinkPrepares(t *testing.T) { "idp-name", domain.IDPTypeJWT, domain.IdentityProviderTypeSystem, + true, + true, + true, + true, + domain.AutoLinkingOptionUsername, }, }, ), @@ -71,10 +86,15 @@ func Test_IDPLoginPolicyLinkPrepares(t *testing.T) { }, Links: []*IDPLoginPolicyLink{ { - IDPID: "idp-id", - IDPName: "idp-name", - IDPType: domain.IDPTypeJWT, - OwnerType: domain.IdentityProviderTypeSystem, + IDPID: "idp-id", + IDPName: "idp-name", + IDPType: domain.IDPTypeJWT, + OwnerType: domain.IdentityProviderTypeSystem, + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + AutoLinking: domain.AutoLinkingOptionUsername, }, }, }, @@ -94,6 +114,11 @@ func Test_IDPLoginPolicyLinkPrepares(t *testing.T) { nil, nil, nil, + false, + false, + false, + false, + 0, }, }, ), @@ -104,9 +129,14 @@ func Test_IDPLoginPolicyLinkPrepares(t *testing.T) { }, Links: []*IDPLoginPolicyLink{ { - IDPID: "idp-id", - IDPName: "", - IDPType: domain.IDPTypeUnspecified, + IDPID: "idp-id", + IDPName: "", + IDPType: domain.IDPTypeUnspecified, + IsCreationAllowed: false, + IsLinkingAllowed: false, + IsAutoCreation: false, + IsAutoUpdate: false, + AutoLinking: domain.AutoLinkingOptionUnspecified, }, }, }, diff --git a/internal/query/idp_template.go b/internal/query/idp_template.go index e3250f1ae..dd36cee19 100644 --- a/internal/query/idp_template.go +++ b/internal/query/idp_template.go @@ -825,6 +825,22 @@ func NewIDPTemplateResourceOwnerListSearchQuery(ids ...string) (SearchQuery, err return NewListQuery(IDPTemplateResourceOwnerCol, list, ListIn) } +func NewIDPTemplateIsCreationAllowedSearchQuery(value bool) (SearchQuery, error) { + return NewBoolQuery(IDPTemplateIsCreationAllowedCol, value) +} + +func NewIDPTemplateIsLinkingAllowedSearchQuery(value bool) (SearchQuery, error) { + return NewBoolQuery(IDPTemplateIsLinkingAllowedCol, value) +} + +func NewIDPTemplateIsAutoCreationSearchQuery(value bool) (SearchQuery, error) { + return NewBoolQuery(IDPTemplateIsAutoCreationCol, value) +} + +func NewIDPTemplateAutoLinkingSearchQuery(value int, method NumberComparison) (SearchQuery, error) { + return NewNumberQuery(IDPTemplateAutoLinkingCol, value, method) +} + func (q *IDPTemplateSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { query = q.SearchRequest.toQuery(query) for _, q := range q.Queries { diff --git a/proto/zitadel/settings/v2/login_settings.proto b/proto/zitadel/settings/v2/login_settings.proto index 9fdbb4599..2b5194f0b 100644 --- a/proto/zitadel/settings/v2/login_settings.proto +++ b/proto/zitadel/settings/v2/login_settings.proto @@ -6,6 +6,7 @@ option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2;settings"; import "protoc-gen-openapiv2/options/annotations.proto"; import "zitadel/settings/v2/settings.proto"; +import "zitadel/idp/v2/idp.proto"; import "google/protobuf/duration.proto"; message LoginSettings { @@ -134,6 +135,7 @@ message IdentityProvider { string id = 1; string name = 2; IdentityProviderType type = 3; + zitadel.idp.v2.Options options = 4; } enum IdentityProviderType { diff --git a/proto/zitadel/settings/v2/settings_service.proto b/proto/zitadel/settings/v2/settings_service.proto index cc8e5d05c..77c20eb1c 100644 --- a/proto/zitadel/settings/v2/settings_service.proto +++ b/proto/zitadel/settings/v2/settings_service.proto @@ -324,7 +324,7 @@ service SettingsService { }; } -// Get the security settings + // Get the security settings rpc GetSecuritySettings(GetSecuritySettingsRequest) returns (GetSecuritySettingsResponse) { option (google.api.http) = { get: "/v2/settings/security"; @@ -343,7 +343,7 @@ service SettingsService { }; } -// Set the security settings + // Set the security settings rpc SetSecuritySettings(SetSecuritySettingsRequest) returns (SetSecuritySettingsResponse) { option (google.api.http) = { put: "/v2/policies/security"; @@ -429,6 +429,10 @@ message GetLockoutSettingsResponse { message GetActiveIdentityProvidersRequest { zitadel.object.v2.RequestContext ctx = 1; + optional bool creation_allowed = 2; + optional bool linking_allowed = 3; + optional bool auto_creation = 4; + optional bool auto_linking = 5; } message GetActiveIdentityProvidersResponse {