fix: Remove user with cascading memberships (#1811)

* fix: remove usermemberships on user remove

* fix: text user remove with memberships

* fix: translations

* Update internal/iam/repository/eventsourcing/model/types.go

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

* fix: uncomment tests

* fix: remove memberships if user removed

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi
2021-06-07 07:20:47 +02:00
committed by GitHub
parent 9ffc9d9330
commit 1143e3773e
52 changed files with 693 additions and 84 deletions

View File

@@ -2,9 +2,10 @@ package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"reflect"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
@@ -99,7 +100,8 @@ func (c *Commands) RemoveIAMMember(ctx context.Context, userID string) (*domain.
}
iamAgg := IAMAggregateFromWriteModel(&memberWriteModel.MemberWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewMemberRemovedEvent(ctx, iamAgg, userID))
removeEvent := c.removeIAMMember(ctx, iamAgg, userID, false)
pushedEvents, err := c.eventstore.PushEvents(ctx, removeEvent)
if err != nil {
return nil, err
}
@@ -111,6 +113,17 @@ func (c *Commands) RemoveIAMMember(ctx context.Context, userID string) (*domain.
return writeModelToObjectDetails(&memberWriteModel.MemberWriteModel.WriteModel), nil
}
func (c *Commands) removeIAMMember(ctx context.Context, iamAgg *eventstore.Aggregate, userID string, cascade bool) eventstore.EventPusher {
if cascade {
return iam_repo.NewMemberCascadeRemovedEvent(
ctx,
iamAgg,
userID)
} else {
return iam_repo.NewMemberRemovedEvent(ctx, iamAgg, userID)
}
}
func (c *Commands) iamMemberWriteModelByID(ctx context.Context, userID string) (member *IAMMemberWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()

View File

@@ -40,6 +40,11 @@ func (wm *IAMMemberWriteModel) AppendEvents(events ...eventstore.EventReader) {
continue
}
wm.MemberWriteModel.AppendEvents(&e.MemberRemovedEvent)
case *iam.MemberCascadeRemovedEvent:
if e.UserID != wm.MemberWriteModel.UserID {
continue
}
wm.MemberWriteModel.AppendEvents(&e.MemberCascadeRemovedEvent)
}
}
}
@@ -55,5 +60,6 @@ func (wm *IAMMemberWriteModel) Query() *eventstore.SearchQueryBuilder {
EventTypes(
iam.MemberAddedEventType,
iam.MemberChangedEventType,
iam.MemberRemovedEventType)
iam.MemberRemovedEventType,
iam.MemberCascadeRemovedEventType)
}

View File

