diff --git a/internal/command/identity_provider_model.go b/internal/command/identity_provider_model.go index 4375fa0041..4cc51f2e00 100644 --- a/internal/command/identity_provider_model.go +++ b/internal/command/identity_provider_model.go @@ -23,6 +23,8 @@ func (wm *IdentityProviderWriteModel) Reduce() error { wm.State = domain.IdentityProviderStateActive case *policy.IdentityProviderRemovedEvent: wm.State = domain.IdentityProviderStateRemoved + case *policy.LoginPolicyRemovedEvent: + wm.State = domain.IdentityProviderStateRemoved } } return wm.WriteModel.Reduce() diff --git a/internal/command/org_policy_login_identity_provider_model.go b/internal/command/org_policy_login_identity_provider_model.go index bc01bc2f40..8b74daf3e8 100644 --- a/internal/command/org_policy_login_identity_provider_model.go +++ b/internal/command/org_policy_login_identity_provider_model.go @@ -34,6 +34,8 @@ func (wm *OrgIdentityProviderWriteModel) AppendEvents(events ...eventstore.Event continue } wm.IdentityProviderWriteModel.AppendEvents(&e.IdentityProviderRemovedEvent) + case *org.LoginPolicyRemovedEvent: + wm.IdentityProviderWriteModel.AppendEvents(&e.LoginPolicyRemovedEvent) } } } @@ -50,6 +52,7 @@ func (wm *OrgIdentityProviderWriteModel) Query() *eventstore.SearchQueryBuilder AggregateIDs(wm.AggregateID). EventTypes( org.LoginPolicyIDPProviderAddedEventType, - org.LoginPolicyIDPProviderRemovedEventType). + org.LoginPolicyIDPProviderRemovedEventType, + org.LoginPolicyRemovedEventType). Builder() } diff --git a/internal/command/org_test.go b/internal/command/org_test.go index 57ab589920..690efefe47 100644 --- a/internal/command/org_test.go +++ b/internal/command/org_test.go @@ -86,6 +86,7 @@ func TestCommandSide_AddOrg(t *testing.T) { context.Background(), &user.NewAggregate("user1", "org1").Aggregate, "username1", + nil, true, ), ), diff --git a/internal/command/user.go b/internal/command/user.go index 07531a8005..4c6eaf3366 100644 --- a/internal/command/user.go +++ b/internal/command/user.go @@ -179,7 +179,7 @@ func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string, return nil, err } if !isUserStateExists(existingUser.UserState) { - return nil, caos_errs.ThrowNotFound(nil, "COMMAND-5M0od", "Errors.User.NotFound") + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-m9od", "Errors.User.NotFound") } orgIAMPolicy, err := c.getOrgIAMPolicy(ctx, existingUser.ResourceOwner) @@ -188,7 +188,7 @@ func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string, } var events []eventstore.EventPusher userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) - events = append(events, user.NewUserRemovedEvent(ctx, userAgg, existingUser.UserName, orgIAMPolicy.UserLoginMustBeDomain)) + events = append(events, user.NewUserRemovedEvent(ctx, userAgg, existingUser.UserName, existingUser.ExternalIDPs, orgIAMPolicy.UserLoginMustBeDomain)) for _, grantID := range cascadingGrantIDs { removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true) diff --git a/internal/command/user_grant_test.go b/internal/command/user_grant_test.go index f9f4f521a1..6afb871805 100644 --- a/internal/command/user_grant_test.go +++ b/internal/command/user_grant_test.go @@ -99,6 +99,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) { context.Background(), &user.NewAggregate("user1", "org1").Aggregate, "username1", + nil, true, ), ), @@ -670,6 +671,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) { context.Background(), &user.NewAggregate("user1", "org1").Aggregate, "username1", + nil, true, ), ), diff --git a/internal/command/user_human_externalidp_test.go b/internal/command/user_human_externalidp_test.go index 9438ed45cb..d08b30d229 100644 --- a/internal/command/user_human_externalidp_test.go +++ b/internal/command/user_human_externalidp_test.go @@ -340,6 +340,7 @@ func TestCommandSide_RemoveExternalIDP(t *testing.T) { user.NewUserRemovedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, "username", + nil, true, ), ), @@ -500,6 +501,7 @@ func TestCommandSide_ExternalLoginCheck(t *testing.T) { user.NewUserRemovedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, "username", + nil, true, ), ), diff --git a/internal/command/user_model.go b/internal/command/user_model.go index 1caae847f3..abb5e70913 100644 --- a/internal/command/user_model.go +++ b/internal/command/user_model.go @@ -13,8 +13,9 @@ import ( type UserWriteModel struct { eventstore.WriteModel - UserName string - UserState domain.UserState + UserName string + ExternalIDPs []*domain.ExternalIDP + UserState domain.UserState } func NewUserWriteModel(userID, resourceOwner string) *UserWriteModel { @@ -23,6 +24,7 @@ func NewUserWriteModel(userID, resourceOwner string) *UserWriteModel { AggregateID: userID, ResourceOwner: resourceOwner, }, + ExternalIDPs: make([]*domain.ExternalIDP, 0), } } @@ -39,6 +41,24 @@ func (wm *UserWriteModel) Reduce() error { wm.UserState = domain.UserStateInitial case *user.HumanInitializedCheckSucceededEvent: wm.UserState = domain.UserStateActive + case *user.HumanExternalIDPAddedEvent: + wm.ExternalIDPs = append(wm.ExternalIDPs, &domain.ExternalIDP{IDPConfigID: e.IDPConfigID, ExternalUserID: e.ExternalUserID}) + case *user.HumanExternalIDPRemovedEvent: + idx, _ := wm.ExternalIDPByID(e.IDPConfigID, e.ExternalUserID) + if idx < 0 { + continue + } + copy(wm.ExternalIDPs[idx:], wm.ExternalIDPs[idx+1:]) + wm.ExternalIDPs[len(wm.ExternalIDPs)-1] = nil + wm.ExternalIDPs = wm.ExternalIDPs[:len(wm.ExternalIDPs)-1] + case *user.HumanExternalIDPCascadeRemovedEvent: + idx, _ := wm.ExternalIDPByID(e.IDPConfigID, e.ExternalUserID) + if idx < 0 { + continue + } + copy(wm.ExternalIDPs[idx:], wm.ExternalIDPs[idx+1:]) + wm.ExternalIDPs[len(wm.ExternalIDPs)-1] = nil + wm.ExternalIDPs = wm.ExternalIDPs[:len(wm.ExternalIDPs)-1] case *user.MachineAddedEvent: wm.UserName = e.UserName wm.UserState = domain.UserStateActive @@ -76,6 +96,9 @@ func (wm *UserWriteModel) Query() *eventstore.SearchQueryBuilder { user.HumanAddedType, user.HumanRegisteredType, user.HumanInitializedCheckSucceededType, + user.HumanExternalIDPAddedType, + user.HumanExternalIDPRemovedType, + user.HumanExternalIDPCascadeRemovedType, user.MachineAddedEventType, user.UserUserNameChangedType, user.MachineChangedEventType, @@ -125,3 +148,12 @@ func hasUserState(check domain.UserState, states ...domain.UserState) bool { } return false } + +func (wm *UserWriteModel) ExternalIDPByID(idpID, externalUserID string) (idx int, idp *domain.ExternalIDP) { + for idx, idp = range wm.ExternalIDPs { + if idp.IDPConfigID == idpID && idp.ExternalUserID == externalUserID { + return idx, idp + } + } + return -1, nil +} diff --git a/internal/command/user_test.go b/internal/command/user_test.go index a7bec73bbf..cc20956087 100644 --- a/internal/command/user_test.go +++ b/internal/command/user_test.go @@ -1037,6 +1037,7 @@ func TestCommandSide_RemoveUser(t *testing.T) { user.NewUserRemovedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, "username", + nil, true, ), ), @@ -1056,6 +1057,71 @@ func TestCommandSide_RemoveUser(t *testing.T) { }, }, }, + { + name: "remove user with erxternal idp, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + user.NewHumanAddedEvent(context.Background(), + &user.NewAggregate("user1", "org1").Aggregate, + "username", + "firstname", + "lastname", + "nickname", + "displayname", + language.German, + domain.GenderUnspecified, + "email@test.ch", + true, + ), + ), + eventFromEventPusher( + user.NewHumanExternalIDPAddedEvent(context.Background(), + &user.NewAggregate("user1", "org1").Aggregate, + "idpConfigID", + "displayName", + "externalUserID", + ), + ), + ), + expectFilter(), + expectFilter( + eventFromEventPusher( + iam.NewOrgIAMPolicyAddedEvent(context.Background(), + &user.NewAggregate("user1", "org1").Aggregate, + true, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + user.NewUserRemovedEvent(context.Background(), + &user.NewAggregate("user1", "org1").Aggregate, + "username", + nil, + true, + ), + ), + }, + uniqueConstraintsFromEventConstraint(user.NewRemoveUsernameUniqueConstraint("username", "org1", true)), + uniqueConstraintsFromEventConstraint(user.NewRemoveExternalIDPUniqueConstraint("idpConfigID", "externalUserID")), + ), + ), + }, + args: args{ + ctx: context.Background(), + orgID: "org1", + userID: "user1", + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, { name: "remove user with user memberships, ok", fields: fields{ @@ -1092,6 +1158,7 @@ func TestCommandSide_RemoveUser(t *testing.T) { user.NewUserRemovedEvent(context.Background(), &user.NewAggregate("user1", "org1").Aggregate, "username", + nil, true, ), ), diff --git a/internal/repository/user/user.go b/internal/repository/user/user.go index 5fecdfe2a7..e6a7fd5146 100644 --- a/internal/repository/user/user.go +++ b/internal/repository/user/user.go @@ -5,6 +5,7 @@ import ( "encoding/json" "time" + "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/errors" @@ -162,6 +163,7 @@ type UserRemovedEvent struct { eventstore.BaseEvent `json:"-"` userName string + externalIDPs []*domain.ExternalIDP loginMustBeDomain bool } @@ -170,13 +172,21 @@ func (e *UserRemovedEvent) Data() interface{} { } func (e *UserRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { - return []*eventstore.EventUniqueConstraint{NewRemoveUsernameUniqueConstraint(e.userName, e.Aggregate().ResourceOwner, e.loginMustBeDomain)} + events := make([]*eventstore.EventUniqueConstraint, 0) + if e.userName != "" { + events = append(events, NewRemoveUsernameUniqueConstraint(e.userName, e.Aggregate().ResourceOwner, e.loginMustBeDomain)) + } + for _, idp := range e.externalIDPs { + events = append(events, NewRemoveExternalIDPUniqueConstraint(idp.IDPConfigID, idp.ExternalUserID)) + } + return events } func NewUserRemovedEvent( ctx context.Context, aggregate *eventstore.Aggregate, userName string, + externalIDPs []*domain.ExternalIDP, userLoginMustBeDomain bool, ) *UserRemovedEvent { return &UserRemovedEvent{ @@ -186,6 +196,7 @@ func NewUserRemovedEvent( UserRemovedType, ), userName: userName, + externalIDPs: externalIDPs, loginMustBeDomain: userLoginMustBeDomain, } } diff --git a/internal/ui/login/handler/external_login_handler.go b/internal/ui/login/handler/external_login_handler.go index acce955aba..472cf8dc68 100644 --- a/internal/ui/login/handler/external_login_handler.go +++ b/internal/ui/login/handler/external_login_handler.go @@ -275,6 +275,9 @@ func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *iam_model.OrgIAMPolicyV username = linkingUser.Email } } + if username == "" { + username = linkingUser.Email + } if orgIamPolicy.UserLoginMustBeDomain { splittedUsername := strings.Split(username, "@") @@ -310,6 +313,9 @@ func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *iam_model.OrgIAMPolicyV displayName = linkingUser.Email } } + if displayName == "" { + displayName = linkingUser.Email + } externalIDP := &domain.ExternalIDP{ IDPConfigID: idpConfig.IDPConfigID,