diff --git a/internal/admin/repository/eventsourcing/handler/iam_member.go b/internal/admin/repository/eventsourcing/handler/iam_member.go index f5c39d9b7..e3d16fef0 100644 --- a/internal/admin/repository/eventsourcing/handler/iam_member.go +++ b/internal/admin/repository/eventsourcing/handler/iam_member.go @@ -101,6 +101,8 @@ func (m *IamMember) processUser(event *models.Event) (err error) { m.fillUserData(member, user) } return m.view.PutIAMMembers(members, event.Sequence) + case usr_es_model.UserRemoved: + return m.view.DeleteIAMMembersByUserID(event.AggregateID, event.Sequence) default: return m.view.ProcessedIAMMemberSequence(event.Sequence) } diff --git a/internal/admin/repository/eventsourcing/handler/user.go b/internal/admin/repository/eventsourcing/handler/user.go index e60e18219..3d77f972e 100644 --- a/internal/admin/repository/eventsourcing/handler/user.go +++ b/internal/admin/repository/eventsourcing/handler/user.go @@ -106,7 +106,7 @@ func (u *User) ProcessUser(event *models.Event) (err error) { } err = u.fillLoginNames(user) case es_model.UserRemoved: - err = u.view.DeleteUser(event.AggregateID, event.Sequence) + return u.view.DeleteUser(event.AggregateID, event.Sequence) default: return u.view.ProcessedUserSequence(event.Sequence) } diff --git a/internal/admin/repository/eventsourcing/handler/user_external_idps.go b/internal/admin/repository/eventsourcing/handler/user_external_idps.go index 464b7874b..b9c74c1c8 100644 --- a/internal/admin/repository/eventsourcing/handler/user_external_idps.go +++ b/internal/admin/repository/eventsourcing/handler/user_external_idps.go @@ -69,6 +69,8 @@ func (m *ExternalIDP) processUser(event *models.Event) (err error) { return err } return m.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event.Sequence) + case model.UserRemoved: + return m.view.DeleteExternalIDPsByUserID(event.AggregateID, event.Sequence) default: return m.view.ProcessedExternalIDPSequence(event.Sequence) } diff --git a/internal/admin/repository/eventsourcing/view/external_idps.go b/internal/admin/repository/eventsourcing/view/external_idps.go index 103f4f76e..de919a589 100644 --- a/internal/admin/repository/eventsourcing/view/external_idps.go +++ b/internal/admin/repository/eventsourcing/view/external_idps.go @@ -56,6 +56,14 @@ func (v *View) DeleteExternalIDP(externalUserID, idpConfigID string, eventSequen return v.ProcessedExternalIDPSequence(eventSequence) } +func (v *View) DeleteExternalIDPsByUserID(userID string, eventSequence uint64) error { + err := view.DeleteExternalIDPsByUserID(v.Db, externalIDPTable, userID) + if err != nil { + return err + } + return v.ProcessedExternalIDPSequence(eventSequence) +} + func (v *View) GetLatestExternalIDPSequence() (*global_view.CurrentSequence, error) { return v.latestSequence(externalIDPTable) } diff --git a/internal/admin/repository/eventsourcing/view/iam_member.go b/internal/admin/repository/eventsourcing/view/iam_member.go index 99aa58ce3..2e27aa418 100644 --- a/internal/admin/repository/eventsourcing/view/iam_member.go +++ b/internal/admin/repository/eventsourcing/view/iam_member.go @@ -48,6 +48,14 @@ func (v *View) DeleteIAMMember(iamID, userID string, eventSequence uint64) error return v.ProcessedIAMMemberSequence(eventSequence) } +func (v *View) DeleteIAMMembersByUserID(userID string, eventSequence uint64) error { + err := view.DeleteIAMMembersByUserID(v.Db, iamMemberTable, userID) + if err != nil { + return err + } + return v.ProcessedIAMMemberSequence(eventSequence) +} + func (v *View) GetLatestIAMMemberSequence() (*global_view.CurrentSequence, error) { return v.latestSequence(iamMemberTable) } diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go index acd1b0ef2..f1ba57583 100644 --- a/internal/api/grpc/management/user.go +++ b/internal/api/grpc/management/user.go @@ -3,7 +3,6 @@ package management import ( "context" "github.com/caos/zitadel/internal/api/authz" - "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/pkg/grpc/management" "github.com/golang/protobuf/ptypes/empty" ) @@ -91,7 +90,8 @@ func (s *Server) UnlockUser(ctx context.Context, in *management.UserID) (*manage } func (s *Server) DeleteUser(ctx context.Context, in *management.UserID) (*empty.Empty, error) { - return nil, errors.ThrowUnimplemented(nil, "GRPC-as4fg", "Not implemented") + err := s.user.RemoveUser(ctx, in.Id) + return &empty.Empty{}, err } func (s *Server) UpdateUserMachine(ctx context.Context, in *management.UpdateMachineRequest) (*management.MachineResponse, error) { diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index b350cbf2d..8eca24fd3 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -732,6 +732,9 @@ func userByID(ctx context.Context, viewProvider userViewProvider, eventProvider return user_view_model.UserToModel(user), nil } } + if userCopy.State == int32(user_model.UserStateDeleted) { + return nil, errors.ThrowNotFound(nil, "EVENT-3F9so", "Errors.User.NotFound") + } return user_view_model.UserToModel(&userCopy), nil } diff --git a/internal/auth/repository/eventsourcing/eventstore/user.go b/internal/auth/repository/eventsourcing/eventstore/user.go index 616027bf2..e9d754f1e 100644 --- a/internal/auth/repository/eventsourcing/eventstore/user.go +++ b/internal/auth/repository/eventsourcing/eventstore/user.go @@ -346,6 +346,9 @@ func (repo *UserRepo) UserByID(ctx context.Context, id string) (*model.UserView, return usr_view_model.UserToModel(user), nil } } + if userCopy.State == int32(model.UserStateDeleted) { + return nil, errors.ThrowNotFound(nil, "EVENT-vZ8us", "Errors.User.NotFound") + } return usr_view_model.UserToModel(&userCopy), nil } diff --git a/internal/auth/repository/eventsourcing/handler/user.go b/internal/auth/repository/eventsourcing/handler/user.go index 4f2aa1cb6..1663a1b92 100644 --- a/internal/auth/repository/eventsourcing/handler/user.go +++ b/internal/auth/repository/eventsourcing/handler/user.go @@ -111,7 +111,7 @@ func (u *User) ProcessUser(event *models.Event) (err error) { } err = u.fillLoginNames(user) case es_model.UserRemoved: - err = u.view.DeleteUser(event.AggregateID, event.Sequence) + return u.view.DeleteUser(event.AggregateID, event.Sequence) default: return u.view.ProcessedUserSequence(event.Sequence) } diff --git a/internal/auth/repository/eventsourcing/handler/user_external_idps.go b/internal/auth/repository/eventsourcing/handler/user_external_idps.go index 3fac6f177..5de4130bc 100644 --- a/internal/auth/repository/eventsourcing/handler/user_external_idps.go +++ b/internal/auth/repository/eventsourcing/handler/user_external_idps.go @@ -68,6 +68,8 @@ func (m *ExternalIDP) processUser(event *models.Event) (err error) { return err } return m.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event.Sequence) + case model.UserRemoved: + return m.view.DeleteExternalIDPsByUserID(event.AggregateID, event.Sequence) default: return m.view.ProcessedExternalIDPSequence(event.Sequence) } diff --git a/internal/auth/repository/eventsourcing/handler/user_membership.go b/internal/auth/repository/eventsourcing/handler/user_membership.go index 379677884..9835b9fac 100644 --- a/internal/auth/repository/eventsourcing/handler/user_membership.go +++ b/internal/auth/repository/eventsourcing/handler/user_membership.go @@ -2,6 +2,7 @@ package handler import ( "context" + "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" org_model "github.com/caos/zitadel/internal/org/model" @@ -39,7 +40,7 @@ func (m *UserMembership) EventQuery() (*models.SearchQuery, error) { return nil, err } return es_models.NewSearchQuery(). - AggregateTypeFilter(iam_es_model.IAMAggregate, org_es_model.OrgAggregate, proj_es_model.ProjectAggregate). + AggregateTypeFilter(iam_es_model.IAMAggregate, org_es_model.OrgAggregate, proj_es_model.ProjectAggregate, model.UserAggregate). LatestSequenceFilter(sequence.CurrentSequence), nil } @@ -51,6 +52,8 @@ func (m *UserMembership) Reduce(event *models.Event) (err error) { err = m.processOrg(event) case proj_es_model.ProjectAggregate: err = m.processProject(event) + case model.UserAggregate: + err = m.processUser(event) } return err } @@ -210,6 +213,15 @@ func (m *UserMembership) updateProjectDisplayName(event *models.Event) error { return m.view.BulkPutUserMemberships(memberships, event.Sequence) } +func (m *UserMembership) processUser(event *models.Event) (err error) { + switch event.Type { + case model.UserRemoved: + return m.view.DeleteUserMembershipsByUserID(event.AggregateID, event.Sequence) + default: + return m.view.ProcessedUserMembershipSequence(event.Sequence) + } +} + func (m *UserMembership) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Ms3fj", "id", event.AggregateID).WithError(err).Warn("something went wrong in orgmember handler") return spooler.HandleError(event, err, m.view.GetLatestUserMembershipFailedEvent, m.view.ProcessedUserMembershipFailedEvent, m.view.ProcessedUserMembershipSequence, m.errorCountUntilSkip) diff --git a/internal/auth/repository/eventsourcing/view/external_idps.go b/internal/auth/repository/eventsourcing/view/external_idps.go index 70a382b3b..6050dd73d 100644 --- a/internal/auth/repository/eventsourcing/view/external_idps.go +++ b/internal/auth/repository/eventsourcing/view/external_idps.go @@ -56,6 +56,14 @@ func (v *View) DeleteExternalIDP(externalUserID, idpConfigID string, eventSequen return v.ProcessedExternalIDPSequence(eventSequence) } +func (v *View) DeleteExternalIDPsByUserID(userID string, eventSequence uint64) error { + err := view.DeleteExternalIDPsByUserID(v.Db, externalIDPTable, userID) + if err != nil { + return err + } + return v.ProcessedExternalIDPSequence(eventSequence) +} + func (v *View) GetLatestExternalIDPSequence() (*global_view.CurrentSequence, error) { return v.latestSequence(externalIDPTable) } diff --git a/internal/auth/repository/eventsourcing/view/user_membership.go b/internal/auth/repository/eventsourcing/view/user_membership.go index 30ce719b0..b3bc810c7 100644 --- a/internal/auth/repository/eventsourcing/view/user_membership.go +++ b/internal/auth/repository/eventsourcing/view/user_membership.go @@ -51,6 +51,14 @@ func (v *View) DeleteUserMembership(userID, aggregateID, objectID string, member return v.ProcessedUserMembershipSequence(eventSequence) } +func (v *View) DeleteUserMembershipsByUserID(userID string, eventSequence uint64) error { + err := view.DeleteUserMembershipsByUserID(v.Db, userMembershipTable, userID) + if err != nil { + return nil + } + return v.ProcessedUserMembershipSequence(eventSequence) +} + func (v *View) GetLatestUserMembershipSequence() (*repository.CurrentSequence, error) { return v.latestSequence(userMembershipTable) } diff --git a/internal/iam/repository/view/iam_member_view.go b/internal/iam/repository/view/iam_member_view.go index 3b2aaad40..711683b6c 100644 --- a/internal/iam/repository/view/iam_member_view.go +++ b/internal/iam/repository/view/iam_member_view.go @@ -70,3 +70,8 @@ func DeleteIAMMember(db *gorm.DB, table, orgID, userID string) error { delete := repository.PrepareDeleteByObject(table, member) return delete(db) } + +func DeleteIAMMembersByUserID(db *gorm.DB, table, userID string) error { + delete := repository.PrepareDeleteByKey(table, model.IAMMemberSearchKey(iam_model.IAMMemberSearchKeyUserID), userID) + return delete(db) +} diff --git a/internal/management/repository/eventsourcing/eventstore/project.go b/internal/management/repository/eventsourcing/eventstore/project.go index 096927bf3..d9938a6f6 100644 --- a/internal/management/repository/eventsourcing/eventstore/project.go +++ b/internal/management/repository/eventsourcing/eventstore/project.go @@ -62,7 +62,9 @@ func (repo *ProjectRepo) ProjectByID(ctx context.Context, id string) (*proj_mode return model.ProjectToModel(&viewProject), nil } } - + if viewProject.State == int32(proj_model.ProjectStateRemoved) { + return nil, caos_errs.ThrowNotFound(nil, "EVENT-3Mo0s", "Errors.Project.NotFound") + } return model.ProjectToModel(project), nil } diff --git a/internal/management/repository/eventsourcing/eventstore/user.go b/internal/management/repository/eventsourcing/eventstore/user.go index 6dc41cb56..82934740d 100644 --- a/internal/management/repository/eventsourcing/eventstore/user.go +++ b/internal/management/repository/eventsourcing/eventstore/user.go @@ -2,6 +2,10 @@ package eventstore import ( "context" + es_int "github.com/caos/zitadel/internal/eventstore" + es_models "github.com/caos/zitadel/internal/eventstore/models" + es_sdk "github.com/caos/zitadel/internal/eventstore/sdk" + usr_grant_event "github.com/caos/zitadel/internal/usergrant/repository/eventsourcing" "github.com/caos/logging" "github.com/caos/zitadel/internal/api/authz" @@ -19,12 +23,14 @@ import ( ) type UserRepo struct { - SearchLimit uint64 - UserEvents *usr_event.UserEventstore - PolicyEvents *policy_event.PolicyEventstore - OrgEvents *org_event.OrgEventstore - View *view.View - SystemDefaults systemdefaults.SystemDefaults + es_int.Eventstore + SearchLimit uint64 + UserEvents *usr_event.UserEventstore + PolicyEvents *policy_event.PolicyEventstore + OrgEvents *org_event.OrgEventstore + UserGrantEvents *usr_grant_event.UserGrantEventStore + View *view.View + SystemDefaults systemdefaults.SystemDefaults } func (repo *UserRepo) UserByID(ctx context.Context, id string) (*usr_model.UserView, error) { @@ -49,6 +55,9 @@ func (repo *UserRepo) UserByID(ctx context.Context, id string) (*usr_model.UserV return model.UserToModel(user), nil } } + if userCopy.State == int32(usr_model.UserStateDeleted) { + return nil, caos_errs.ThrowNotFound(nil, "EVENT-4Fm9s", "Errors.User.NotFound") + } return model.UserToModel(&userCopy), nil } @@ -96,6 +105,36 @@ func (repo *UserRepo) UnlockUser(ctx context.Context, id string) (*usr_model.Use return repo.UserEvents.UnlockUser(ctx, id) } +func (repo *UserRepo) RemoveUser(ctx context.Context, id string) error { + aggregates := make([]*es_models.Aggregate, 0) + orgPolicy, err := repo.OrgEvents.GetOrgIAMPolicy(ctx, authz.GetCtxData(ctx).OrgID) + if err != nil { + return err + } + user, agg, err := repo.UserEvents.PrepareRemoveUser(ctx, id, orgPolicy) + if err != nil { + return err + } + aggregates = append(aggregates, agg...) + + // remove user_grants + usergrants, err := repo.View.UserGrantsByUserID(id) + if err != nil { + return err + } + for _, grant := range usergrants { + _, aggs, err := repo.UserGrantEvents.PrepareRemoveUserGrant(ctx, grant.ID, true) + if err != nil { + return err + } + for _, agg := range aggs { + aggregates = append(aggregates, agg) + } + } + + return es_sdk.PushAggregates(ctx, repo.Eventstore.PushAggregates, user.AppendEvents, aggregates...) +} + func (repo *UserRepo) SearchUsers(ctx context.Context, request *usr_model.UserSearchRequest) (*usr_model.UserSearchResponse, error) { request.EnsureLimit(repo.SearchLimit) sequence, sequenceErr := repo.View.GetLatestUserSequence() @@ -107,7 +146,7 @@ func (repo *UserRepo) SearchUsers(ctx context.Context, request *usr_model.UserSe result := &usr_model.UserSearchResponse{ Offset: request.Offset, Limit: request.Limit, - TotalResult: uint64(count), + TotalResult: count, Result: model.UsersToModel(users), } if sequenceErr == nil { diff --git a/internal/management/repository/eventsourcing/handler/org_member.go b/internal/management/repository/eventsourcing/handler/org_member.go index 61e8816ca..c238c2363 100644 --- a/internal/management/repository/eventsourcing/handler/org_member.go +++ b/internal/management/repository/eventsourcing/handler/org_member.go @@ -104,6 +104,8 @@ func (m *OrgMember) processUser(event *models.Event) (err error) { m.fillUserData(member, user) } return m.view.PutOrgMembers(members, event.Sequence) + case usr_es_model.UserRemoved: + return m.view.DeleteOrgMembersByUserID(event.AggregateID, event.Sequence) default: return m.view.ProcessedOrgMemberSequence(event.Sequence) } diff --git a/internal/management/repository/eventsourcing/handler/user.go b/internal/management/repository/eventsourcing/handler/user.go index 3ba2b916c..46eb983cf 100644 --- a/internal/management/repository/eventsourcing/handler/user.go +++ b/internal/management/repository/eventsourcing/handler/user.go @@ -106,7 +106,7 @@ func (u *User) ProcessUser(event *models.Event) (err error) { } err = u.fillLoginNames(user) case es_model.UserRemoved: - err = u.view.DeleteUser(event.AggregateID, event.Sequence) + return u.view.DeleteUser(event.AggregateID, event.Sequence) default: return u.view.ProcessedUserSequence(event.Sequence) } diff --git a/internal/management/repository/eventsourcing/handler/user_external_idps.go b/internal/management/repository/eventsourcing/handler/user_external_idps.go index 7d38de915..cfcb8f488 100644 --- a/internal/management/repository/eventsourcing/handler/user_external_idps.go +++ b/internal/management/repository/eventsourcing/handler/user_external_idps.go @@ -69,6 +69,8 @@ func (m *ExternalIDP) processUser(event *models.Event) (err error) { return err } return m.view.DeleteExternalIDP(externalIDP.ExternalUserID, externalIDP.IDPConfigID, event.Sequence) + case model.UserRemoved: + return m.view.DeleteExternalIDPsByUserID(event.AggregateID, event.Sequence) default: return m.view.ProcessedExternalIDPSequence(event.Sequence) } diff --git a/internal/management/repository/eventsourcing/handler/user_membership.go b/internal/management/repository/eventsourcing/handler/user_membership.go index 7b3e240e1..763a446f1 100644 --- a/internal/management/repository/eventsourcing/handler/user_membership.go +++ b/internal/management/repository/eventsourcing/handler/user_membership.go @@ -7,6 +7,7 @@ import ( org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing" proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing" proj_es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" + "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" "github.com/caos/logging" @@ -38,7 +39,7 @@ func (m *UserMembership) EventQuery() (*models.SearchQuery, error) { return nil, err } return es_models.NewSearchQuery(). - AggregateTypeFilter(iam_es_model.IAMAggregate, org_es_model.OrgAggregate, proj_es_model.ProjectAggregate). + AggregateTypeFilter(iam_es_model.IAMAggregate, org_es_model.OrgAggregate, proj_es_model.ProjectAggregate, model.UserAggregate). LatestSequenceFilter(sequence.CurrentSequence), nil } @@ -50,6 +51,8 @@ func (m *UserMembership) Reduce(event *models.Event) (err error) { err = m.processOrg(event) case proj_es_model.ProjectAggregate: err = m.processProject(event) + case model.UserAggregate: + err = m.processUser(event) } return err } @@ -198,6 +201,15 @@ func (m *UserMembership) updateProjectDisplayName(event *models.Event) error { return m.view.BulkPutUserMemberships(memberships, event.Sequence) } +func (m *UserMembership) processUser(event *models.Event) (err error) { + switch event.Type { + case model.UserRemoved: + return m.view.DeleteUserMembershipsByUserID(event.AggregateID, event.Sequence) + default: + return m.view.ProcessedUserMembershipSequence(event.Sequence) + } +} + func (m *UserMembership) OnError(event *models.Event, err error) error { logging.LogWithFields("SPOOL-Ms3fj", "id", event.AggregateID).WithError(err).Warn("something went wrong in orgmember handler") return spooler.HandleError(event, err, m.view.GetLatestUserMembershipFailedEvent, m.view.ProcessedUserMembershipFailedEvent, m.view.ProcessedUserMembershipSequence, m.errorCountUntilSkip) diff --git a/internal/management/repository/eventsourcing/repository.go b/internal/management/repository/eventsourcing/repository.go index a87124efa..3c0dbf2bd 100644 --- a/internal/management/repository/eventsourcing/repository.go +++ b/internal/management/repository/eventsourcing/repository.go @@ -96,7 +96,7 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, roles []string) (*EsRe spooler: spool, OrgRepository: eventstore.OrgRepository{conf.SearchLimit, org, user, view, roles, systemDefaults}, ProjectRepo: eventstore.ProjectRepo{es, conf.SearchLimit, project, usergrant, user, iam, view, roles, systemDefaults.IamID}, - UserRepo: eventstore.UserRepo{conf.SearchLimit, user, policy, org, view, systemDefaults}, + UserRepo: eventstore.UserRepo{es, conf.SearchLimit, user, policy, org, usergrant, view, systemDefaults}, UserGrantRepo: eventstore.UserGrantRepo{conf.SearchLimit, usergrant, view}, PolicyRepo: eventstore.PolicyRepo{policy}, IAMRepository: eventstore.IAMRepository{iam}, diff --git a/internal/management/repository/eventsourcing/view/external_idps.go b/internal/management/repository/eventsourcing/view/external_idps.go index 650c8111d..e7f9f9fd0 100644 --- a/internal/management/repository/eventsourcing/view/external_idps.go +++ b/internal/management/repository/eventsourcing/view/external_idps.go @@ -56,6 +56,13 @@ func (v *View) DeleteExternalIDP(externalUserID, idpConfigID string, eventSequen return v.ProcessedExternalIDPSequence(eventSequence) } +func (v *View) DeleteExternalIDPsByUserID(userID string, eventSequence uint64) error { + err := view.DeleteExternalIDPsByUserID(v.Db, externalIDPTable, userID) + if err != nil { + return err + } + return v.ProcessedExternalIDPSequence(eventSequence) +} func (v *View) GetLatestExternalIDPSequence() (*global_view.CurrentSequence, error) { return v.latestSequence(externalIDPTable) } diff --git a/internal/management/repository/eventsourcing/view/org_member.go b/internal/management/repository/eventsourcing/view/org_member.go index ca99202b0..c4396d5a5 100644 --- a/internal/management/repository/eventsourcing/view/org_member.go +++ b/internal/management/repository/eventsourcing/view/org_member.go @@ -47,6 +47,14 @@ func (v *View) DeleteOrgMember(orgID, userID string, eventSequence uint64) error return v.ProcessedOrgMemberSequence(eventSequence) } +func (v *View) DeleteOrgMembersByUserID(userID string, eventSequence uint64) error { + err := view.DeleteOrgMembersByUserID(v.Db, orgMemberTable, userID) + if err != nil { + return nil + } + return v.ProcessedOrgMemberSequence(eventSequence) +} + func (v *View) GetLatestOrgMemberSequence() (*repository.CurrentSequence, error) { return v.latestSequence(orgMemberTable) } diff --git a/internal/management/repository/eventsourcing/view/user_membership.go b/internal/management/repository/eventsourcing/view/user_membership.go index 303623627..2a82ae89c 100644 --- a/internal/management/repository/eventsourcing/view/user_membership.go +++ b/internal/management/repository/eventsourcing/view/user_membership.go @@ -47,6 +47,14 @@ func (v *View) DeleteUserMembership(userID, aggregateID, objectID string, member return v.ProcessedUserMembershipSequence(eventSequence) } +func (v *View) DeleteUserMembershipsByUserID(userID string, eventSequence uint64) error { + err := view.DeleteUserMembershipsByUserID(v.Db, userMembershipTable, userID) + if err != nil { + return nil + } + return v.ProcessedUserMembershipSequence(eventSequence) +} + func (v *View) GetLatestUserMembershipSequence() (*repository.CurrentSequence, error) { return v.latestSequence(userMembershipTable) } diff --git a/internal/management/repository/user.go b/internal/management/repository/user.go index 8b75ee119..93380aca5 100644 --- a/internal/management/repository/user.go +++ b/internal/management/repository/user.go @@ -14,6 +14,7 @@ type UserRepository interface { ReactivateUser(ctx context.Context, id string) (*model.User, error) LockUser(ctx context.Context, id string) (*model.User, error) UnlockUser(ctx context.Context, id string) (*model.User, error) + RemoveUser(ctx context.Context, id string) error SearchUsers(ctx context.Context, request *model.UserSearchRequest) (*model.UserSearchResponse, error) GetUserByLoginNameGlobal(ctx context.Context, email string) (*model.UserView, error) diff --git a/internal/notification/repository/eventsourcing/handler/notify_user.go b/internal/notification/repository/eventsourcing/handler/notify_user.go index 7c72d9220..7309220ee 100644 --- a/internal/notification/repository/eventsourcing/handler/notify_user.go +++ b/internal/notification/repository/eventsourcing/handler/notify_user.go @@ -90,7 +90,7 @@ func (u *NotifyUser) ProcessUser(event *models.Event) (err error) { } u.fillLoginNames(user) case es_model.UserRemoved: - err = u.view.DeleteNotifyUser(event.AggregateID, event.Sequence) + return u.view.DeleteNotifyUser(event.AggregateID, event.Sequence) default: return u.view.ProcessedNotifyUserSequence(event.Sequence) } diff --git a/internal/org/repository/view/org_member_view.go b/internal/org/repository/view/org_member_view.go index 2d4401366..ef52864b5 100644 --- a/internal/org/repository/view/org_member_view.go +++ b/internal/org/repository/view/org_member_view.go @@ -70,3 +70,8 @@ func DeleteOrgMember(db *gorm.DB, table, orgID, userID string) error { delete := repository.PrepareDeleteByObject(table, member) return delete(db) } + +func DeleteOrgMembersByUserID(db *gorm.DB, table, userID string) error { + delete := repository.PrepareDeleteByKey(table, model.OrgMemberSearchKey(org_model.OrgMemberSearchKeyUserID), userID) + return delete(db) +} diff --git a/internal/project/repository/eventsourcing/eventstore.go b/internal/project/repository/eventsourcing/eventstore.go index 6ec960fd4..f7a344c32 100644 --- a/internal/project/repository/eventsourcing/eventstore.go +++ b/internal/project/repository/eventsourcing/eventstore.go @@ -67,6 +67,9 @@ func (es *ProjectEventstore) ProjectByID(ctx context.Context, id string) (*proj_ if err != nil && !(caos_errs.IsNotFound(err) && project.Sequence != 0) { return nil, err } + if project.State == int32(proj_model.ProjectStateRemoved) { + return nil, caos_errs.ThrowNotFound(nil, "EVENT-dG8ie", "Errors.Project.NotFound") + } es.projectCache.cacheProject(project) return model.ProjectToModel(project), nil } diff --git a/internal/project/repository/view/model/project.go b/internal/project/repository/view/model/project.go index e496dd334..97187f21f 100644 --- a/internal/project/repository/view/model/project.go +++ b/internal/project/repository/view/model/project.go @@ -73,6 +73,8 @@ func (p *ProjectView) AppendEvent(event *models.Event) (err error) { p.State = int32(model.ProjectStateInactive) case es_model.ProjectReactivated: p.State = int32(model.ProjectStateActive) + case es_model.ProjectRemoved: + p.State = int32(model.ProjectStateRemoved) } return err } diff --git a/internal/user/repository/eventsourcing/eventstore.go b/internal/user/repository/eventsourcing/eventstore.go index d15252775..74805a5fb 100644 --- a/internal/user/repository/eventsourcing/eventstore.go +++ b/internal/user/repository/eventsourcing/eventstore.go @@ -104,6 +104,9 @@ func (es *UserEventstore) UserByID(ctx context.Context, id string) (*usr_model.U if err != nil && caos_errs.IsNotFound(err) && user.Sequence == 0 { return nil, err } + if user.State == int32(usr_model.UserStateDeleted) { + return nil, caos_errs.ThrowNotFound(nil, "EVENT-6hsK9", "Errors.User.NotFound") + } es.userCache.cacheUser(user) return model.UserToModel(user), nil } @@ -323,6 +326,28 @@ func (es *UserEventstore) UnlockUser(ctx context.Context, id string) (*usr_model return model.UserToModel(repoUser), nil } +func (es *UserEventstore) PrepareRemoveUser(ctx context.Context, id string, orgIamPolicy *org_model.OrgIAMPolicy) (*model.User, []*es_models.Aggregate, error) { + user, err := es.UserByID(ctx, id) + if err != nil { + return nil, nil, err + } + + repoUser := model.UserFromModel(user) + aggregate, err := UserRemoveAggregate(ctx, es.AggregateCreator(), repoUser, orgIamPolicy.UserLoginMustBeDomain) + if err != nil { + return nil, nil, err + } + return repoUser, aggregate, nil +} + +func (es *UserEventstore) RemoveUser(ctx context.Context, id string, orgIamPolicy *org_model.OrgIAMPolicy) error { + repoUser, aggregate, err := es.PrepareRemoveUser(ctx, id, orgIamPolicy) + if err != nil { + return err + } + return es_sdk.PushAggregates(ctx, es.PushAggregates, repoUser.AppendEvents, aggregate...) +} + func (es *UserEventstore) UserChanges(ctx context.Context, id string, lastSequence uint64, limit uint64, sortAscending bool) (*usr_model.UserChanges, error) { query := ChangesQuery(id, lastSequence, limit, sortAscending) diff --git a/internal/user/repository/eventsourcing/model/user.go b/internal/user/repository/eventsourcing/model/user.go index 02127bbf7..d97ace768 100644 --- a/internal/user/repository/eventsourcing/model/user.go +++ b/internal/user/repository/eventsourcing/model/user.go @@ -94,6 +94,8 @@ func (u *User) AppendEvent(event *es_models.Event) error { u.appendLockedEvent() case UserUnlocked: u.appendUnlockedEvent() + case UserRemoved: + u.appendRemovedEvent() } if u.Human != nil { @@ -138,3 +140,7 @@ func (u *User) appendLockedEvent() { func (u *User) appendUnlockedEvent() { u.State = int32(model.UserStateActive) } + +func (u *User) appendRemovedEvent() { + u.State = int32(model.UserStateDeleted) +} diff --git a/internal/user/repository/eventsourcing/user.go b/internal/user/repository/eventsourcing/user.go index b8430fcdc..e2cb2a1b4 100644 --- a/internal/user/repository/eventsourcing/user.go +++ b/internal/user/repository/eventsourcing/user.go @@ -282,7 +282,7 @@ func releasedUniqueUserNameAggregate(ctx context.Context, aggCreator *es_models. return nil, err } - return aggregate.SetPrecondition(UserUserNameUniqueQuery(uniqueUserName), isEventValidation(aggregate, model.UserUserNameReserved)), nil + return aggregate.SetPrecondition(UserUserNameUniqueQuery(uniqueUserName), isEventValidation(aggregate, model.UserUserNameReleased)), nil } func changeUniqueUserNameAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, oldUsername, username string, userLoginMustBeDomain bool) ([]*es_models.Aggregate, error) { @@ -315,6 +315,25 @@ func UserUnlockAggregate(aggCreator *es_models.AggregateCreator, user *model.Use return userStateAggregate(aggCreator, user, model.UserUnlocked) } +func UserRemoveAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, user *model.User, userLoginMustBeDomain bool) ([]*es_models.Aggregate, error) { + agg, err := UserAggregate(ctx, aggCreator, user) + if err != nil { + return nil, err + } + agg, err = agg.AppendEvent(model.UserRemoved, nil) + if err != nil { + return nil, err + } + uniqueAgg, err := releasedUniqueUserNameAggregate(ctx, aggCreator, user.ResourceOwner, user.UserName, userLoginMustBeDomain) + if err != nil { + return nil, err + } + return []*es_models.Aggregate{ + agg, + uniqueAgg, + }, nil +} + func userStateAggregate(aggCreator *es_models.AggregateCreator, user *model.User, state es_models.EventType) func(ctx context.Context) (*es_models.Aggregate, error) { return func(ctx context.Context) (*es_models.Aggregate, error) { agg, err := UserAggregate(ctx, aggCreator, user) diff --git a/internal/user/repository/view/external_idp_view.go b/internal/user/repository/view/external_idp_view.go index cd1e5d5f7..ce01d3d45 100644 --- a/internal/user/repository/view/external_idp_view.go +++ b/internal/user/repository/view/external_idp_view.go @@ -115,3 +115,8 @@ func DeleteExternalIDP(db *gorm.DB, table, externalUserID, idpConfigID string) e ) return delete(db) } + +func DeleteExternalIDPsByUserID(db *gorm.DB, table, userID string) error { + delete := repository.PrepareDeleteByKey(table, model.ExternalIDPSearchKey(usr_model.ExternalIDPSearchKeyUserID), userID) + return delete(db) +} diff --git a/internal/user/repository/view/model/user.go b/internal/user/repository/view/model/user.go index 44aeecbe5..218a6eda7 100644 --- a/internal/user/repository/view/model/user.go +++ b/internal/user/repository/view/model/user.go @@ -206,6 +206,8 @@ func (u *UserView) AppendEvent(event *models.Event) (err error) { return err } err = u.setPasswordData(event) + case es_model.UserRemoved: + u.State = int32(model.UserStateDeleted) case es_model.UserPasswordChanged, es_model.HumanPasswordChanged: err = u.setPasswordData(event) diff --git a/internal/user/repository/view/usermembership_view.go b/internal/user/repository/view/usermembership_view.go index 303529010..089b36050 100644 --- a/internal/user/repository/view/usermembership_view.go +++ b/internal/user/repository/view/usermembership_view.go @@ -78,3 +78,8 @@ func DeleteUserMembership(db *gorm.DB, table, userID, aggregateID, objectID stri ) return delete(db) } + +func DeleteUserMembershipsByUserID(db *gorm.DB, table, userID string) error { + delete := repository.PrepareDeleteByKey(table, model.UserMembershipSearchKey(usr_model.UserMembershipSearchKeyUserID), userID) + return delete(db) +}