@@ -2,9 +2,10 @@ package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"reflect"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
@@ -92,7 +93,8 @@ func (c *Commands) RemoveOrgMember(ctx context.Context, orgID, userID string) (*
}
orgAgg := OrgAggregateFromWriteModel(&m.MemberWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewMemberRemovedEvent(ctx, orgAgg, userID))
removeEvent := c.removeOrgMember(ctx, orgAgg, userID, false)
pushedEvents, err := c.eventstore.PushEvents(ctx, removeEvent)
if err != nil {
return nil, err
}
@@ -103,6 +105,17 @@ func (c *Commands) RemoveOrgMember(ctx context.Context, orgID, userID string) (*
return writeModelToObjectDetails(&m.WriteModel), nil
}
func (c *Commands) removeOrgMember(ctx context.Context, orgAgg *eventstore.Aggregate, userID string, cascade bool) eventstore.EventPusher {
if cascade {
return org.NewMemberCascadeRemovedEvent(
ctx,
orgAgg,
userID)
} else {
return org.NewMemberRemovedEvent(ctx, orgAgg, userID)
}
}
func (c *Commands) orgMemberWriteModelByID(ctx context.Context, orgID, userID string) (member *OrgMemberWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()

View File

@@ -39,6 +39,11 @@ func (wm *OrgMemberWriteModel) AppendEvents(events ...eventstore.EventReader) {
continue
}
wm.MemberWriteModel.AppendEvents(&e.MemberRemovedEvent)
case *org.MemberCascadeRemovedEvent:
if e.UserID != wm.MemberWriteModel.UserID {
continue
}
wm.MemberWriteModel.AppendEvents(&e.MemberCascadeRemovedEvent)
}
}
}
@@ -54,5 +59,6 @@ func (wm *OrgMemberWriteModel) Query() *eventstore.SearchQueryBuilder {
EventTypes(
org.MemberAddedEventType,
org.MemberChangedEventType,
org.MemberRemovedEventType)
org.MemberRemovedEventType,
org.MemberCascadeRemovedEventType)
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/project"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
@@ -87,7 +88,8 @@ func (c *Commands) RemoveProjectGrantMember(ctx context.Context, projectID, user
}
projectAgg := ProjectAggregateFromWriteModel(&m.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, project.NewProjectGrantMemberRemovedEvent(ctx, projectAgg, userID, grantID))
removeEvent := c.removeProjectGrantMember(ctx, projectAgg, userID, grantID, false)
pushedEvents, err := c.eventstore.PushEvents(ctx, removeEvent)
if err != nil {
return nil, err
}
@@ -98,6 +100,18 @@ func (c *Commands) RemoveProjectGrantMember(ctx context.Context, projectID, user
return writeModelToObjectDetails(&m.WriteModel), nil
}
func (c *Commands) removeProjectGrantMember(ctx context.Context, projectAgg *eventstore.Aggregate, userID, grantID string, cascade bool) eventstore.EventPusher {
if cascade {
return project.NewProjectGrantMemberCascadeRemovedEvent(
ctx,
projectAgg,
userID,
grantID)
} else {
return project.NewProjectGrantMemberRemovedEvent(ctx, projectAgg, userID, grantID)
}
}
func (c *Commands) projectGrantMemberWriteModelByID(ctx context.Context, projectID, userID, grantID string) (member *ProjectGrantMemberWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()

View File

@@ -44,6 +44,11 @@ func (wm *ProjectGrantMemberWriteModel) AppendEvents(events ...eventstore.EventR
continue
}
wm.WriteModel.AppendEvents(e)
case *project.GrantMemberCascadeRemovedEvent:
if e.UserID != wm.UserID || e.GrantID != wm.GrantID {
continue
}
wm.WriteModel.AppendEvents(e)
case *project.GrantRemovedEvent:
if e.GrantID != wm.GrantID {
continue
@@ -65,6 +70,8 @@ func (wm *ProjectGrantMemberWriteModel) Reduce() error {
wm.Roles = e.Roles
case *project.GrantMemberRemovedEvent:
wm.State = domain.MemberStateRemoved
case *project.GrantMemberCascadeRemovedEvent:
wm.State = domain.MemberStateRemoved
case *project.GrantRemovedEvent, *project.ProjectRemovedEvent:
wm.State = domain.MemberStateRemoved
}
@@ -79,6 +86,7 @@ func (wm *ProjectGrantMemberWriteModel) Query() *eventstore.SearchQueryBuilder {
project.GrantMemberAddedType,
project.GrantMemberChangedType,
project.GrantMemberRemovedType,
project.GrantMemberCascadeRemovedType,
project.GrantRemovedType,
project.ProjectRemovedType)
}

View File

@@ -2,9 +2,10 @@ package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"reflect"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
@@ -99,7 +100,8 @@ func (c *Commands) RemoveProjectMember(ctx context.Context, projectID, userID, r
}
projectAgg := ProjectAggregateFromWriteModel(&m.MemberWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, project.NewProjectMemberRemovedEvent(ctx, projectAgg, userID))
removeEvent := c.removeProjectMember(ctx, projectAgg, userID, false)
pushedEvents, err := c.eventstore.PushEvents(ctx, removeEvent)
if err != nil {
return nil, err
}
@@ -110,6 +112,17 @@ func (c *Commands) RemoveProjectMember(ctx context.Context, projectID, userID, r
return writeModelToObjectDetails(&m.WriteModel), nil
}
func (c *Commands) removeProjectMember(ctx context.Context, projectAgg *eventstore.Aggregate, userID string, cascade bool) eventstore.EventPusher {
if cascade {
return project.NewProjectMemberCascadeRemovedEvent(
ctx,
projectAgg,
userID)
} else {
return project.NewProjectMemberRemovedEvent(ctx, projectAgg, userID)
}
}
func (c *Commands) projectMemberWriteModelByID(ctx context.Context, projectID, userID, resourceOwner string) (member *ProjectMemberWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()

View File

@@ -39,6 +39,11 @@ func (wm *ProjectMemberWriteModel) AppendEvents(events ...eventstore.EventReader
continue
}
wm.MemberWriteModel.AppendEvents(&e.MemberRemovedEvent)
case *project.MemberCascadeRemovedEvent:
if e.UserID != wm.MemberWriteModel.UserID {
continue
}
wm.MemberWriteModel.AppendEvents(&e.MemberCascadeRemovedEvent)
}
}
}
@@ -53,5 +58,6 @@ func (wm *ProjectMemberWriteModel) Query() *eventstore.SearchQueryBuilder {
ResourceOwner(wm.ResourceOwner).
EventTypes(project.MemberAddedType,
project.MemberChangedType,
project.MemberRemovedType)
project.MemberRemovedType,
project.MemberCascadeRemovedType)
}

