diff --git a/internal/api/grpc/admin/import.go b/internal/api/grpc/admin/import.go index b5efbb7817..6b1e54fefa 100644 --- a/internal/api/grpc/admin/import.go +++ b/internal/api/grpc/admin/import.go @@ -623,11 +623,10 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm if org.UserLinks != nil { for _, userLinks := range org.GetUserLinks() { logging.Debugf("import userlink: %s", userLinks.GetUserId()+"_"+userLinks.GetIdpId()+"_"+userLinks.GetProvidedUserId()+"_"+userLinks.GetProvidedUserName()) - externalIDP := &domain.UserIDPLink{ - ObjectRoot: models.ObjectRoot{AggregateID: userLinks.UserId}, - IDPConfigID: userLinks.IdpId, - ExternalUserID: userLinks.ProvidedUserId, - DisplayName: userLinks.ProvidedUserName, + externalIDP := &command.AddLink{ + IDPID: userLinks.IdpId, + IDPExternalID: userLinks.ProvidedUserId, + DisplayName: userLinks.ProvidedUserName, } if _, err := s.command.AddUserIDPLink(ctx, userLinks.UserId, org.GetOrgId(), externalIDP); err != nil { errors = append(errors, &admin_pb.ImportDataError{Type: "user_link", Id: userLinks.UserId + "_" + userLinks.IdpId, Message: err.Error()}) diff --git a/internal/api/grpc/user/v2/user.go b/internal/api/grpc/user/v2/user.go index f4ec0eaa9d..78df98a90e 100644 --- a/internal/api/grpc/user/v2/user.go +++ b/internal/api/grpc/user/v2/user.go @@ -116,10 +116,10 @@ func genderToDomain(gender user.Gender) domain.Gender { func (s *Server) AddIDPLink(ctx context.Context, req *user.AddIDPLinkRequest) (_ *user.AddIDPLinkResponse, err error) { orgID := authz.GetCtxData(ctx).OrgID - details, err := s.command.AddUserIDPLink(ctx, req.UserId, orgID, &domain.UserIDPLink{ - IDPConfigID: req.GetIdpLink().GetIdpId(), - ExternalUserID: req.GetIdpLink().GetUserId(), - DisplayName: req.GetIdpLink().GetUserName(), + details, err := s.command.AddUserIDPLink(ctx, req.UserId, orgID, &command.AddLink{ + IDPID: req.GetIdpLink().GetIdpId(), + DisplayName: req.GetIdpLink().GetUserName(), + IDPExternalID: req.GetIdpLink().GetUserId(), }) if err != nil { return nil, err diff --git a/internal/api/ui/login/external_provider_handler.go b/internal/api/ui/login/external_provider_handler.go index d895f1aef7..f9bfb4023f 100644 --- a/internal/api/ui/login/external_provider_handler.go +++ b/internal/api/ui/login/external_provider_handler.go @@ -66,6 +66,7 @@ type externalNotFoundOptionData struct { ExternalEmailVerified bool ExternalPhone domain.PhoneNumber ExternalPhoneVerified bool + ProviderName string } type externalRegisterFormData struct { @@ -503,6 +504,7 @@ func (l *Login) renderExternalNotFoundOption(w http.ResponseWriter, r *http.Requ ShowUsername: orgIAMPolicy.UserLoginMustBeDomain, ShowUsernameSuffix: !labelPolicy.HideLoginNameSuffix, OrgRegister: orgIAMPolicy.UserLoginMustBeDomain, + ProviderName: domain.IDPName(idpTemplate.Name, idpTemplate.Type), } if human.Phone != nil { data.Phone = human.PhoneNumber diff --git a/internal/api/ui/login/static/templates/external_not_found_option.html b/internal/api/ui/login/static/templates/external_not_found_option.html index 086662a8ed..dcc1961b4b 100644 --- a/internal/api/ui/login/static/templates/external_not_found_option.html +++ b/internal/api/ui/login/static/templates/external_not_found_option.html @@ -1,8 +1,11 @@ {{template "main-top" .}}
-

{{t "ExternalNotFound.Title"}}

+

{{.ProviderName}} - {{t "ExternalNotFound.Title"}}

+ + {{ if or .IsLinkingAllowed .IsCreationAllowed }}

{{t "ExternalNotFound.Description"}}

+ {{ end }}
@@ -21,6 +24,7 @@
+ {{ if or .IsCreationAllowed }}
@@ -85,8 +89,9 @@
+ {{end}} - {{ if or .TOSLink .PrivacyLink }} + {{ if and (or .IsLinkingAllowed .IsCreationAllowed) (or .TOSLink .PrivacyLink) }}
{{ if .TOSLink }} diff --git a/internal/command/idp_model.go b/internal/command/idp_model.go index 417f984648..e01d831247 100644 --- a/internal/command/idp_model.go +++ b/internal/command/idp_model.go @@ -189,6 +189,10 @@ func (wm *OAuthIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.Encry ) } +func (wm *OAuthIDPWriteModel) GetProviderOptions() idp.Options { + return wm.Options +} + type OIDCIDPWriteModel struct { eventstore.WriteModel @@ -310,7 +314,10 @@ func (wm *OIDCIDPWriteModel) NewChanges( // reduceIDPConfigAddedEvent handles old idpConfig events func (wm *OIDCIDPWriteModel) reduceIDPConfigAddedEvent(e *idpconfig.IDPConfigAddedEvent) { wm.Name = e.Name + wm.Options.IsCreationAllowed = true + wm.Options.IsLinkingAllowed = true wm.Options.IsAutoCreation = e.AutoRegister + wm.Options.IsAutoUpdate = false wm.State = domain.IDPStateActive } @@ -382,6 +389,10 @@ func (wm *OIDCIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.Encryp ) } +func (wm *OIDCIDPWriteModel) GetProviderOptions() idp.Options { + return wm.Options +} + type JWTIDPWriteModel struct { eventstore.WriteModel @@ -483,7 +494,10 @@ func (wm *JWTIDPWriteModel) NewChanges( // reduceIDPConfigAddedEvent handles old idpConfig events func (wm *JWTIDPWriteModel) reduceIDPConfigAddedEvent(e *idpconfig.IDPConfigAddedEvent) { wm.Name = e.Name + wm.Options.IsCreationAllowed = true + wm.Options.IsLinkingAllowed = true wm.Options.IsAutoCreation = e.AutoRegister + wm.Options.IsAutoUpdate = false wm.State = domain.IDPStateActive } @@ -546,6 +560,10 @@ func (wm *JWTIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.Encrypt ) } +func (wm *JWTIDPWriteModel) GetProviderOptions() idp.Options { + return wm.Options +} + type AzureADIDPWriteModel struct { eventstore.WriteModel @@ -690,6 +708,10 @@ func (wm *AzureADIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.Enc ) } +func (wm *AzureADIDPWriteModel) GetProviderOptions() idp.Options { + return wm.Options +} + type GitHubIDPWriteModel struct { eventstore.WriteModel @@ -803,6 +825,10 @@ func (wm *GitHubIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.Encr ) } +func (wm *GitHubIDPWriteModel) GetProviderOptions() idp.Options { + return wm.Options +} + type GitHubEnterpriseIDPWriteModel struct { eventstore.WriteModel @@ -947,6 +973,10 @@ func (wm *GitHubEnterpriseIDPWriteModel) ToProvider(callbackURL string, idpAlg c ) } +func (wm *GitHubEnterpriseIDPWriteModel) GetProviderOptions() idp.Options { + return wm.Options +} + type GitLabIDPWriteModel struct { eventstore.WriteModel @@ -1061,6 +1091,10 @@ func (wm *GitLabIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.Encr ) } +func (wm *GitLabIDPWriteModel) GetProviderOptions() idp.Options { + return wm.Options +} + type GitLabSelfHostedIDPWriteModel struct { eventstore.WriteModel @@ -1185,6 +1219,10 @@ func (wm *GitLabSelfHostedIDPWriteModel) ToProvider(callbackURL string, idpAlg c ) } +func (wm *GitLabSelfHostedIDPWriteModel) GetProviderOptions() idp.Options { + return wm.Options +} + type GoogleIDPWriteModel struct { eventstore.WriteModel @@ -1306,6 +1344,10 @@ func (wm *GoogleIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.Encr ) } +func (wm *GoogleIDPWriteModel) GetProviderOptions() idp.Options { + return wm.Options +} + type LDAPIDPWriteModel struct { eventstore.WriteModel @@ -1541,6 +1583,10 @@ func (wm *LDAPIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.Encryp ), nil } +func (wm *LDAPIDPWriteModel) GetProviderOptions() idp.Options { + return wm.Options +} + type IDPRemoveWriteModel struct { eventstore.WriteModel @@ -1771,6 +1817,7 @@ func (wm *IDPTypeWriteModel) Query() *eventstore.SearchQueryBuilder { type IDP interface { eventstore.QueryReducer ToProvider(string, crypto.EncryptionAlgorithm) (providers.Provider, error) + GetProviderOptions() idp.Options } type AllIDPWriteModel struct { @@ -1863,3 +1910,7 @@ func (wm *AllIDPWriteModel) AppendEvents(events ...eventstore.Event) { func (wm *AllIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.EncryptionAlgorithm) (providers.Provider, error) { return wm.model.ToProvider(callbackURL, idpAlg) } + +func (wm *AllIDPWriteModel) GetProviderOptions() idp.Options { + return wm.model.GetProviderOptions() +} diff --git a/internal/command/user_human.go b/internal/command/user_human.go index 044066aba7..645d6bf9e3 100644 --- a/internal/command/user_human.go +++ b/internal/command/user_human.go @@ -475,7 +475,8 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai if err != nil { return nil, errors.ThrowPreconditionFailed(err, "COMMAND-Dfg3g", "Errors.Org.LoginPolicy.NotFound") } - if !loginPolicy.AllowRegister { + // check only if local registration is allowed, the idp will be checked separately + if !loginPolicy.AllowRegister && link == nil { return nil, errors.ThrowPreconditionFailed(err, "COMMAND-SAbr3", "Errors.Org.LoginPolicy.RegistrationNotAllowed") } userEvents, registeredHuman, err := c.registerHuman(ctx, orgID, human, link, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator) @@ -605,7 +606,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain. } for _, link := range links { - event, err := c.addUserIDPLink(ctx, userAgg, link) + event, err := c.addUserIDPLink(ctx, userAgg, link, false) if err != nil { return nil, nil, err } diff --git a/internal/command/user_human_test.go b/internal/command/user_human_test.go index d6964ac90a..4b02e6e447 100644 --- a/internal/command/user_human_test.go +++ b/internal/command/user_human_test.go @@ -20,6 +20,7 @@ import ( "github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/id" id_mock "github.com/zitadel/zitadel/internal/id/mock" + "github.com/zitadel/zitadel/internal/repository/idp" "github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/user" ) @@ -2000,6 +2001,31 @@ func TestCommandSide_ImportHuman(t *testing.T) { ), ), ), + expectFilter( + eventFromEventPusher( + org.NewIDPConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "idpID", + "name", + domain.IDPConfigTypeOIDC, + domain.IDPConfigStylingTypeUnspecified, + false, + ), + ), + eventFromEventPusher( + org.NewIDPOIDCConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "clientID", + "idpID", + "issuer", + "authEndpoint", + "tokenEndpoint", + nil, + domain.OIDCMappingFieldUnspecified, + domain.OIDCMappingFieldUnspecified, + ), + ), + ), expectFilter( eventFromEventPusher( org.NewIDPConfigAddedEvent(context.Background(), @@ -2102,6 +2128,146 @@ func TestCommandSide_ImportHuman(t *testing.T) { }, }, }, + { + name: "add human (with idp, creation not allowed), precondition error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + org.NewDomainPolicyAddedEvent(context.Background(), + &user.NewAggregate("user1", "org1").Aggregate, + true, + true, + true, + ), + ), + ), + expectFilter( + eventFromEventPusher( + org.NewPasswordComplexityPolicyAddedEvent(context.Background(), + &user.NewAggregate("user1", "org1").Aggregate, + 1, + false, + false, + false, + false, + ), + ), + ), + expectFilter( + eventFromEventPusher( + org.NewIDPConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "idpID", + "name", + domain.IDPConfigTypeOIDC, + domain.IDPConfigStylingTypeUnspecified, + false, + ), + ), + eventFromEventPusher( + org.NewIDPOIDCConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "clientID", + "idpID", + "issuer", + "authEndpoint", + "tokenEndpoint", + nil, + domain.OIDCMappingFieldUnspecified, + domain.OIDCMappingFieldUnspecified, + ), + ), + eventFromEventPusher( + func() eventstore.Command { + e, _ := org.NewOIDCIDPChangedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "config1", + []idp.OIDCIDPChanges{ + idp.ChangeOIDCOptions(idp.OptionChanges{IsCreationAllowed: gu.Ptr(false)}), + }, + ) + return e + }(), + ), + ), + expectFilter( + eventFromEventPusher( + org.NewIDPConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "idpID", + "name", + domain.IDPConfigTypeOIDC, + domain.IDPConfigStylingTypeUnspecified, + false, + ), + ), + eventFromEventPusher( + org.NewIDPOIDCConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "clientID", + "idpID", + "issuer", + "authEndpoint", + "tokenEndpoint", + nil, + domain.OIDCMappingFieldUnspecified, + domain.OIDCMappingFieldUnspecified, + ), + ), + eventFromEventPusher( + func() eventstore.Command { + e, _ := org.NewOIDCIDPChangedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "config1", + []idp.OIDCIDPChanges{ + idp.ChangeOIDCOptions(idp.OptionChanges{IsCreationAllowed: gu.Ptr(false)}), + }, + ) + return e + }(), + ), + eventFromEventPusher( + org.NewIdentityProviderAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "idpID", + domain.IdentityProviderTypeOrg, + ), + ), + ), + ), + idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), + userPasswordHasher: mockPasswordHasher("x"), + }, + args: args{ + ctx: context.Background(), + orgID: "org1", + human: &domain.Human{ + Username: "username", + Profile: &domain.Profile{ + FirstName: "firstname", + LastName: "lastname", + PreferredLanguage: language.English, + }, + Email: &domain.Email{ + EmailAddress: "email@test.ch", + IsEmailVerified: true, + }, + }, + links: []*domain.UserIDPLink{ + { + IDPConfigID: "idpID", + ExternalUserID: "externalID", + DisplayName: "name", + }, + }, + secretGenerator: GetMockSecretGenerator(t), + }, + res: res{ + err: caos_errs.IsPreconditionFailed, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -3333,6 +3499,31 @@ func TestCommandSide_RegisterHuman(t *testing.T) { ), ), ), + expectFilter( + eventFromEventPusher( + org.NewIDPConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "idpID", + "name", + domain.IDPConfigTypeOIDC, + domain.IDPConfigStylingTypeUnspecified, + false, + ), + ), + eventFromEventPusher( + org.NewIDPOIDCConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "clientID", + "idpID", + "issuer", + "authEndpoint", + "tokenEndpoint", + nil, + domain.OIDCMappingFieldUnspecified, + domain.OIDCMappingFieldUnspecified, + ), + ), + ), expectPush( []*repository.Event{ eventFromEventPusher( diff --git a/internal/command/user_idp_link.go b/internal/command/user_idp_link.go index 46dd96b409..bf7b6c72c7 100644 --- a/internal/command/user_idp_link.go +++ b/internal/command/user_idp_link.go @@ -11,7 +11,7 @@ import ( "github.com/zitadel/zitadel/internal/telemetry/tracing" ) -func (c *Commands) AddUserIDPLink(ctx context.Context, userID, resourceOwner string, link *domain.UserIDPLink) (_ *domain.ObjectDetails, err error) { +func (c *Commands) AddUserIDPLink(ctx context.Context, userID, resourceOwner string, link *AddLink) (_ *domain.ObjectDetails, err error) { if userID == "" { return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-03j8f", "Errors.IDMissing") } @@ -23,11 +23,7 @@ func (c *Commands) AddUserIDPLink(ctx context.Context, userID, resourceOwner str return nil, err } } - - linkWriteModel := NewUserIDPLinkWriteModel(userID, link.IDPConfigID, link.ExternalUserID, resourceOwner) - userAgg := UserAggregateFromWriteModel(&linkWriteModel.WriteModel) - - event, err := c.addUserIDPLink(ctx, userAgg, link) + event, err := addLink(ctx, c.eventstore.Filter, user.NewAggregate(userID, resourceOwner), link) if err != nil { return nil, err } @@ -60,7 +56,7 @@ func (c *Commands) BulkAddedUserIDPLinks(ctx context.Context, userID, resourceOw linkWriteModel := NewUserIDPLinkWriteModel(userID, link.IDPConfigID, link.ExternalUserID, resourceOwner) userAgg := UserAggregateFromWriteModel(&linkWriteModel.WriteModel) - events[i], err = c.addUserIDPLink(ctx, userAgg, link) + events[i], err = c.addUserIDPLink(ctx, userAgg, link, true) if err != nil { return err } @@ -70,18 +66,25 @@ func (c *Commands) BulkAddedUserIDPLinks(ctx context.Context, userID, resourceOw return err } -func (c *Commands) addUserIDPLink(ctx context.Context, human *eventstore.Aggregate, link *domain.UserIDPLink) (eventstore.Command, error) { +func (c *Commands) addUserIDPLink(ctx context.Context, human *eventstore.Aggregate, link *domain.UserIDPLink, linkToExistingUser bool) (eventstore.Command, error) { if link.AggregateID != "" && human.ID != link.AggregateID { return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-33M0g", "Errors.IDMissing") } if !link.IsValid() { return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-6m9Kd", "Errors.User.ExternalIDP.Invalid") } - - exists, err := ExistsIDP(ctx, c.eventstore.Filter, link.IDPConfigID, human.ResourceOwner) - if !exists || err != nil { + idpWriteModel, err := IDPProviderWriteModel(ctx, c.eventstore.Filter, link.IDPConfigID) + if err != nil { return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-39nfs", "Errors.IDPConfig.NotExisting") } + // IDP user will either be linked or created on a new user + // Therefore we need to either check if linking is allowed or creation: + if linkToExistingUser && !idpWriteModel.GetProviderOptions().IsLinkingAllowed { + return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-Sfee2", "Errors.ExternalIDP.LinkingNotAllowed") + } + if !linkToExistingUser && !idpWriteModel.GetProviderOptions().IsCreationAllowed { + return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-SJI3g", "Errors.ExternalIDP.CreationNotAllowed") + } return user.NewUserIDPLinkAddedEvent(ctx, human, link.IDPConfigID, link.DisplayName, link.ExternalUserID), nil } diff --git a/internal/command/user_idp_link_test.go b/internal/command/user_idp_link_test.go index 207a8e9df5..68d7611c57 100644 --- a/internal/command/user_idp_link_test.go +++ b/internal/command/user_idp_link_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/muhlemmer/gu" "github.com/stretchr/testify/assert" "golang.org/x/text/language" @@ -12,6 +13,7 @@ import ( "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/repository" "github.com/zitadel/zitadel/internal/eventstore/v1/models" + "github.com/zitadel/zitadel/internal/repository/idp" "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/user" @@ -28,7 +30,7 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) { links []*domain.UserIDPLink } type res struct { - err func(error) bool + err error } tests := []struct { name string @@ -55,7 +57,7 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) { resourceOwner: "org1", }, res: res{ - err: caos_errs.IsErrorInvalidArgument, + err: caos_errs.ThrowInvalidArgument(nil, "COMMAND-03j8f", "Errors.IDMissing"), }, }, { @@ -71,7 +73,7 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) { resourceOwner: "org1", }, res: res{ - err: caos_errs.IsErrorInvalidArgument, + err: caos_errs.ThrowInvalidArgument(nil, "COMMAND-Ek9s", "Errors.User.ExternalIDP.MinimumExternalIDPNeeded"), }, }, { @@ -113,7 +115,7 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) { }, }, res: res{ - err: caos_errs.IsErrorInvalidArgument, + err: caos_errs.ThrowInvalidArgument(nil, "COMMAND-33M0g", "Errors.IDMissing"), }, }, { @@ -155,7 +157,7 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) { }, }, res: res{ - err: caos_errs.IsErrorInvalidArgument, + err: caos_errs.ThrowInvalidArgument(nil, "COMMAND-6m9Kd", "Errors.User.ExternalIDP.Invalid"), }, }, { @@ -181,7 +183,6 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) { ), ), expectFilter(), - expectFilter(), ), }, args: args{ @@ -199,7 +200,112 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) { }, }, res: res{ - err: caos_errs.IsPreconditionFailed, + err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-as02jin", "Errors.IDPConfig.NotExisting"), + }, + }, + { + name: "linking not allowed, precondition error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + user.NewHumanAddedEvent( + context.Background(), + &user.NewAggregate("user1", "org1").Aggregate, + "userName", + "firstName", + "lastName", + "nickName", + "displayName", + language.German, + domain.GenderFemale, + "email@Address.ch", + false, + ), + ), + ), + expectFilter( + eventFromEventPusher( + org.NewIDPConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "config1", + "name", + domain.IDPConfigTypeOIDC, + domain.IDPConfigStylingTypeUnspecified, + true, + ), + ), + eventFromEventPusher( + org.NewIDPOIDCConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "clientID", + "config1", + "issuer", + "authEndpoint", + "tokenEndpoint", + nil, + domain.OIDCMappingFieldUnspecified, + domain.OIDCMappingFieldUnspecified, + ), + ), + ), + expectFilter( + eventFromEventPusher( + org.NewIDPConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "config1", + "name", + domain.IDPConfigTypeOIDC, + domain.IDPConfigStylingTypeUnspecified, + true, + ), + ), + eventFromEventPusher( + org.NewIDPOIDCConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "clientID", + "config1", + "issuer", + "authEndpoint", + "tokenEndpoint", + nil, + domain.OIDCMappingFieldUnspecified, + domain.OIDCMappingFieldUnspecified, + ), + ), + eventFromEventPusher( + func() eventstore.Command { + e, _ := org.NewOIDCIDPChangedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "config1", + []idp.OIDCIDPChanges{ + idp.ChangeOIDCOptions(idp.OptionChanges{IsLinkingAllowed: gu.Ptr(false)}), + }, + ) + return e + }(), + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + resourceOwner: "org1", + links: []*domain.UserIDPLink{ + { + ObjectRoot: models.ObjectRoot{ + AggregateID: "user1", + }, + IDPConfigID: "config1", + DisplayName: "name", + ExternalUserID: "externaluser1", + }, + }, + }, + res: res{ + err: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Sfee2", "Errors.ExternalIDP.LinkingNotAllowed"), }, }, { @@ -235,6 +341,44 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) { true, ), ), + eventFromEventPusher( + org.NewIDPOIDCConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "clientID", + "config1", + "issuer", + "authEndpoint", + "tokenEndpoint", + nil, + domain.OIDCMappingFieldUnspecified, + domain.OIDCMappingFieldUnspecified, + ), + ), + ), + expectFilter( + eventFromEventPusher( + org.NewIDPConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "config1", + "name", + domain.IDPConfigTypeOIDC, + domain.IDPConfigStylingTypeUnspecified, + true, + ), + ), + eventFromEventPusher( + org.NewIDPOIDCConfigAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + "clientID", + "config1", + "issuer", + "authEndpoint", + "tokenEndpoint", + nil, + domain.OIDCMappingFieldUnspecified, + domain.OIDCMappingFieldUnspecified, + ), + ), ), expectPush( []*repository.Event{ @@ -290,11 +434,10 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) { ), ), ), - expectFilter(), expectFilter( - eventFromEventPusher( + eventFromEventPusherWithInstanceID("instance1", instance.NewIDPConfigAddedEvent(context.Background(), - &org.NewAggregate("org1").Aggregate, + &instance.NewAggregate("instance1").Aggregate, "config1", "name", domain.IDPConfigTypeOIDC, @@ -302,6 +445,44 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) { true, ), ), + eventFromEventPusherWithInstanceID("instance1", + instance.NewIDPOIDCConfigAddedEvent(context.Background(), + &instance.NewAggregate("instance1").Aggregate, + "clientID", + "config1", + "issuer", + "authEndpoint", + "tokenEndpoint", + nil, + domain.OIDCMappingFieldUnspecified, + domain.OIDCMappingFieldUnspecified, + ), + ), + ), + expectFilter( + eventFromEventPusherWithInstanceID("instance1", + instance.NewIDPConfigAddedEvent(context.Background(), + &instance.NewAggregate("instance1").Aggregate, + "config1", + "name", + domain.IDPConfigTypeOIDC, + domain.IDPConfigStylingTypeUnspecified, + true, + ), + ), + eventFromEventPusherWithInstanceID("instance1", + instance.NewIDPOIDCConfigAddedEvent(context.Background(), + &instance.NewAggregate("instance1").Aggregate, + "clientID", + "config1", + "issuer", + "authEndpoint", + "tokenEndpoint", + nil, + domain.OIDCMappingFieldUnspecified, + domain.OIDCMappingFieldUnspecified, + ), + ), ), expectPush( []*repository.Event{ @@ -342,12 +523,7 @@ func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) { eventstore: tt.fields.eventstore, } err := r.BulkAddedUserIDPLinks(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.links) - if tt.res.err == nil { - assert.NoError(t, err) - } - if tt.res.err != nil && !tt.res.err(err) { - t.Errorf("got wrong err: %v ", err) - } + assert.ErrorIs(t, err, tt.res.err) }) } }