View File

@@ -101,6 +101,8 @@ func (rm *UniqueConstraintReadModel) Reduce() error {
rm.addUniqueConstraint(e.Aggregate().ID, e.GrantID+e.UserID, project.NewAddProjectGrantMemberUniqueConstraint(e.Aggregate().ID, e.UserID, e.GrantID))
case *project.GrantMemberRemovedEvent:
rm.removeUniqueConstraint(e.Aggregate().ID, e.GrantID+e.UserID, project.UniqueProjectGrantMemberType)
case *project.GrantMemberCascadeRemovedEvent:
rm.removeUniqueConstraint(e.Aggregate().ID, e.GrantID+e.UserID, project.UniqueProjectGrantMemberType)
case *project.RoleAddedEvent:
rm.addUniqueConstraint(e.Aggregate().ID, e.Key, project.NewAddProjectRoleUniqueConstraint(e.Key, e.Aggregate().ID))
case *project.RoleRemovedEvent:
@@ -159,14 +161,20 @@ func (rm *UniqueConstraintReadModel) Reduce() error {
rm.addUniqueConstraint(e.Aggregate().ID, e.UserID, member.NewAddMemberUniqueConstraint(e.Aggregate().ID, e.UserID))
case *iam.MemberRemovedEvent:
rm.removeUniqueConstraint(e.Aggregate().ID, e.UserID, member.UniqueMember)
case *iam.MemberCascadeRemovedEvent:
rm.removeUniqueConstraint(e.Aggregate().ID, e.UserID, member.UniqueMember)
case *org.MemberAddedEvent:
rm.addUniqueConstraint(e.Aggregate().ID, e.UserID, member.NewAddMemberUniqueConstraint(e.Aggregate().ID, e.UserID))
case *org.MemberRemovedEvent:
rm.removeUniqueConstraint(e.Aggregate().ID, e.UserID, member.UniqueMember)
case *org.MemberCascadeRemovedEvent:
rm.removeUniqueConstraint(e.Aggregate().ID, e.UserID, member.UniqueMember)
case *project.MemberAddedEvent:
rm.addUniqueConstraint(e.Aggregate().ID, e.UserID, member.NewAddMemberUniqueConstraint(e.Aggregate().ID, e.UserID))
case *project.MemberRemovedEvent:
rm.removeUniqueConstraint(e.Aggregate().ID, e.UserID, member.UniqueMember)
case *project.MemberCascadeRemovedEvent:
rm.removeUniqueConstraint(e.Aggregate().ID, e.UserID, member.UniqueMember)
}
}
return nil
@@ -204,6 +212,7 @@ func (rm *UniqueConstraintReadModel) Query() *eventstore.SearchQueryBuilder {
project.GrantRemovedType,
project.GrantMemberAddedType,
project.GrantMemberRemovedType,
project.GrantMemberCascadeRemovedType,
project.RoleAddedType,
project.RoleRemovedType,
user.UserV1AddedType,
@@ -222,10 +231,13 @@ func (rm *UniqueConstraintReadModel) Query() *eventstore.SearchQueryBuilder {
usergrant.UserGrantCascadeRemovedType,
iam.MemberAddedEventType,
iam.MemberRemovedEventType,
iam.MemberCascadeRemovedEventType,
org.MemberAddedEventType,
org.MemberRemovedEventType,
org.MemberCascadeRemovedEventType,
project.MemberAddedType,
project.MemberRemovedType,
project.MemberCascadeRemovedType,
)
}

View File

@@ -169,7 +169,7 @@ func (c *Commands) UnlockUser(ctx context.Context, userID, resourceOwner string)
return writeModelToObjectDetails(&existingUser.WriteModel), nil
}
func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string, cascadingGrantIDs ...string) (*domain.ObjectDetails, error) {
func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string, cascadingUserMemberships []*domain.UserMembership, cascadingGrantIDs ...string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
}
@@ -199,6 +199,14 @@ func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string,
events = append(events, removeEvent)
}
if len(cascadingUserMemberships) > 0 {
membershipEvents, err := c.removeUserMemberships(ctx, cascadingUserMemberships, true)
if err != nil {
return nil, err
}
events = append(events, membershipEvents...)
}
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
if err != nil {
return nil, err

View File

@@ -0,0 +1,36 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/project"
)
func (c *Commands) removeUserMemberships(ctx context.Context, memberships []*domain.UserMembership, cascade bool) (_ []eventstore.EventPusher, err error) {
events := make([]eventstore.EventPusher, 0)
for _, membership := range memberships {
switch membership.MemberType {
case domain.MemberTypeIam:
iamAgg := iam.NewAggregate()
removeEvent := c.removeIAMMember(ctx, &iamAgg.Aggregate, membership.UserID, true)
events = append(events, removeEvent)
case domain.MemberTypeOrganisation:
iamAgg := org.NewAggregate(membership.AggregateID, membership.ResourceOwner)
removeEvent := c.removeOrgMember(ctx, &iamAgg.Aggregate, membership.UserID, true)
events = append(events, removeEvent)
case domain.MemberTypeProject:
projectAgg := project.NewAggregate(membership.AggregateID, membership.ResourceOwner)
removeEvent := c.removeProjectMember(ctx, &projectAgg.Aggregate, membership.UserID, true)
events = append(events, removeEvent)
case domain.MemberTypeProjectGrant:
projectAgg := project.NewAggregate(membership.AggregateID, membership.ResourceOwner)
removeEvent := c.removeProjectGrantMember(ctx, &projectAgg.Aggregate, membership.UserID, membership.ObjectID, true)
events = append(events, removeEvent)
}
}
return events, nil
}

View File

@@ -14,6 +14,9 @@ import (
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/id"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/member"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/project"
"github.com/caos/zitadel/internal/repository/user"
)
@@ -914,9 +917,11 @@ func TestCommandSide_RemoveUser(t *testing.T) {
}
type (
args struct {
ctx context.Context
orgID string
userID string
ctx context.Context
orgID string
userID string
cascadeUserMemberships []*domain.UserMembership
cascadeUserGrants []string
}
)
type res struct {
@@ -1051,13 +1056,124 @@ func TestCommandSide_RemoveUser(t *testing.T) {
},
},
},
{
name: "remove user with user memberships, 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,
),
),
),
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",
true,
),
),
eventFromEventPusher(
iam.NewMemberCascadeRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"user1",
),
),
eventFromEventPusher(
org.NewMemberCascadeRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"user1",
),
),
eventFromEventPusher(
project.NewProjectMemberCascadeRemovedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
),
),
eventFromEventPusher(
project.NewProjectGrantMemberCascadeRemovedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
"grant1",
),
),
},
uniqueConstraintsFromEventConstraint(user.NewRemoveUsernameUniqueConstraint("username", "org1", true)),
uniqueConstraintsFromEventConstraint(member.NewRemoveMemberUniqueConstraint(domain.IAMID, "user1")),
uniqueConstraintsFromEventConstraint(member.NewRemoveMemberUniqueConstraint("org1", "user1")),
uniqueConstraintsFromEventConstraint(member.NewRemoveMemberUniqueConstraint("project1", "user1")),
uniqueConstraintsFromEventConstraint(project.NewRemoveProjectGrantMemberUniqueConstraint("project1", "user1", "grant1")),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
cascadeUserMemberships: []*domain.UserMembership{
{
MemberType: domain.MemberTypeIam,
UserID: "user1",
AggregateID: "IAM",
ResourceOwner: "org1",
},
{
MemberType: domain.MemberTypeOrganisation,
UserID: "user1",
ResourceOwner: "org1",
AggregateID: "org1",
},
{
MemberType: domain.MemberTypeProject,
UserID: "user1",
ResourceOwner: "org1",
AggregateID: "project1",
},
{
MemberType: domain.MemberTypeProjectGrant,
UserID: "user1",
ResourceOwner: "org1",
AggregateID: "project1",
ObjectID: "grant1",
},
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemoveUser(tt.args.ctx, tt.args.userID, tt.args.orgID)
got, err := r.RemoveUser(tt.args.ctx, tt.args.userID, tt.args.orgID, tt.args.cascadeUserMemberships, tt.args.cascadeUserGrants...)
if tt.res.err == nil {
assert.NoError(t, err)
}