mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 17:27:31 +00:00
feat: user profile requests in resource APIs (#10151)
# Which Problems Are Solved The commands for the resource based v2beta AuthorizationService API are added. Authorizations, previously knows as user grants, give a user in a specific organization and project context roles. The project can be owned or granted. The given roles can be used to restrict access within the projects applications. The commands for the resource based v2beta InteralPermissionService API are added. Administrators, previously knows as memberships, give a user in a specific organization and project context roles. The project can be owned or granted. The give roles give the user permissions to manage different resources in Zitadel. API definitions from https://github.com/zitadel/zitadel/issues/9165 are implemented. Contains endpoints for user metadata. # How the Problems Are Solved ### New Methods - CreateAuthorization - UpdateAuthorization - DeleteAuthorization - ActivateAuthorization - DeactivateAuthorization - ListAuthorizations - CreateAdministrator - UpdateAdministrator - DeleteAdministrator - ListAdministrators - SetUserMetadata to set metadata on a user - DeleteUserMetadata to delete metadata on a user - ListUserMetadata to query for metadata of a user ## Deprecated Methods ### v1.ManagementService - GetUserGrantByID - ListUserGrants - AddUserGrant - UpdateUserGrant - DeactivateUserGrant - ReactivateUserGrant - RemoveUserGrant - BulkRemoveUserGrant ### v1.AuthService - ListMyUserGrants - ListMyProjectPermissions # Additional Changes - Permission checks for metadata functionality on query and command side - correct existence checks for resources, for example you can only be an administrator on an existing project - combined all member tables to singular query for the administrators - add permission checks for command an query side functionality - combined functions on command side where necessary for easier maintainability # Additional Context Closes #9165 --------- Co-authored-by: Elio Bischof <elio@zitadel.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
@@ -690,7 +690,7 @@ func setupLoginClient(commands *Commands, validations *[]preparation.Validation,
|
||||
|
||||
func setupAdminMembers(commands *Commands, validations *[]preparation.Validation, instanceAgg *instance.Aggregate, orgAgg *org.Aggregate, userID string) {
|
||||
*validations = append(*validations,
|
||||
commands.AddOrgMemberCommand(orgAgg, userID, domain.RoleOrgOwner),
|
||||
commands.AddOrgMemberCommand(&AddOrgMember{orgAgg.ID, userID, []string{domain.RoleOrgOwner}}),
|
||||
commands.AddInstanceMemberCommand(instanceAgg, userID, domain.RoleIAMOwner),
|
||||
)
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
@@ -69,9 +69,19 @@ func IsInstanceMember(ctx context.Context, filter preparation.FilterToQueryReduc
|
||||
return isMember, nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddInstanceMember(ctx context.Context, userID string, roles ...string) (*domain.Member, error) {
|
||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.AddInstanceMemberCommand(instanceAgg, userID, roles...))
|
||||
type AddInstanceMember struct {
|
||||
InstanceID string
|
||||
UserID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (c *Commands) AddInstanceMember(ctx context.Context, member *AddInstanceMember) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(member.InstanceID)
|
||||
if err := c.checkPermissionUpdateInstanceMember(ctx, member.InstanceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//nolint:staticcheck
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.AddInstanceMemberCommand(instanceAgg, member.UserID, member.Roles...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -79,33 +89,56 @@ func (c *Commands) AddInstanceMember(ctx context.Context, userID string, roles .
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addedMember := NewInstanceMemberWriteModel(ctx, userID)
|
||||
addedMember := NewInstanceMemberWriteModel(member.InstanceID, member.UserID)
|
||||
err = AppendAndReduce(addedMember, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return memberWriteModelToMember(&addedMember.MemberWriteModel), nil
|
||||
return writeModelToObjectDetails(&addedMember.WriteModel), nil
|
||||
}
|
||||
|
||||
type ChangeInstanceMember struct {
|
||||
InstanceID string
|
||||
UserID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (i *ChangeInstanceMember) IsValid(zitadelRoles []authz.RoleMapping) error {
|
||||
if i.InstanceID == "" || i.UserID == "" || len(i.Roles) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "INSTANCE-LiaZi", "Errors.IAM.MemberInvalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(i.Roles, domain.IAMRolePrefix, zitadelRoles)) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "INSTANCE-3m9fs", "Errors.IAM.MemberInvalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangeInstanceMember updates an existing member
|
||||
func (c *Commands) ChangeInstanceMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
|
||||
if !member.IsIAMValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "INSTANCE-LiaZi", "Errors.IAM.MemberInvalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.IAMRolePrefix, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "INSTANCE-3m9fs", "Errors.IAM.MemberInvalid")
|
||||
}
|
||||
|
||||
existingMember, err := c.instanceMemberWriteModelByID(ctx, member.UserID)
|
||||
if err != nil {
|
||||
func (c *Commands) ChangeInstanceMember(ctx context.Context, member *ChangeInstanceMember) (*domain.ObjectDetails, error) {
|
||||
if err := member.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(existingMember.Roles, member.Roles) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "INSTANCE-LiaZi", "Errors.IAM.Member.RolesNotChanged")
|
||||
existingMember, err := c.instanceMemberWriteModelByID(ctx, member.InstanceID, member.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instanceAgg := InstanceAggregateFromWriteModel(&existingMember.MemberWriteModel.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, instance.NewMemberChangedEvent(ctx, instanceAgg, member.UserID, member.Roles...))
|
||||
if !existingMember.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "INSTANCE-D8JxR", "Errors.NotFound")
|
||||
}
|
||||
if err := c.checkPermissionUpdateInstanceMember(ctx, existingMember.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if slices.Compare(existingMember.Roles, member.Roles) == 0 {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx,
|
||||
instance.NewMemberChangedEvent(ctx,
|
||||
InstanceAggregateFromWriteModel(&existingMember.WriteModel),
|
||||
member.UserID,
|
||||
member.Roles...,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -114,34 +147,40 @@ func (c *Commands) ChangeInstanceMember(ctx context.Context, member *domain.Memb
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberWriteModelToMember(&existingMember.MemberWriteModel), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveInstanceMember(ctx context.Context, userID string) (*domain.ObjectDetails, error) {
|
||||
func (c *Commands) RemoveInstanceMember(ctx context.Context, instanceID, userID string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "INSTANCE-LiaZi", "Errors.IDMissing")
|
||||
}
|
||||
memberWriteModel, err := c.instanceMemberWriteModelByID(ctx, userID)
|
||||
if err != nil && !zerrors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
if zerrors.IsNotFound(err) {
|
||||
// empty response because we have no data that match the request
|
||||
return &domain.ObjectDetails{}, nil
|
||||
}
|
||||
|
||||
instanceAgg := InstanceAggregateFromWriteModel(&memberWriteModel.MemberWriteModel.WriteModel)
|
||||
removeEvent := c.removeInstanceMember(ctx, instanceAgg, userID, false)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, removeEvent)
|
||||
existingMember, err := c.instanceMemberWriteModelByID(ctx, instanceID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(memberWriteModel, pushedEvents...)
|
||||
if err := c.checkPermissionDeleteInstanceMember(ctx, instanceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existingMember.State.Exists() {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx,
|
||||
c.removeInstanceMember(ctx,
|
||||
InstanceAggregateFromWriteModel(&existingMember.WriteModel),
|
||||
userID,
|
||||
false,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingMember, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return writeModelToObjectDetails(&memberWriteModel.MemberWriteModel.WriteModel), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeInstanceMember(ctx context.Context, instanceAgg *eventstore.Aggregate, userID string, cascade bool) eventstore.Command {
|
||||
@@ -155,19 +194,15 @@ func (c *Commands) removeInstanceMember(ctx context.Context, instanceAgg *events
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) instanceMemberWriteModelByID(ctx context.Context, userID string) (member *InstanceMemberWriteModel, err error) {
|
||||
func (c *Commands) instanceMemberWriteModelByID(ctx context.Context, instanceID, userID string) (member *InstanceMemberWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel := NewInstanceMemberWriteModel(ctx, userID)
|
||||
writeModel := NewInstanceMemberWriteModel(instanceID, userID)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if writeModel.State == domain.MemberStateUnspecified || writeModel.State == domain.MemberStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "INSTANCE-D8JxR", "Errors.NotFound")
|
||||
}
|
||||
|
||||
return writeModel, nil
|
||||
}
|
||||
|
@@ -1,9 +1,6 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
)
|
||||
@@ -12,12 +9,12 @@ type InstanceMemberWriteModel struct {
|
||||
MemberWriteModel
|
||||
}
|
||||
|
||||
func NewInstanceMemberWriteModel(ctx context.Context, userID string) *InstanceMemberWriteModel {
|
||||
func NewInstanceMemberWriteModel(instanceID, userID string) *InstanceMemberWriteModel {
|
||||
return &InstanceMemberWriteModel{
|
||||
MemberWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: authz.GetInstance(ctx).InstanceID(),
|
||||
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
|
||||
AggregateID: instanceID,
|
||||
ResourceOwner: instanceID,
|
||||
},
|
||||
UserID: userID,
|
||||
},
|
||||
|
@@ -10,24 +10,22 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
func TestCommandSide_AddInstanceMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
roles []string
|
||||
member *AddInstanceMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.Member
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -39,28 +37,28 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &AddInstanceMember{},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
err: zerrors.IsInternal,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
roles: []string{"IAM_OWNER"},
|
||||
member: &AddInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -69,10 +67,10 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
@@ -80,9 +78,11 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
roles: []string{"IAM_OWNER"},
|
||||
member: &AddInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
@@ -91,8 +91,7 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "member already exists, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -118,6 +117,7 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
@@ -125,9 +125,11 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
roles: []string{"IAM_OWNER"},
|
||||
member: &AddInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
@@ -136,8 +138,7 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "member add uniqueconstraint err, already exists",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -163,6 +164,7 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
@@ -170,9 +172,11 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
userID: "user1",
|
||||
roles: []string{"IAM_OWNER"},
|
||||
member: &AddInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
@@ -181,8 +185,7 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "member add, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"INSTANCE",
|
||||
@@ -209,6 +212,7 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
@@ -216,30 +220,49 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
userID: "user1",
|
||||
roles: []string{"IAM_OWNER"},
|
||||
member: &AddInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
InstanceID: "INSTANCE",
|
||||
ResourceOwner: "INSTANCE",
|
||||
AggregateID: "INSTANCE",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member add, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &AddInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.AddInstanceMember(tt.args.ctx, tt.args.userID, tt.args.roles...)
|
||||
got, err := r.AddInstanceMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -247,24 +270,23 @@ func TestCommandSide_AddIAMMember(t *testing.T) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
func TestCommandSide_ChangeInstanceMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
instanceID string
|
||||
member *domain.Member
|
||||
member *ChangeInstanceMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.Member
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -276,13 +298,11 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{},
|
||||
member: &ChangeInstanceMember{},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -291,15 +311,14 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
member: &ChangeInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -309,10 +328,10 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "member not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
@@ -320,10 +339,10 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
member: &ChangeInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -333,8 +352,7 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "member not changed, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewMemberAddedEvent(context.Background(),
|
||||
@@ -345,6 +363,7 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleIAMOwner,
|
||||
@@ -352,21 +371,22 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
member: &ChangeInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewMemberAddedEvent(context.Background(),
|
||||
@@ -384,6 +404,7 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
@@ -394,32 +415,62 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER", "IAM_OWNER_VIEWER"},
|
||||
member: &ChangeInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER", "IAM_OWNER_VIEWER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "INSTANCE",
|
||||
AggregateID: "INSTANCE",
|
||||
InstanceID: "INSTANCE",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER", "IAM_OWNER_VIEWER"},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewMemberAddedEvent(context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
"user1",
|
||||
[]string{"IAM_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "IAM_OWNER",
|
||||
},
|
||||
{
|
||||
Role: "IAM_OWNER_VIEWER",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &ChangeInstanceMember{
|
||||
InstanceID: "INSTANCE",
|
||||
UserID: "user1",
|
||||
Roles: []string{"IAM_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.ChangeInstanceMember(tt.args.ctx, tt.args.member)
|
||||
got, err := r.ChangeInstanceMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -427,18 +478,18 @@ func TestCommandSide_ChangeIAMMember(t *testing.T) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_RemoveIAMMember(t *testing.T) {
|
||||
func TestCommandSide_RemoveInstanceMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
instanceID string
|
||||
userID string
|
||||
}
|
||||
@@ -455,13 +506,12 @@ func TestCommandSide_RemoveIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member userid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "",
|
||||
instanceID: "INSTANCE",
|
||||
userID: "",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -470,24 +520,25 @@ func TestCommandSide_RemoveIAMMember(t *testing.T) {
|
||||
{
|
||||
name: "member not existing, empty object details result",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
instanceID: "INSTANCE",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewMemberAddedEvent(context.Background(),
|
||||
@@ -504,10 +555,11 @@ func TestCommandSide_RemoveIAMMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
userID: "user1",
|
||||
instanceID: "INSTANCE",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
@@ -515,13 +567,38 @@ func TestCommandSide_RemoveIAMMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewMemberAddedEvent(context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
"user1",
|
||||
[]string{"IAM_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
instanceID: "INSTANCE",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveInstanceMember(tt.args.ctx, tt.args.userID)
|
||||
got, err := r.RemoveInstanceMember(context.Background(), tt.args.instanceID, tt.args.userID)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@@ -123,7 +123,7 @@ func (c *Commands) newOrgSetupCommands(ctx context.Context, orgID string, orgSet
|
||||
|
||||
func (c *orgSetupCommands) setupOrgAdmin(admin *OrgSetupAdmin, allowInitialMail bool) error {
|
||||
if admin.ID != "" {
|
||||
c.validations = append(c.validations, c.commands.AddOrgMemberCommand(c.aggregate, admin.ID, orgAdminRoles(admin.Roles)...))
|
||||
c.validations = append(c.validations, c.commands.AddOrgMemberCommand(&AddOrgMember{OrgID: c.aggregate.ID, UserID: admin.ID, Roles: orgAdminRoles(admin.Roles)}))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ func (c *orgSetupCommands) setupOrgAdmin(admin *OrgSetupAdmin, allowInitialMail
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.validations = append(c.validations, c.commands.AddOrgMemberCommand(c.aggregate, userID, orgAdminRoles(admin.Roles)...))
|
||||
c.validations = append(c.validations, c.commands.AddOrgMemberCommand(&AddOrgMember{OrgID: c.aggregate.ID, UserID: userID, Roles: orgAdminRoles(admin.Roles)}))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -359,16 +359,15 @@ func (c *Commands) addOrgWithIDAndMember(ctx context.Context, name, userID, reso
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
_, err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addedMember := NewOrgMemberWriteModel(addedOrg.AggregateID, userID)
|
||||
orgMemberEvent, err := c.addOrgMember(ctx, orgAgg, addedMember, domain.NewMember(orgAgg.ID, userID, domain.RoleOrgOwner))
|
||||
if err != nil {
|
||||
addMember := &AddOrgMember{OrgID: orgAgg.ID, UserID: userID, Roles: []string{domain.RoleOrgOwner}}
|
||||
if err := addMember.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events = append(events, orgMemberEvent)
|
||||
events = append(events, org.NewMemberAddedEvent(ctx, orgAgg, addMember.UserID, addMember.Roles...))
|
||||
if setOrgInactive {
|
||||
deactivateOrgEvent := org.NewOrgDeactivatedEvent(ctx, orgAgg)
|
||||
events = append(events, deactivateOrgEvent)
|
||||
|
@@ -2,8 +2,9 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
@@ -12,29 +13,22 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (c *Commands) AddOrgMemberCommand(a *org.Aggregate, userID string, roles ...string) preparation.Validation {
|
||||
func (c *Commands) AddOrgMemberCommand(member *AddOrgMember) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument")
|
||||
}
|
||||
if len(roles) == 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "V2-PfYhb", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
if len(domain.CheckForInvalidRoles(roles, domain.OrgRolePrefix, c.zitadelRoles)) > 0 && len(domain.CheckForInvalidRoles(roles, domain.RoleSelfManagementGlobal, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "Org-4N8es", "Errors.Org.MemberInvalid")
|
||||
if err := member.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) (_ []eventstore.Command, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if exists, err := ExistsUser(ctx, filter, userID, "", false); err != nil || !exists {
|
||||
if exists, err := ExistsUser(ctx, filter, member.UserID, "", false); err != nil || !exists {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "ORG-GoXOn", "Errors.User.NotFound")
|
||||
}
|
||||
if isMember, err := IsOrgMember(ctx, filter, a.ID, userID); err != nil || isMember {
|
||||
if isMember, err := IsOrgMember(ctx, filter, member.OrgID, member.UserID); err != nil || isMember {
|
||||
return nil, zerrors.ThrowAlreadyExists(err, "ORG-poWwe", "Errors.Org.Member.AlreadyExists")
|
||||
}
|
||||
return []eventstore.Command{org.NewMemberAddedEvent(ctx, &a.Aggregate, userID, roles...)}, nil
|
||||
return []eventstore.Command{org.NewMemberAddedEvent(ctx, &org.NewAggregate(member.OrgID).Aggregate, member.UserID, member.Roles...)}, nil
|
||||
},
|
||||
nil
|
||||
}
|
||||
@@ -76,12 +70,33 @@ func IsOrgMember(ctx context.Context, filter preparation.FilterToQueryReducer, o
|
||||
return isMember, nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddOrgMember(ctx context.Context, orgID, userID string, roles ...string) (_ *domain.Member, err error) {
|
||||
type AddOrgMember struct {
|
||||
OrgID string
|
||||
UserID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (m *AddOrgMember) IsValid(zitadelRoles []authz.RoleMapping) error {
|
||||
if m.UserID == "" || m.OrgID == "" || len(m.Roles) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(m.Roles, domain.OrgRolePrefix, zitadelRoles)) > 0 && len(domain.CheckForInvalidRoles(m.Roles, domain.RoleSelfManagementGlobal, zitadelRoles)) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "Org-4N8es", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddOrgMember(ctx context.Context, member *AddOrgMember) (_ *domain.ObjectDetails, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
orgAgg := org.NewAggregate(orgID)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.AddOrgMemberCommand(orgAgg, userID, roles...))
|
||||
if err := c.checkOrgExists(ctx, member.OrgID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.checkPermissionUpdateOrgMember(ctx, member.OrgID, member.OrgID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//nolint:staticcheck
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.AddOrgMemberCommand(member))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -89,51 +104,59 @@ func (c *Commands) AddOrgMember(ctx context.Context, orgID, userID string, roles
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addedMember := NewOrgMemberWriteModel(orgID, userID)
|
||||
addedMember := NewOrgMemberWriteModel(member.OrgID, member.UserID)
|
||||
err = AppendAndReduce(addedMember, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return memberWriteModelToMember(&addedMember.MemberWriteModel), nil
|
||||
return writeModelToObjectDetails(&addedMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) addOrgMember(ctx context.Context, orgAgg *eventstore.Aggregate, addedMember *OrgMemberWriteModel, member *domain.Member) (eventstore.Command, error) {
|
||||
if !member.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "Org-W8m4l", "Errors.Org.MemberInvalid")
|
||||
type ChangeOrgMember struct {
|
||||
OrgID string
|
||||
UserID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (c *ChangeOrgMember) IsValid(zitadelRoles []authz.RoleMapping) error {
|
||||
if c.OrgID == "" || c.UserID == "" || len(c.Roles) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "Org-LiaZi", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.OrgRolePrefix, c.zitadelRoles)) > 0 && len(domain.CheckForInvalidRoles(member.Roles, domain.RoleSelfManagementGlobal, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "Org-4N8es", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, addedMember)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if addedMember.State == domain.MemberStateActive {
|
||||
return nil, zerrors.ThrowAlreadyExists(nil, "Org-PtXi1", "Errors.Org.Member.AlreadyExists")
|
||||
if len(domain.CheckForInvalidRoles(c.Roles, domain.OrgRolePrefix, zitadelRoles)) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "IAM-m9fG8", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
|
||||
return org.NewMemberAddedEvent(ctx, orgAgg, member.UserID, member.Roles...), nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangeOrgMember updates an existing member
|
||||
func (c *Commands) ChangeOrgMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
|
||||
if !member.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "Org-LiaZi", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.OrgRolePrefix, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "IAM-m9fG8", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
|
||||
existingMember, err := c.orgMemberWriteModelByID(ctx, member.AggregateID, member.UserID)
|
||||
if err != nil {
|
||||
func (c *Commands) ChangeOrgMember(ctx context.Context, member *ChangeOrgMember) (*domain.ObjectDetails, error) {
|
||||
if err := member.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(existingMember.Roles, member.Roles) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "Org-LiaZi", "Errors.Org.Member.RolesNotChanged")
|
||||
existingMember, err := c.orgMemberWriteModelByID(ctx, member.OrgID, member.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
orgAgg := OrgAggregateFromWriteModel(&existingMember.MemberWriteModel.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, org.NewMemberChangedEvent(ctx, orgAgg, member.UserID, member.Roles...))
|
||||
if !existingMember.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "Org-D8JxR", "Errors.NotFound")
|
||||
}
|
||||
if err := c.checkPermissionUpdateOrgMember(ctx, existingMember.ResourceOwner, existingMember.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if slices.Compare(existingMember.Roles, member.Roles) == 0 {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx,
|
||||
org.NewMemberChangedEvent(ctx,
|
||||
OrgAggregateFromWriteModelWithCTX(ctx, &existingMember.WriteModel),
|
||||
member.UserID,
|
||||
member.Roles...,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -142,30 +165,39 @@ func (c *Commands) ChangeOrgMember(ctx context.Context, member *domain.Member) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberWriteModelToMember(&existingMember.MemberWriteModel), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveOrgMember(ctx context.Context, orgID, userID string) (*domain.ObjectDetails, error) {
|
||||
m, err := c.orgMemberWriteModelByID(ctx, orgID, userID)
|
||||
if err != nil && !zerrors.IsNotFound(err) {
|
||||
if orgID == "" || userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "Org-LiaZi", "Errors.Org.MemberInvalid")
|
||||
}
|
||||
existingMember, err := c.orgMemberWriteModelByID(ctx, orgID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if zerrors.IsNotFound(err) {
|
||||
// empty response because we have no data that match the request
|
||||
return &domain.ObjectDetails{}, nil
|
||||
if !existingMember.State.Exists() {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
if err := c.checkPermissionDeleteOrgMember(ctx, existingMember.ResourceOwner, existingMember.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgAgg := OrgAggregateFromWriteModel(&m.MemberWriteModel.WriteModel)
|
||||
removeEvent := c.removeOrgMember(ctx, orgAgg, userID, false)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, removeEvent)
|
||||
pushedEvents, err := c.eventstore.Push(ctx,
|
||||
c.removeOrgMember(ctx,
|
||||
OrgAggregateFromWriteModelWithCTX(ctx, &existingMember.WriteModel),
|
||||
userID,
|
||||
false,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(m, pushedEvents...)
|
||||
err = AppendAndReduce(existingMember, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&m.WriteModel), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeOrgMember(ctx context.Context, orgAgg *eventstore.Aggregate, userID string, cascade bool) eventstore.Command {
|
||||
@@ -189,9 +221,5 @@ func (c *Commands) orgMemberWriteModelByID(ctx context.Context, orgID, userID st
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if writeModel.State == domain.MemberStateUnspecified || writeModel.State == domain.MemberStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "Org-D8JxR", "Errors.NotFound")
|
||||
}
|
||||
|
||||
return writeModel, nil
|
||||
}
|
||||
|
@@ -11,24 +11,19 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestAddMember(t *testing.T) {
|
||||
type args struct {
|
||||
a *org.Aggregate
|
||||
userID string
|
||||
roles []string
|
||||
member *AddOrgMember
|
||||
zitadelRoles []authz.RoleMapping
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
agg := org.NewAggregate("test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -38,8 +33,10 @@ func TestAddMember(t *testing.T) {
|
||||
{
|
||||
name: "no user id",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "",
|
||||
member: &AddOrgMember{
|
||||
OrgID: "test",
|
||||
UserID: "",
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: zerrors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument"),
|
||||
@@ -48,19 +45,23 @@ func TestAddMember(t *testing.T) {
|
||||
{
|
||||
name: "no roles",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "12342",
|
||||
member: &AddOrgMember{
|
||||
OrgID: "test",
|
||||
UserID: "12342",
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: zerrors.ThrowInvalidArgument(nil, "V2-PfYhb", "Errors.Invalid.Argument"),
|
||||
ValidationErr: zerrors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TODO: invalid roles",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "123",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "test",
|
||||
UserID: "12342",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: zerrors.ThrowInvalidArgument(nil, "Org-4N8es", ""),
|
||||
@@ -69,9 +70,11 @@ func TestAddMember(t *testing.T) {
|
||||
{
|
||||
name: "user not exists",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "userID",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "test",
|
||||
UserID: "userID",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "ORG_OWNER",
|
||||
@@ -89,9 +92,11 @@ func TestAddMember(t *testing.T) {
|
||||
{
|
||||
name: "already member",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "userID",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "test",
|
||||
UserID: "userID",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "ORG_OWNER",
|
||||
@@ -129,9 +134,11 @@ func TestAddMember(t *testing.T) {
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "userID",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "test",
|
||||
UserID: "userID",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "ORG_OWNER",
|
||||
@@ -158,14 +165,14 @@ func TestAddMember(t *testing.T) {
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
org.NewMemberAddedEvent(ctx, &agg.Aggregate, "userID", "ORG_OWNER"),
|
||||
org.NewMemberAddedEvent(ctx, &org.NewAggregate("test").Aggregate, "userID", "ORG_OWNER"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, context.Background(), (&Commands{zitadelRoles: tt.args.zitadelRoles}).AddOrgMemberCommand(tt.args.a, tt.args.userID, tt.args.roles...), tt.args.filter, tt.want)
|
||||
AssertValidation(t, context.Background(), (&Commands{zitadelRoles: tt.args.zitadelRoles}).AddOrgMemberCommand(tt.args.member), tt.args.filter, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -287,17 +294,15 @@ func TestIsMember(t *testing.T) {
|
||||
|
||||
func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userID string
|
||||
orgID string
|
||||
roles []string
|
||||
member *AddOrgMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.Member
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -309,13 +314,22 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
member: &AddOrgMember{
|
||||
OrgID: "org1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -324,15 +338,24 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -341,10 +364,18 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleOrgOwner,
|
||||
@@ -352,10 +383,11 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
roles: []string{domain.RoleOrgOwner},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
@@ -364,8 +396,15 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "member already exists, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -391,6 +430,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleOrgOwner,
|
||||
@@ -398,10 +438,11 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
@@ -410,8 +451,15 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "member add uniqueconstraint err, already exists",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -437,6 +485,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleOrgOwner,
|
||||
@@ -444,10 +493,11 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
@@ -456,8 +506,15 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "member add, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -483,6 +540,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleOrgOwner,
|
||||
@@ -490,30 +548,58 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
roles: []string{"ORG_OWNER"},
|
||||
member: &AddOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
AggregateID: "org1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{domain.RoleOrgOwner},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member add, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"org",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleOrgOwner,
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &AddOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.AddOrgMember(tt.args.ctx, tt.args.orgID, tt.args.userID, tt.args.roles...)
|
||||
got, err := r.AddOrgMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -521,7 +607,7 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -529,15 +615,15 @@ func TestCommandSide_AddOrgMember(t *testing.T) {
|
||||
|
||||
func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
member *domain.Member
|
||||
member *ChangeOrgMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.Member
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -549,16 +635,12 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
},
|
||||
member: &ChangeOrgMember{
|
||||
OrgID: "org1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -568,16 +650,12 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
},
|
||||
member: &ChangeOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
@@ -589,10 +667,10 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
{
|
||||
name: "member not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleOrgOwner,
|
||||
@@ -600,11 +678,8 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
},
|
||||
member: &ChangeOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
@@ -614,10 +689,9 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member not changed, precondition error",
|
||||
name: "member not changed, no change",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewMemberAddedEvent(context.Background(),
|
||||
@@ -628,6 +702,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleOrgOwner,
|
||||
@@ -635,24 +710,22 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
},
|
||||
member: &ChangeOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewMemberAddedEvent(context.Background(),
|
||||
@@ -670,6 +743,7 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "ORG_OWNER",
|
||||
@@ -680,160 +754,210 @@ func TestCommandSide_ChangeOrgMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
},
|
||||
member: &ChangeOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER", "ORG_OWNER_VIEWER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
AggregateID: "org1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER", "ORG_OWNER_VIEWER"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
}
|
||||
got, err := r.ChangeOrgMember(tt.args.ctx, tt.args.member)
|
||||
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)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_RemoveOrgMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
projectID string
|
||||
userID string
|
||||
resourceOwner string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "invalid member projectid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "",
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid member userid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "project1",
|
||||
userID: "",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member not existing, empty object details result",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "project1",
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
[]string{"PROJECT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
project.NewProjectMemberRemovedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "project1",
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewMemberAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"user1",
|
||||
[]string{"ORG_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "ORG_OWNER",
|
||||
},
|
||||
{
|
||||
Role: "ORG_OWNER_VIEWER",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &ChangeOrgMember{
|
||||
OrgID: "org1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"ORG_OWNER", "ORG_OWNER_VIEWER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveProjectMember(tt.args.ctx, tt.args.projectID, tt.args.userID, tt.args.resourceOwner)
|
||||
got, err := r.ChangeOrgMember(context.Background(), tt.args.member)
|
||||
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)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_RemoveOrgMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
userID string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "invalid member orgID missing, error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid member userid missing, error",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member not existing, empty object details result",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewMemberAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"user1",
|
||||
[]string{"PROJECT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
org.NewMemberRemovedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"user1",
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewMemberAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
"user1",
|
||||
[]string{"PROJECT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveOrgMember(tt.args.ctx, tt.args.orgID, tt.args.userID)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
@@ -63,3 +65,7 @@ func (wm *OrgWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
func OrgAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||
return eventstore.AggregateFromWriteModel(wm, org.AggregateType, org.AggregateVersion)
|
||||
}
|
||||
|
||||
func OrgAggregateFromWriteModelWithCTX(ctx context.Context, wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||
return org.AggregateFromWriteModel(ctx, wm)
|
||||
}
|
||||
|
@@ -73,7 +73,7 @@ func TestAddOrg(t *testing.T) {
|
||||
|
||||
func TestCommandSide_AddOrg(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
zitadelRoles []authz.RoleMapping
|
||||
}
|
||||
@@ -97,9 +97,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
{
|
||||
name: "invalid org, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -113,9 +111,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
{
|
||||
name: "invalid org (spaces), error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -130,8 +126,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
{
|
||||
name: "user removed, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilterOrgDomainNotFound(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -174,8 +169,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
{
|
||||
name: "push failed unique constraint, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilterOrgDomainNotFound(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -193,7 +187,6 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilterOrgMemberNotFound(),
|
||||
expectPushFailed(zerrors.ThrowAlreadyExists(nil, "id", "internal"),
|
||||
org.NewOrgAddedEvent(
|
||||
context.Background(),
|
||||
@@ -242,8 +235,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
{
|
||||
name: "push failed, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilterOrgDomainNotFound(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -261,7 +253,6 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilterOrgMemberNotFound(),
|
||||
expectPushFailed(zerrors.ThrowInternal(nil, "id", "internal"),
|
||||
org.NewOrgAddedEvent(
|
||||
context.Background(),
|
||||
@@ -310,8 +301,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
{
|
||||
name: "add org, no error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilterOrgDomainNotFound(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -329,7 +319,6 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilterOrgMemberNotFound(),
|
||||
expectPush(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org2").Aggregate,
|
||||
@@ -381,8 +370,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
{
|
||||
name: "add org (remove spaces), no error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilterOrgDomainNotFound(),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -400,7 +388,6 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilterOrgMemberNotFound(),
|
||||
expectPush(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org2").Aggregate,
|
||||
@@ -453,7 +440,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
}
|
||||
@@ -473,7 +460,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
|
||||
|
||||
func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -492,9 +479,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "empty name, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -507,9 +492,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "empty name (spaces), invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -523,8 +506,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "org not found, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -540,8 +522,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "no change (spaces), error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -563,8 +544,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "push failed, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -593,8 +573,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "change org name verified, not primary",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -645,8 +624,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "change org name verified, with primary",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -705,8 +683,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
{
|
||||
name: "change org name case verified, with primary",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -754,7 +731,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
_, err := r.ChangeOrg(tt.args.ctx, tt.args.orgID, tt.args.name)
|
||||
if tt.res.err == nil {
|
||||
@@ -769,7 +746,7 @@ func TestCommandSide_ChangeOrg(t *testing.T) {
|
||||
|
||||
func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
iamDomain string
|
||||
}
|
||||
@@ -790,8 +767,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "org not found, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -806,8 +782,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "org already inactive, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -832,8 +807,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "push failed, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -860,8 +834,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "deactivate org",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -886,7 +859,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
_, err := r.DeactivateOrg(tt.args.ctx, tt.args.orgID)
|
||||
@@ -902,7 +875,7 @@ func TestCommandSide_DeactivateOrg(t *testing.T) {
|
||||
|
||||
func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
iamDomain string
|
||||
}
|
||||
@@ -923,8 +896,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "org not found, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -939,8 +911,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "org already active, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -961,8 +932,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "push failed, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -994,8 +964,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
{
|
||||
name: "reactivate org",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(),
|
||||
@@ -1024,7 +993,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
_, err := r.ReactivateOrg(tt.args.ctx, tt.args.orgID)
|
||||
@@ -1040,7 +1009,7 @@ func TestCommandSide_ReactivateOrg(t *testing.T) {
|
||||
|
||||
func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
}
|
||||
type args struct {
|
||||
@@ -1059,9 +1028,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
{
|
||||
name: "default org, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstance(context.Background(), &mockInstance{}),
|
||||
@@ -1074,8 +1041,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
{
|
||||
name: "zitadel org, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectAddedEvent(context.Background(),
|
||||
@@ -1100,8 +1066,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
{
|
||||
name: "org not found, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(), // zitadel project check
|
||||
expectFilter(),
|
||||
),
|
||||
@@ -1117,8 +1082,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
{
|
||||
name: "push failed, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(), // zitadel project check
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -1160,8 +1124,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
{
|
||||
name: "remove org",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(), // zitadel project check
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -1200,8 +1163,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
{
|
||||
name: "remove org with usernames and domains",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(), // zitadel project check
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
@@ -1291,7 +1253,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
_, err := r.RemoveOrg(tt.args.ctx, tt.args.orgID)
|
||||
|
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
"github.com/zitadel/zitadel/internal/v2/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@@ -13,6 +15,8 @@ import (
|
||||
|
||||
type PermissionCheck func(resourceOwner, aggregateID string) error
|
||||
|
||||
type UserGrantPermissionCheck func(projectID, projectGrantID string) PermissionCheck
|
||||
|
||||
func (c *Commands) newPermissionCheck(ctx context.Context, permission string, aggregateType eventstore.AggregateType) PermissionCheck {
|
||||
return func(resourceOwner, aggregateID string) error {
|
||||
if aggregateID == "" {
|
||||
@@ -93,3 +97,62 @@ func (c *Commands) checkPermissionUpdateApplication(ctx context.Context, resourc
|
||||
func (c *Commands) checkPermissionDeleteApp(ctx context.Context, resourceOwner, appID string) error {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionProjectAppDelete, project.AggregateType)(resourceOwner, appID)
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionUpdateInstanceMember(ctx context.Context, instanceID string) error {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionInstanceMemberWrite, instance.AggregateType)(instanceID, instanceID)
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionDeleteInstanceMember(ctx context.Context, instanceID string) error {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionInstanceMemberDelete, instance.AggregateType)(instanceID, instanceID)
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionUpdateOrgMember(ctx context.Context, instanceID, orgID string) error {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionOrgMemberWrite, org.AggregateType)(instanceID, orgID)
|
||||
}
|
||||
func (c *Commands) checkPermissionDeleteOrgMember(ctx context.Context, instanceID, orgID string) error {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionOrgMemberDelete, org.AggregateType)(instanceID, orgID)
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionUpdateProjectMember(ctx context.Context, resourceOwner, projectID string) error {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionProjectMemberWrite, project.AggregateType)(resourceOwner, projectID)
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionDeleteProjectMember(ctx context.Context, resourceOwner, projectID string) error {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionProjectMemberDelete, project.AggregateType)(resourceOwner, projectID)
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionUpdateProjectGrantMember(ctx context.Context, grantedOrgID, projectGrantID string) (err error) {
|
||||
// TODO: add permission check for project grant owners
|
||||
//if err := c.newPermissionCheck(ctx, domain.PermissionProjectGrantMemberWrite, project.AggregateType)(resourceOwner, projectGrantID); err != nil {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionProjectGrantMemberWrite, project.AggregateType)(grantedOrgID, projectGrantID)
|
||||
//}
|
||||
//return nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionDeleteProjectGrantMember(ctx context.Context, grantedOrgID, projectGrantID string) (err error) {
|
||||
// TODO: add permission check for project grant owners
|
||||
//if err := c.newPermissionCheck(ctx, domain.PermissionProjectGrantMemberDelete, project.AggregateType)(resourceOwner, projectGrantID); err != nil {
|
||||
return c.newPermissionCheck(ctx, domain.PermissionProjectGrantMemberDelete, project.AggregateType)(grantedOrgID, projectGrantID)
|
||||
//}
|
||||
//return nil
|
||||
}
|
||||
|
||||
func (c *Commands) newUserGrantPermissionCheck(ctx context.Context, permission string) UserGrantPermissionCheck {
|
||||
check := c.newPermissionCheck(ctx, permission, project.AggregateType)
|
||||
return func(projectID, projectGrantID string) PermissionCheck {
|
||||
return func(resourceOwner, _ string) error {
|
||||
if projectGrantID != "" {
|
||||
return check(resourceOwner, projectGrantID)
|
||||
}
|
||||
return check(resourceOwner, projectID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) NewPermissionCheckUserGrantWrite(ctx context.Context) UserGrantPermissionCheck {
|
||||
return c.newUserGrantPermissionCheck(ctx, domain.PermissionUserGrantWrite)
|
||||
}
|
||||
|
||||
func (c *Commands) NewPermissionCheckUserGrantDelete(ctx context.Context) UserGrantPermissionCheck {
|
||||
return c.newUserGrantPermissionCheck(ctx, domain.PermissionUserGrantDelete)
|
||||
}
|
||||
|
@@ -346,7 +346,7 @@ func (c *Commands) RemoveProject(ctx context.Context, projectID, resourceOwner s
|
||||
}
|
||||
|
||||
for _, grantID := range cascadingUserGrantIDs {
|
||||
event, _, err := c.removeUserGrant(ctx, grantID, "", true)
|
||||
event, _, err := c.removeUserGrant(ctx, grantID, "", true, false, nil)
|
||||
if err != nil {
|
||||
logging.WithFields("id", "COMMAND-b8Djf", "usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant")
|
||||
continue
|
||||
@@ -398,7 +398,7 @@ func (c *Commands) DeleteProject(ctx context.Context, id, resourceOwner string,
|
||||
),
|
||||
}
|
||||
for _, grantID := range cascadingUserGrantIDs {
|
||||
event, _, err := c.removeUserGrant(ctx, grantID, "", true)
|
||||
event, _, err := c.removeUserGrant(ctx, grantID, "", true, false, nil)
|
||||
if err != nil {
|
||||
logging.WithFields("id", "COMMAND-b8Djf", "usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant")
|
||||
continue
|
||||
|
@@ -234,6 +234,17 @@ func (c *Commands) DeactivateProjectGrant(ctx context.Context, projectID, grantI
|
||||
return writeModelToObjectDetails(&existingGrant.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkProjectGrantExists(ctx context.Context, grantID, grantedOrgID, projectID, resourceOwner string) (string, string, error) {
|
||||
existingGrant, err := c.projectGrantWriteModelByID(ctx, grantID, grantedOrgID, projectID, resourceOwner)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if !existingGrant.State.Exists() {
|
||||
return "", "", zerrors.ThrowNotFound(nil, "PROJECT-D8JxR", "Errors.Project.Grant.NotFound")
|
||||
}
|
||||
return existingGrant.GrantedOrgID, existingGrant.ResourceOwner, nil
|
||||
}
|
||||
|
||||
func (c *Commands) ReactivateProjectGrant(ctx context.Context, projectID, grantID, grantedOrgID, resourceOwner string) (details *domain.ObjectDetails, err error) {
|
||||
if (grantID == "" && grantedOrgID == "") || projectID == "" {
|
||||
return details, zerrors.ThrowInvalidArgument(nil, "PROJECT-p0s4V", "Errors.IDMissing")
|
||||
@@ -302,16 +313,17 @@ func (c *Commands) RemoveProjectGrant(ctx context.Context, projectID, grantID, r
|
||||
ProjectAggregateFromWriteModelWithCTX(ctx, &existingGrant.WriteModel),
|
||||
existingGrant.GrantID,
|
||||
existingGrant.GrantedOrgID,
|
||||
),
|
||||
)
|
||||
))
|
||||
|
||||
for _, userGrantID := range cascadeUserGrantIDs {
|
||||
event, _, err := c.removeUserGrant(ctx, userGrantID, "", true)
|
||||
event, _, err := c.removeUserGrant(ctx, userGrantID, "", true, true, nil)
|
||||
if err != nil {
|
||||
logging.WithFields("id", "COMMAND-3m8sG", "usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant")
|
||||
continue
|
||||
}
|
||||
events = append(events, event)
|
||||
if event != nil {
|
||||
events = append(events, event)
|
||||
}
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
||||
if err != nil {
|
||||
@@ -348,12 +360,14 @@ func (c *Commands) DeleteProjectGrant(ctx context.Context, projectID, grantID, g
|
||||
)
|
||||
|
||||
for _, userGrantID := range cascadeUserGrantIDs {
|
||||
event, _, err := c.removeUserGrant(ctx, userGrantID, "", true)
|
||||
event, _, err := c.removeUserGrant(ctx, userGrantID, "", true, true, nil)
|
||||
if err != nil {
|
||||
logging.WithFields("id", "COMMAND-3m8sG", "usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant")
|
||||
continue
|
||||
}
|
||||
events = append(events, event)
|
||||
if event != nil {
|
||||
events = append(events, event)
|
||||
}
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
||||
if err != nil {
|
||||
|
@@ -2,8 +2,9 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
@@ -11,25 +12,66 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (c *Commands) AddProjectGrantMember(ctx context.Context, member *domain.ProjectGrantMember) (_ *domain.ProjectGrantMember, err error) {
|
||||
type AddProjectGrantMember struct {
|
||||
ResourceOwner string
|
||||
UserID string
|
||||
GrantID string
|
||||
ProjectID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (i *AddProjectGrantMember) IsValid(zitadelRoles []authz.RoleMapping) error {
|
||||
if i.ProjectID == "" || i.GrantID == "" || i.UserID == "" || len(i.Roles) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-8fi7G", "Errors.Project.Grant.Member.Invalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(i.Roles, domain.ProjectGrantRolePrefix, zitadelRoles)) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-m9gKK", "Errors.Project.Grant.Member.Invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddProjectGrantMember(ctx context.Context, member *AddProjectGrantMember) (_ *domain.ObjectDetails, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if !member.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-8fi7G", "Errors.Project.Grant.Member.Invalid")
|
||||
if err := member.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.ProjectGrantRolePrefix, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-m9gKK", "Errors.Project.Grant.Member.Invalid")
|
||||
}
|
||||
err = c.checkUserExists(ctx, member.UserID, "")
|
||||
_, err = c.checkUserExists(ctx, member.UserID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addedMember := NewProjectGrantMemberWriteModel(member.AggregateID, member.UserID, member.GrantID)
|
||||
projectAgg := ProjectAggregateFromWriteModel(&addedMember.WriteModel)
|
||||
grantedOrgID, projectGrantResourceOwner, err := c.checkProjectGrantExists(ctx, member.GrantID, "", member.ProjectID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if member.ResourceOwner == "" {
|
||||
member.ResourceOwner = projectGrantResourceOwner
|
||||
}
|
||||
addedMember, err := c.projectGrantMemberWriteModelByID(ctx, member.ProjectID, member.UserID, member.GrantID, member.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: change e2e tests to use correct resourceowner, wrong resource owner is corrected through aggregate
|
||||
// error if provided resourceowner is not equal to the resourceowner of the project grant
|
||||
//if projectGrantResourceOwner != addedMember.ResourceOwner {
|
||||
// return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-0l10S9OmZV", "Errors.Project.Grant.Invalid")
|
||||
//}
|
||||
if addedMember.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "PROJECT-37fug", "Errors.AlreadyExists")
|
||||
}
|
||||
if err := c.checkPermissionUpdateProjectGrantMember(ctx, grantedOrgID, addedMember.GrantID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(
|
||||
ctx,
|
||||
project.NewProjectGrantMemberAddedEvent(ctx, projectAgg, member.UserID, member.GrantID, member.Roles...))
|
||||
project.NewProjectGrantMemberAddedEvent(ctx,
|
||||
ProjectAggregateFromWriteModelWithCTX(ctx, &addedMember.WriteModel),
|
||||
member.UserID,
|
||||
member.GrantID,
|
||||
member.Roles...,
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -38,30 +80,58 @@ func (c *Commands) AddProjectGrantMember(ctx context.Context, member *domain.Pro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberWriteModelToProjectGrantMember(addedMember), nil
|
||||
return writeModelToObjectDetails(&addedMember.WriteModel), nil
|
||||
}
|
||||
|
||||
type ChangeProjectGrantMember struct {
|
||||
UserID string
|
||||
GrantID string
|
||||
ProjectID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (i *ChangeProjectGrantMember) IsValid(zitadelRoles []authz.RoleMapping) error {
|
||||
if i.ProjectID == "" || i.GrantID == "" || i.UserID == "" || len(i.Roles) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-109fs", "Errors.Project.Grant.Member.Invalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(i.Roles, domain.ProjectGrantRolePrefix, zitadelRoles)) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-m0sDf", "Errors.Project.Grant.Member.Invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangeProjectGrantMember updates an existing member
|
||||
func (c *Commands) ChangeProjectGrantMember(ctx context.Context, member *domain.ProjectGrantMember) (*domain.ProjectGrantMember, error) {
|
||||
if !member.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-109fs", "Errors.Project.Member.Invalid")
|
||||
func (c *Commands) ChangeProjectGrantMember(ctx context.Context, member *ChangeProjectGrantMember) (*domain.ObjectDetails, error) {
|
||||
if err := member.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.ProjectGrantRolePrefix, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-m0sDf", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
|
||||
existingMember, err := c.projectGrantMemberWriteModelByID(ctx, member.AggregateID, member.UserID, member.GrantID)
|
||||
existingGrant, err := c.projectGrantWriteModelByID(ctx, member.GrantID, "", member.ProjectID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(existingMember.Roles, member.Roles) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-2n8vx", "Errors.Project.Member.RolesNotChanged")
|
||||
existingMember, err := c.projectGrantMemberWriteModelByID(ctx, member.ProjectID, member.UserID, member.GrantID, existingGrant.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
projectAgg := ProjectAggregateFromWriteModel(&existingMember.WriteModel)
|
||||
if !existingMember.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "PROJECT-37fug", "Errors.NotFound")
|
||||
}
|
||||
|
||||
if err := c.checkPermissionUpdateProjectGrantMember(ctx, existingGrant.GrantedOrgID, existingMember.GrantID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if slices.Compare(existingMember.Roles, member.Roles) == 0 {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(
|
||||
ctx,
|
||||
project.NewProjectGrantMemberChangedEvent(ctx, projectAgg, member.UserID, member.GrantID, member.Roles...))
|
||||
project.NewProjectGrantMemberChangedEvent(ctx,
|
||||
ProjectAggregateFromWriteModelWithCTX(ctx, &existingMember.WriteModel),
|
||||
member.UserID,
|
||||
member.GrantID,
|
||||
member.Roles...,
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -70,29 +140,43 @@ func (c *Commands) ChangeProjectGrantMember(ctx context.Context, member *domain.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberWriteModelToProjectGrantMember(existingMember), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveProjectGrantMember(ctx context.Context, projectID, userID, grantID string) (*domain.ObjectDetails, error) {
|
||||
if projectID == "" || userID == "" || grantID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-66mHd", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
m, err := c.projectGrantMemberWriteModelByID(ctx, projectID, userID, grantID)
|
||||
existingGrant, err := c.projectGrantWriteModelByID(ctx, grantID, "", projectID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingMember, err := c.projectGrantMemberWriteModelByID(ctx, projectID, userID, grantID, existingGrant.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existingMember.State.Exists() {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
if err := c.checkPermissionDeleteProjectGrantMember(ctx, existingGrant.GrantedOrgID, existingMember.GrantID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
projectAgg := ProjectAggregateFromWriteModel(&m.WriteModel)
|
||||
removeEvent := c.removeProjectGrantMember(ctx, projectAgg, userID, grantID, false)
|
||||
removeEvent := c.removeProjectGrantMember(ctx,
|
||||
ProjectAggregateFromWriteModelWithCTX(ctx, &existingMember.WriteModel),
|
||||
userID,
|
||||
grantID,
|
||||
false,
|
||||
)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, removeEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(m, pushedEvents...)
|
||||
err = AppendAndReduce(existingMember, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&m.WriteModel), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeProjectGrantMember(ctx context.Context, projectAgg *eventstore.Aggregate, userID, grantID string, cascade bool) eventstore.Command {
|
||||
@@ -107,19 +191,15 @@ func (c *Commands) removeProjectGrantMember(ctx context.Context, projectAgg *eve
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) projectGrantMemberWriteModelByID(ctx context.Context, projectID, userID, grantID string) (member *ProjectGrantMemberWriteModel, err error) {
|
||||
func (c *Commands) projectGrantMemberWriteModelByID(ctx context.Context, projectID, userID, grantID, resourceOwner string) (member *ProjectGrantMemberWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel := NewProjectGrantMemberWriteModel(projectID, userID, grantID)
|
||||
writeModel := NewProjectGrantMemberWriteModel(projectID, userID, grantID, resourceOwner)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if writeModel.State == domain.MemberStateUnspecified || writeModel.State == domain.MemberStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "PROJECT-37fug", "Errors.NotFound")
|
||||
}
|
||||
|
||||
return writeModel, nil
|
||||
}
|
||||
|
@@ -16,10 +16,11 @@ type ProjectGrantMemberWriteModel struct {
|
||||
State domain.MemberState
|
||||
}
|
||||
|
||||
func NewProjectGrantMemberWriteModel(projectID, userID, grantID string) *ProjectGrantMemberWriteModel {
|
||||
func NewProjectGrantMemberWriteModel(projectID, userID, grantID, resourceOwner string) *ProjectGrantMemberWriteModel {
|
||||
return &ProjectGrantMemberWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: projectID,
|
||||
AggregateID: projectID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
UserID: userID,
|
||||
GrantID: grantID,
|
||||
@@ -66,6 +67,7 @@ func (wm *ProjectGrantMemberWriteModel) Reduce() error {
|
||||
case *project.GrantMemberAddedEvent:
|
||||
wm.Roles = e.Roles
|
||||
wm.State = domain.MemberStateActive
|
||||
wm.ResourceOwner = e.Aggregate().ResourceOwner
|
||||
case *project.GrantMemberChangedEvent:
|
||||
wm.Roles = e.Roles
|
||||
case *project.GrantMemberRemovedEvent:
|
||||
@@ -80,7 +82,8 @@ func (wm *ProjectGrantMemberWriteModel) Reduce() error {
|
||||
}
|
||||
|
||||
func (wm *ProjectGrantMemberWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(project.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
@@ -92,4 +95,5 @@ func (wm *ProjectGrantMemberWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
project.GrantRemovedType,
|
||||
project.ProjectRemovedType).
|
||||
Builder()
|
||||
return query
|
||||
}
|
||||
|
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@@ -18,15 +17,15 @@ import (
|
||||
|
||||
func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
member *domain.ProjectGrantMember
|
||||
member *AddProjectGrantMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ProjectGrantMember
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -38,16 +37,12 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
member: &AddProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -57,19 +52,15 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
member: &AddProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -79,10 +70,10 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
@@ -90,14 +81,11 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
member: &AddProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -107,8 +95,7 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "member add uniqueconstraint err, already exists",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -125,15 +112,28 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
eventFromEventPusher(project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"grantedorg1",
|
||||
[]string{"key1"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPushFailed(zerrors.ThrowAlreadyExists(nil, "ERROR", "internal"),
|
||||
project.NewProjectGrantMemberAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "").Aggregate,
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
"projectgrant1",
|
||||
[]string{"PROJECT_GRANT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
@@ -141,14 +141,11 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
member: &AddProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -158,8 +155,7 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "member add, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -176,15 +172,28 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
eventFromEventPusher(project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"grantedorg1",
|
||||
[]string{"key1"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
project.NewProjectGrantMemberAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "").Aggregate,
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
"projectgrant1",
|
||||
[]string{"PROJECT_GRANT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
@@ -192,35 +201,81 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
GrantID: "projectgrant1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
member: &AddProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
ID: "project1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member add, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username1",
|
||||
"firstname1",
|
||||
"lastname1",
|
||||
"nickname1",
|
||||
"displayname1",
|
||||
language.German,
|
||||
domain.GenderMale,
|
||||
"email1",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
eventFromEventPusher(project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"grantedorg1",
|
||||
[]string{"key1"},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &AddProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.AddProjectGrantMember(tt.args.ctx, tt.args.member)
|
||||
got, err := r.AddProjectGrantMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -236,15 +291,15 @@ func TestCommandSide_AddProjectGrantMember(t *testing.T) {
|
||||
|
||||
func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
member *domain.ProjectGrantMember
|
||||
member *ChangeProjectGrantMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ProjectGrantMember
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -256,16 +311,12 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
member: &ChangeProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -275,19 +326,15 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &ChangeProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -297,10 +344,20 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "member not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"org2",
|
||||
[]string{"rol1", "role2"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
@@ -308,14 +365,11 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
member: &ChangeProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -325,8 +379,17 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "member not changed, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"org2",
|
||||
[]string{"rol1", "role2"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectGrantMemberAddedEvent(context.Background(),
|
||||
@@ -338,6 +401,7 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
@@ -345,25 +409,33 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
member: &ChangeProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"org2",
|
||||
[]string{"rol1", "role2"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectGrantMemberAddedEvent(context.Background(),
|
||||
@@ -383,6 +455,7 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
@@ -393,36 +466,75 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER", "PROJECT_GRANT_VIEWER"},
|
||||
member: &ChangeProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER", "PROJECT_GRANT_VIEWER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ProjectGrantMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
AggregateID: "project1",
|
||||
},
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER", "PROJECT_GRANT_VIEWER"},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"org2",
|
||||
[]string{"rol1", "role2"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectGrantMemberAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
"projectgrant1",
|
||||
[]string{"PROJECT_GRANT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: "PROJECT_GRANT_OWNER",
|
||||
},
|
||||
{
|
||||
Role: "PROJECT_GRANT_VIEWER",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &ChangeProjectGrantMember{
|
||||
ProjectID: "project1",
|
||||
GrantID: "projectgrant1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_GRANT_OWNER", "PROJECT_GRANT_VIEWER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.ChangeProjectGrantMember(tt.args.ctx, tt.args.member)
|
||||
got, err := r.ChangeProjectGrantMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -430,7 +542,7 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -438,7 +550,8 @@ func TestCommandSide_ChangeProjectGrantMember(t *testing.T) {
|
||||
|
||||
func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -459,9 +572,8 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member projectid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -476,9 +588,8 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member userid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -493,9 +604,8 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member grantid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -508,12 +618,22 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member not existing, not found err",
|
||||
name: "member not existing, not found ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"org2",
|
||||
[]string{"rol1", "role2"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -522,14 +642,25 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
grantID: "projectgrant1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsNotFound,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"org2",
|
||||
[]string{"rol1", "role2"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectGrantMemberAddedEvent(context.Background(),
|
||||
@@ -548,6 +679,7 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -561,11 +693,49 @@ func TestCommandSide_RemoveProjectGrantMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewGrantAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"projectgrant1",
|
||||
"org2",
|
||||
[]string{"rol1", "role2"},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectGrantMemberAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
"projectgrant1",
|
||||
[]string{"PROJECT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "project1",
|
||||
userID: "user1",
|
||||
grantID: "projectgrant1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveProjectGrantMember(tt.args.ctx, tt.args.projectID, tt.args.userID, tt.args.grantID)
|
||||
if tt.res.err == nil {
|
||||
|
@@ -2,8 +2,9 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
@@ -11,18 +12,64 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (c *Commands) AddProjectMember(ctx context.Context, member *domain.Member, resourceOwner string) (_ *domain.Member, err error) {
|
||||
type AddProjectMember struct {
|
||||
ResourceOwner string
|
||||
ProjectID string
|
||||
UserID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (i *AddProjectMember) IsValid(zitadelRoles []authz.RoleMapping) error {
|
||||
if i.ProjectID == "" || i.UserID == "" || len(i.Roles) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-W8m4l", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(i.Roles, domain.ProjectRolePrefix, zitadelRoles)) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-3m9ds", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddProjectMember(ctx context.Context, member *AddProjectMember) (_ *domain.ObjectDetails, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
addedMember := NewProjectMemberWriteModel(member.AggregateID, member.UserID, resourceOwner)
|
||||
projectAgg := ProjectAggregateFromWriteModel(&addedMember.WriteModel)
|
||||
event, err := c.addProjectMember(ctx, projectAgg, addedMember, member)
|
||||
if err := member.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = c.checkUserExists(ctx, member.UserID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
projectResourceOwner, err := c.checkProjectExists(ctx, member.ProjectID, member.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// resourceowner of the member if not provided is the resourceowner of the project
|
||||
if member.ResourceOwner == "" {
|
||||
member.ResourceOwner = projectResourceOwner
|
||||
}
|
||||
addedMember, err := c.projectMemberWriteModelByID(ctx, member.ProjectID, member.UserID, member.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// error if provided resourceowner is not equal to the resourceowner of the project
|
||||
if projectResourceOwner != addedMember.ResourceOwner {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-0l10S9OmZV", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
if err := c.checkPermissionUpdateProjectMember(ctx, addedMember.ResourceOwner, addedMember.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if addedMember.State.Exists() {
|
||||
return nil, zerrors.ThrowAlreadyExists(nil, "PROJECT-PtXi1", "Errors.Project.Member.AlreadyExists")
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx, event)
|
||||
pushedEvents, err := c.eventstore.Push(ctx,
|
||||
project.NewProjectMemberAddedEvent(ctx,
|
||||
ProjectAggregateFromWriteModelWithCTX(ctx, &addedMember.WriteModel),
|
||||
member.UserID,
|
||||
member.Roles...,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -31,53 +78,46 @@ func (c *Commands) AddProjectMember(ctx context.Context, member *domain.Member,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberWriteModelToMember(&addedMember.MemberWriteModel), nil
|
||||
return writeModelToObjectDetails(&addedMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) addProjectMember(ctx context.Context, projectAgg *eventstore.Aggregate, addedMember *ProjectMemberWriteModel, member *domain.Member) (_ eventstore.Command, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
type ChangeProjectMember struct {
|
||||
ResourceOwner string
|
||||
ProjectID string
|
||||
UserID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
if !member.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-W8m4l", "Errors.Project.Member.Invalid")
|
||||
func (i *ChangeProjectMember) IsValid(zitadelRoles []authz.RoleMapping) error {
|
||||
if i.ProjectID == "" || i.UserID == "" || len(i.Roles) == 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-LiaZi", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.ProjectRolePrefix, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-3m9ds", "Errors.Project.Member.Invalid")
|
||||
if len(domain.CheckForInvalidRoles(i.Roles, domain.ProjectRolePrefix, zitadelRoles)) > 0 {
|
||||
return zerrors.ThrowInvalidArgument(nil, "PROJECT-3m9d", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
|
||||
err = c.checkUserExists(ctx, addedMember.UserID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, addedMember)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if addedMember.State == domain.MemberStateActive {
|
||||
return nil, zerrors.ThrowAlreadyExists(nil, "PROJECT-PtXi1", "Errors.Project.Member.AlreadyExists")
|
||||
}
|
||||
|
||||
return project.NewProjectMemberAddedEvent(ctx, projectAgg, member.UserID, member.Roles...), nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangeProjectMember updates an existing member
|
||||
func (c *Commands) ChangeProjectMember(ctx context.Context, member *domain.Member, resourceOwner string) (*domain.Member, error) {
|
||||
if !member.IsValid() {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-LiaZi", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
if len(domain.CheckForInvalidRoles(member.Roles, domain.ProjectRolePrefix, c.zitadelRoles)) > 0 {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-3m9d", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
|
||||
existingMember, err := c.projectMemberWriteModelByID(ctx, member.AggregateID, member.UserID, resourceOwner)
|
||||
if err != nil {
|
||||
func (c *Commands) ChangeProjectMember(ctx context.Context, member *ChangeProjectMember) (*domain.ObjectDetails, error) {
|
||||
if err := member.IsValid(c.zitadelRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(existingMember.Roles, member.Roles) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "PROJECT-LiaZi", "Errors.Project.Member.RolesNotChanged")
|
||||
existingMember, err := c.projectMemberWriteModelByID(ctx, member.ProjectID, member.UserID, member.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
projectAgg := ProjectAggregateFromWriteModel(&existingMember.MemberWriteModel.WriteModel)
|
||||
if !existingMember.State.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "PROJECT-D8JxR", "Errors.NotFound")
|
||||
}
|
||||
if err := c.checkPermissionUpdateProjectMember(ctx, existingMember.ResourceOwner, existingMember.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if slices.Compare(existingMember.Roles, member.Roles) == 0 {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
projectAgg := ProjectAggregateFromWriteModelWithCTX(ctx, &existingMember.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, project.NewProjectMemberChangedEvent(ctx, projectAgg, member.UserID, member.Roles...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -88,33 +128,35 @@ func (c *Commands) ChangeProjectMember(ctx context.Context, member *domain.Membe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberWriteModelToMember(&existingMember.MemberWriteModel), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveProjectMember(ctx context.Context, projectID, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if projectID == "" || userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-66mHd", "Errors.Project.Member.Invalid")
|
||||
}
|
||||
m, err := c.projectMemberWriteModelByID(ctx, projectID, userID, resourceOwner)
|
||||
if err != nil && !zerrors.IsNotFound(err) {
|
||||
existingMember, err := c.projectMemberWriteModelByID(ctx, projectID, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if zerrors.IsNotFound(err) {
|
||||
// empty response because we have no data that match the request
|
||||
return &domain.ObjectDetails{}, nil
|
||||
if !existingMember.State.Exists() {
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
if err := c.checkPermissionDeleteProjectMember(ctx, existingMember.ResourceOwner, existingMember.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
projectAgg := ProjectAggregateFromWriteModel(&m.MemberWriteModel.WriteModel)
|
||||
projectAgg := ProjectAggregateFromWriteModelWithCTX(ctx, &existingMember.WriteModel)
|
||||
removeEvent := c.removeProjectMember(ctx, projectAgg, userID, false)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, removeEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(m, pushedEvents...)
|
||||
err = AppendAndReduce(existingMember, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&m.WriteModel), nil
|
||||
return writeModelToObjectDetails(&existingMember.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeProjectMember(ctx context.Context, projectAgg *eventstore.Aggregate, userID string, cascade bool) eventstore.Command {
|
||||
@@ -138,9 +180,5 @@ func (c *Commands) projectMemberWriteModelByID(ctx context.Context, projectID, u
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if writeModel.State == domain.MemberStateUnspecified || writeModel.State == domain.MemberStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "PROJECT-D8JxR", "Errors.NotFound")
|
||||
}
|
||||
|
||||
return writeModel, nil
|
||||
}
|
||||
|
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@@ -18,16 +17,15 @@ import (
|
||||
|
||||
func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
member *domain.Member
|
||||
resourceOwner string
|
||||
member *AddProjectMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.Member
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -39,18 +37,14 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
member: &AddProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -59,20 +53,16 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &AddProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -81,10 +71,10 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
@@ -92,15 +82,12 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &AddProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
@@ -109,8 +96,7 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "member already exists, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -127,6 +113,19 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectAddedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"project",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingUnspecified,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
@@ -136,6 +135,7 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
@@ -143,15 +143,12 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &AddProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
@@ -160,8 +157,7 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "member add uniqueconstraint err, already exists",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -178,6 +174,19 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectAddedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"project",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingUnspecified,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPushFailed(zerrors.ThrowAlreadyExists(nil, "ERROR", "internal"),
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
@@ -187,6 +196,7 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
@@ -194,15 +204,12 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &AddProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorAlreadyExists,
|
||||
@@ -211,8 +218,7 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "member add, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -229,6 +235,19 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectAddedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"project",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingUnspecified,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
@@ -238,6 +257,7 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
@@ -245,35 +265,81 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &AddProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{domain.RoleProjectOwner},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "member add, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username1",
|
||||
"firstname1",
|
||||
"lastname1",
|
||||
"nickname1",
|
||||
"displayname1",
|
||||
language.German,
|
||||
domain.GenderMale,
|
||||
"email1",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectAddedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"project",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingUnspecified,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &AddProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.AddProjectMember(tt.args.ctx, tt.args.member, tt.args.resourceOwner)
|
||||
got, err := r.AddProjectMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -281,7 +347,7 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -289,16 +355,15 @@ func TestCommandSide_AddProjectMember(t *testing.T) {
|
||||
|
||||
func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
zitadelRoles []authz.RoleMapping
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
member *domain.Member
|
||||
resourceOwner string
|
||||
member *ChangeProjectMember
|
||||
}
|
||||
type res struct {
|
||||
want *domain.Member
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -310,18 +375,14 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
member: &ChangeProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -330,20 +391,16 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid roles, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &ChangeProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -352,10 +409,10 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "member not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
@@ -363,15 +420,12 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &ChangeProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsNotFound,
|
||||
@@ -380,8 +434,7 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "member not changed, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
@@ -392,6 +445,7 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
@@ -399,25 +453,23 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
member: &ChangeProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
@@ -435,6 +487,7 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
@@ -445,35 +498,64 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
member: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER", "PROJECT_VIEWER"},
|
||||
member: &ChangeProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER", "PROJECT_VIEWER"},
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Member{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: "org1",
|
||||
AggregateID: "project1",
|
||||
},
|
||||
UserID: "user1",
|
||||
Roles: []string{domain.RoleProjectOwner, "PROJECT_VIEWER"},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member change, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
[]string{"PROJECT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
zitadelRoles: []authz.RoleMapping{
|
||||
{
|
||||
Role: domain.RoleProjectOwner,
|
||||
},
|
||||
{
|
||||
Role: "PROJECT_VIEWER",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
member: &ChangeProjectMember{
|
||||
ResourceOwner: "org1",
|
||||
ProjectID: "project1",
|
||||
UserID: "user1",
|
||||
Roles: []string{"PROJECT_OWNER", "PROJECT_VIEWER"},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
zitadelRoles: tt.fields.zitadelRoles,
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.ChangeProjectMember(tt.args.ctx, tt.args.member, tt.args.resourceOwner)
|
||||
got, err := r.ChangeProjectMember(context.Background(), tt.args.member)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -481,7 +563,7 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -489,10 +571,10 @@ func TestCommandSide_ChangeProjectMember(t *testing.T) {
|
||||
|
||||
func TestCommandSide_RemoveProjectMember(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
projectID string
|
||||
userID string
|
||||
resourceOwner string
|
||||
@@ -510,12 +592,10 @@ func TestCommandSide_RemoveProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member projectid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "",
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
@@ -527,12 +607,10 @@ func TestCommandSide_RemoveProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "invalid member userid missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "project1",
|
||||
userID: "",
|
||||
resourceOwner: "org1",
|
||||
@@ -544,26 +622,26 @@ func TestCommandSide_RemoveProjectMember(t *testing.T) {
|
||||
{
|
||||
name: "member not existing, empty object details result",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "project1",
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
@@ -580,9 +658,9 @@ func TestCommandSide_RemoveProjectMember(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
projectID: "project1",
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
@@ -593,13 +671,39 @@ func TestCommandSide_RemoveProjectMember(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member remove, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewProjectMemberAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"user1",
|
||||
[]string{"PROJECT_OWNER"}...,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
projectID: "project1",
|
||||
userID: "user1",
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveProjectMember(tt.args.ctx, tt.args.projectID, tt.args.userID, tt.args.resourceOwner)
|
||||
got, err := r.RemoveProjectMember(context.Background(), tt.args.projectID, tt.args.userID, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@@ -194,7 +194,7 @@ func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string,
|
||||
events = append(events, user.NewUserRemovedEvent(ctx, userAgg, existingUser.UserName, existingUser.IDPLinks, domainPolicy.UserLoginMustBeDomain))
|
||||
|
||||
for _, grantID := range cascadingGrantIDs {
|
||||
removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true)
|
||||
removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true, false, nil)
|
||||
if err != nil {
|
||||
logging.WithFields("usergrantid", grantID).WithError(err).Warn("could not cascade remove role on user grant")
|
||||
continue
|
||||
@@ -327,18 +327,18 @@ func (c *Commands) UserDomainClaimedSent(ctx context.Context, orgID, userID stri
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Commands) checkUserExists(ctx context.Context, userID, resourceOwner string) (err error) {
|
||||
func (c *Commands) checkUserExists(ctx context.Context, userID, resourceOwner string) (_ string, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
existingUser, err := c.userWriteModelByID(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
if !isUserStateExists(existingUser.UserState) {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-uXHNj", "Errors.User.NotFound")
|
||||
return "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-uXHNj", "Errors.User.NotFound")
|
||||
}
|
||||
return nil
|
||||
return existingUser.ResourceOwner, nil
|
||||
}
|
||||
|
||||
func (c *Commands) userWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *UserWriteModel, err error) {
|
||||
|
@@ -2,7 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -15,11 +15,14 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (c *Commands) AddUserGrant(ctx context.Context, usergrant *domain.UserGrant, resourceOwner string) (_ *domain.UserGrant, err error) {
|
||||
// AddUserGrant authorizes a user for a project with the given role keys.
|
||||
// The project must be owned by or granted to the resourceOwner.
|
||||
// If the resourceOwner is nil, the project must be owned by the project that belongs to usergrant.ProjectID.
|
||||
func (c *Commands) AddUserGrant(ctx context.Context, usergrant *domain.UserGrant, check UserGrantPermissionCheck) (_ *domain.UserGrant, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
event, addedUserGrant, err := c.addUserGrant(ctx, usergrant, resourceOwner)
|
||||
event, addedUserGrant, err := c.addUserGrant(ctx, usergrant, check)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -35,11 +38,11 @@ func (c *Commands) AddUserGrant(ctx context.Context, usergrant *domain.UserGrant
|
||||
return userGrantWriteModelToUserGrant(addedUserGrant), nil
|
||||
}
|
||||
|
||||
func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string) (command eventstore.Command, _ *UserGrantWriteModel, err error) {
|
||||
func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant, check UserGrantPermissionCheck) (command eventstore.Command, _ *UserGrantWriteModel, err error) {
|
||||
if !userGrant.IsValid() {
|
||||
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-kVfMa", "Errors.UserGrant.Invalid")
|
||||
}
|
||||
err = c.checkUserGrantPreCondition(ctx, userGrant, resourceOwner)
|
||||
err = c.checkUserGrantPreCondition(ctx, userGrant, check)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -48,7 +51,7 @@ func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
addedUserGrant := NewUserGrantWriteModel(userGrant.AggregateID, resourceOwner)
|
||||
addedUserGrant := NewUserGrantWriteModel(userGrant.AggregateID, userGrant.ResourceOwner)
|
||||
userGrantAgg := UserGrantAggregateFromWriteModel(&addedUserGrant.WriteModel)
|
||||
command = usergrant.NewUserGrantAddedEvent(
|
||||
ctx,
|
||||
@@ -61,54 +64,51 @@ func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant
|
||||
return command, addedUserGrant, nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeUserGrant(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string) (_ *domain.UserGrant, err error) {
|
||||
event, changedUserGrant, err := c.changeUserGrant(ctx, userGrant, resourceOwner, false)
|
||||
func (c *Commands) ChangeUserGrant(ctx context.Context, userGrant *domain.UserGrant, cascade, ignoreUnchanged bool, check UserGrantPermissionCheck) (_ *domain.UserGrant, err error) {
|
||||
if userGrant.AggregateID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-3M0sd", "Errors.UserGrant.Invalid")
|
||||
}
|
||||
existingUserGrant, err := c.userGrantWriteModelByID(ctx, userGrant.AggregateID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingUserGrant.State == domain.UserGrantStateUnspecified || existingUserGrant.State == domain.UserGrantStateRemoved {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.UserGrant.NotFound")
|
||||
}
|
||||
|
||||
grantUnchanged := slices.Equal(existingUserGrant.RoleKeys, userGrant.RoleKeys)
|
||||
if grantUnchanged {
|
||||
if ignoreUnchanged {
|
||||
return userGrantWriteModelToUserGrant(existingUserGrant), nil
|
||||
}
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Rs8fy", "Errors.UserGrant.NotChanged")
|
||||
}
|
||||
userGrant.UserID = existingUserGrant.UserID
|
||||
userGrant.ProjectID = existingUserGrant.ProjectID
|
||||
userGrant.ProjectGrantID = existingUserGrant.ProjectGrantID
|
||||
userGrant.ResourceOwner = existingUserGrant.ResourceOwner
|
||||
|
||||
err = c.checkUserGrantPreCondition(ctx, userGrant, check)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
changedUserGrant := NewUserGrantWriteModel(userGrant.AggregateID, userGrant.ResourceOwner)
|
||||
userGrantAgg := UserGrantAggregateFromWriteModel(&changedUserGrant.WriteModel)
|
||||
|
||||
var event eventstore.Command = usergrant.NewUserGrantChangedEvent(ctx, userGrantAgg, existingUserGrant.UserID, userGrant.RoleKeys)
|
||||
if cascade {
|
||||
event = usergrant.NewUserGrantCascadeChangedEvent(ctx, userGrantAgg, userGrant.RoleKeys)
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(changedUserGrant, pushedEvents...)
|
||||
err = AppendAndReduce(existingUserGrant, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return userGrantWriteModelToUserGrant(changedUserGrant), nil
|
||||
}
|
||||
|
||||
func (c *Commands) changeUserGrant(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string, cascade bool) (_ eventstore.Command, _ *UserGrantWriteModel, err error) {
|
||||
if userGrant.AggregateID == "" {
|
||||
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-3M0sd", "Errors.UserGrant.Invalid")
|
||||
}
|
||||
existingUserGrant, err := c.userGrantWriteModelByID(ctx, userGrant.AggregateID, userGrant.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if existingUserGrant.State == domain.UserGrantStateUnspecified || existingUserGrant.State == domain.UserGrantStateRemoved {
|
||||
return nil, nil, zerrors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.UserGrant.NotFound")
|
||||
}
|
||||
if reflect.DeepEqual(existingUserGrant.RoleKeys, userGrant.RoleKeys) {
|
||||
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Rs8fy", "Errors.UserGrant.NotChanged")
|
||||
}
|
||||
userGrant.ProjectID = existingUserGrant.ProjectID
|
||||
userGrant.ProjectGrantID = existingUserGrant.ProjectGrantID
|
||||
err = c.checkUserGrantPreCondition(ctx, userGrant, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
changedUserGrant := NewUserGrantWriteModel(userGrant.AggregateID, resourceOwner)
|
||||
userGrantAgg := UserGrantAggregateFromWriteModel(&changedUserGrant.WriteModel)
|
||||
|
||||
if cascade {
|
||||
return usergrant.NewUserGrantCascadeChangedEvent(ctx, userGrantAgg, userGrant.RoleKeys), existingUserGrant, nil
|
||||
}
|
||||
return usergrant.NewUserGrantChangedEvent(ctx, userGrantAgg, existingUserGrant.UserID, userGrant.RoleKeys), existingUserGrant, nil
|
||||
return userGrantWriteModelToUserGrant(existingUserGrant), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeRoleFromUserGrant(ctx context.Context, userGrantID string, roleKeys []string, cascade bool) (_ eventstore.Command, err error) {
|
||||
@@ -144,8 +144,8 @@ func (c *Commands) removeRoleFromUserGrant(ctx context.Context, userGrantID stri
|
||||
return usergrant.NewUserGrantChangedEvent(ctx, userGrantAgg, existingUserGrant.UserID, existingUserGrant.RoleKeys), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeactivateUserGrant(ctx context.Context, grantID, resourceOwner string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
if grantID == "" || resourceOwner == "" {
|
||||
func (c *Commands) DeactivateUserGrant(ctx context.Context, grantID string, resourceOwner string, check UserGrantPermissionCheck) (objectDetails *domain.ObjectDetails, err error) {
|
||||
if grantID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-N2OhG", "Errors.UserGrant.IDMissing")
|
||||
}
|
||||
|
||||
@@ -157,14 +157,17 @@ func (c *Commands) DeactivateUserGrant(ctx context.Context, grantID, resourceOwn
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.UserGrant.NotFound")
|
||||
}
|
||||
if existingUserGrant.State != domain.UserGrantStateActive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-1S9gx", "Errors.UserGrant.NotActive")
|
||||
return writeModelToObjectDetails(&existingUserGrant.WriteModel), nil
|
||||
}
|
||||
if check != nil {
|
||||
err = check(existingUserGrant.ProjectID, existingUserGrant.ProjectGrantID)(existingUserGrant.ResourceOwner, "")
|
||||
} else {
|
||||
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
|
||||
}
|
||||
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deactivateUserGrant := NewUserGrantWriteModel(grantID, resourceOwner)
|
||||
deactivateUserGrant := NewUserGrantWriteModel(grantID, existingUserGrant.ResourceOwner)
|
||||
userGrantAgg := UserGrantAggregateFromWriteModel(&deactivateUserGrant.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, usergrant.NewUserGrantDeactivatedEvent(ctx, userGrantAgg))
|
||||
if err != nil {
|
||||
@@ -177,8 +180,8 @@ func (c *Commands) DeactivateUserGrant(ctx context.Context, grantID, resourceOwn
|
||||
return writeModelToObjectDetails(&existingUserGrant.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) ReactivateUserGrant(ctx context.Context, grantID, resourceOwner string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
if grantID == "" || resourceOwner == "" {
|
||||
func (c *Commands) ReactivateUserGrant(ctx context.Context, grantID string, resourceOwner string, check UserGrantPermissionCheck) (objectDetails *domain.ObjectDetails, err error) {
|
||||
if grantID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Qxy8v", "Errors.UserGrant.IDMissing")
|
||||
}
|
||||
|
||||
@@ -190,13 +193,17 @@ func (c *Commands) ReactivateUserGrant(ctx context.Context, grantID, resourceOwn
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Lp0gs", "Errors.UserGrant.NotFound")
|
||||
}
|
||||
if existingUserGrant.State != domain.UserGrantStateInactive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-1ML0v", "Errors.UserGrant.NotInactive")
|
||||
return writeModelToObjectDetails(&existingUserGrant.WriteModel), nil
|
||||
}
|
||||
if check != nil {
|
||||
err = check(existingUserGrant.ProjectID, existingUserGrant.ProjectGrantID)(existingUserGrant.ResourceOwner, "")
|
||||
} else {
|
||||
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
|
||||
}
|
||||
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deactivateUserGrant := NewUserGrantWriteModel(grantID, resourceOwner)
|
||||
deactivateUserGrant := NewUserGrantWriteModel(grantID, existingUserGrant.ResourceOwner)
|
||||
userGrantAgg := UserGrantAggregateFromWriteModel(&deactivateUserGrant.WriteModel)
|
||||
pushedEvents, err := c.eventstore.Push(ctx, usergrant.NewUserGrantReactivatedEvent(ctx, userGrantAgg))
|
||||
if err != nil {
|
||||
@@ -209,12 +216,14 @@ func (c *Commands) ReactivateUserGrant(ctx context.Context, grantID, resourceOwn
|
||||
return writeModelToObjectDetails(&existingUserGrant.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveUserGrant(ctx context.Context, grantID, resourceOwner string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
event, existingUserGrant, err := c.removeUserGrant(ctx, grantID, resourceOwner, false)
|
||||
func (c *Commands) RemoveUserGrant(ctx context.Context, grantID string, resourceOwner string, ignoreNotFound bool, check UserGrantPermissionCheck) (objectDetails *domain.ObjectDetails, err error) {
|
||||
event, existingUserGrant, err := c.removeUserGrant(ctx, grantID, resourceOwner, false, ignoreNotFound, check)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if event == nil {
|
||||
return writeModelToObjectDetails(&existingUserGrant.WriteModel), nil
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -232,7 +241,7 @@ func (c *Commands) BulkRemoveUserGrant(ctx context.Context, grantIDs []string, r
|
||||
}
|
||||
events := make([]eventstore.Command, len(grantIDs))
|
||||
for i, grantID := range grantIDs {
|
||||
event, _, err := c.removeUserGrant(ctx, grantID, resourceOwner, false)
|
||||
event, _, err := c.removeUserGrant(ctx, grantID, resourceOwner, false, false, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -242,7 +251,7 @@ func (c *Commands) BulkRemoveUserGrant(ctx context.Context, grantIDs []string, r
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Commands) removeUserGrant(ctx context.Context, grantID, resourceOwner string, cascade bool) (_ eventstore.Command, writeModel *UserGrantWriteModel, err error) {
|
||||
func (c *Commands) removeUserGrant(ctx context.Context, grantID string, resourceOwner string, cascade, ignoreNotFound bool, check UserGrantPermissionCheck) (_ eventstore.Command, writeModel *UserGrantWriteModel, err error) {
|
||||
if grantID == "" {
|
||||
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-J9sc5", "Errors.UserGrant.IDMissing")
|
||||
}
|
||||
@@ -252,15 +261,22 @@ func (c *Commands) removeUserGrant(ctx context.Context, grantID, resourceOwner s
|
||||
return nil, nil, err
|
||||
}
|
||||
if existingUserGrant.State == domain.UserGrantStateUnspecified || existingUserGrant.State == domain.UserGrantStateRemoved {
|
||||
if ignoreNotFound {
|
||||
return nil, existingUserGrant, nil
|
||||
}
|
||||
return nil, nil, zerrors.ThrowNotFound(nil, "COMMAND-1My0t", "Errors.UserGrant.NotFound")
|
||||
}
|
||||
if !cascade {
|
||||
if !cascade && check == nil {
|
||||
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if check != nil {
|
||||
if err = check(existingUserGrant.ProjectID, existingUserGrant.ProjectGrantID)(existingUserGrant.ResourceOwner, ""); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
removeUserGrant := NewUserGrantWriteModel(grantID, existingUserGrant.ResourceOwner)
|
||||
userGrantAgg := UserGrantAggregateFromWriteModel(&removeUserGrant.WriteModel)
|
||||
if cascade {
|
||||
@@ -279,7 +295,7 @@ func (c *Commands) removeUserGrant(ctx context.Context, grantID, resourceOwner s
|
||||
existingUserGrant.ProjectGrantID), existingUserGrant, nil
|
||||
}
|
||||
|
||||
func (c *Commands) userGrantWriteModelByID(ctx context.Context, userGrantID, resourceOwner string) (writeModel *UserGrantWriteModel, err error) {
|
||||
func (c *Commands) userGrantWriteModelByID(ctx context.Context, userGrantID string, resourceOwner string) (writeModel *UserGrantWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -291,31 +307,46 @@ func (c *Commands) userGrantWriteModelByID(ctx context.Context, userGrantID, res
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkUserGrantPreCondition(ctx context.Context, usergrant *domain.UserGrant, resourceOwner string) (err error) {
|
||||
func (c *Commands) checkUserGrantPreCondition(ctx context.Context, usergrant *domain.UserGrant, check UserGrantPermissionCheck) (err error) {
|
||||
if !authz.GetFeatures(ctx).ShouldUseImprovedPerformance(feature.ImprovedPerformanceTypeUserGrant) {
|
||||
return c.checkUserGrantPreConditionOld(ctx, usergrant, resourceOwner)
|
||||
return c.checkUserGrantPreConditionOld(ctx, usergrant, check)
|
||||
}
|
||||
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if err := c.checkUserExists(ctx, usergrant.UserID, ""); err != nil {
|
||||
if _, err := c.checkUserExists(ctx, usergrant.UserID, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
existingRoleKeys, err := c.searchUserGrantPreConditionState(ctx, usergrant, resourceOwner)
|
||||
if usergrant.ProjectGrantID != "" || usergrant.ResourceOwner == "" {
|
||||
projectOwner, grantID, err := c.searchProjectOwnerAndGrantID(ctx, usergrant.ProjectID, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if usergrant.ResourceOwner == "" {
|
||||
usergrant.ResourceOwner = projectOwner
|
||||
}
|
||||
if usergrant.ProjectGrantID == "" {
|
||||
usergrant.ProjectGrantID = grantID
|
||||
}
|
||||
}
|
||||
existingRoleKeys, err := c.searchUserGrantPreConditionState(ctx, usergrant)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if usergrant.HasInvalidRoles(existingRoleKeys) {
|
||||
return zerrors.ThrowPreconditionFailed(err, "COMMAND-mm9F4", "Errors.Project.Role.NotFound")
|
||||
}
|
||||
return nil
|
||||
if check != nil {
|
||||
return check(usergrant.ProjectID, usergrant.ProjectGrantID)(usergrant.ResourceOwner, "")
|
||||
}
|
||||
return checkExplicitProjectPermission(ctx, usergrant.ProjectGrantID, usergrant.ProjectID)
|
||||
}
|
||||
|
||||
// this code needs to be rewritten anyways as soon as we improved the fields handling
|
||||
//
|
||||
//nolint:gocognit
|
||||
func (c *Commands) searchUserGrantPreConditionState(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string) (existingRoleKeys []string, err error) {
|
||||
func (c *Commands) searchUserGrantPreConditionState(ctx context.Context, userGrant *domain.UserGrant) (existingRoleKeys []string, err error) {
|
||||
criteria := []map[eventstore.FieldType]any{
|
||||
// project state query
|
||||
{
|
||||
@@ -327,7 +358,7 @@ func (c *Commands) searchUserGrantPreConditionState(ctx context.Context, userGra
|
||||
// granted org query
|
||||
{
|
||||
eventstore.FieldTypeAggregateType: org.AggregateType,
|
||||
eventstore.FieldTypeAggregateID: resourceOwner,
|
||||
eventstore.FieldTypeAggregateID: userGrant.ResourceOwner,
|
||||
eventstore.FieldTypeFieldName: org.OrgStateSearchField,
|
||||
eventstore.FieldTypeObjectType: org.OrgSearchType,
|
||||
},
|
||||
@@ -386,7 +417,7 @@ func (c *Commands) searchUserGrantPreConditionState(ctx context.Context, userGra
|
||||
case project.ProjectGrantGrantedOrgIDSearchField:
|
||||
var orgID string
|
||||
err := result.Value.Unmarshal(&orgID)
|
||||
if err != nil || orgID != resourceOwner {
|
||||
if err != nil || orgID != userGrant.ResourceOwner {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-3m9gg", "Errors.Org.NotFound")
|
||||
}
|
||||
case project.ProjectGrantStateSearchField:
|
||||
@@ -425,26 +456,63 @@ func (c *Commands) searchUserGrantPreConditionState(ctx context.Context, userGra
|
||||
return existingRoleKeys, nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkUserGrantPreConditionOld(ctx context.Context, usergrant *domain.UserGrant, resourceOwner string) (err error) {
|
||||
func (c *Commands) checkUserGrantPreConditionOld(ctx context.Context, usergrant *domain.UserGrant, check UserGrantPermissionCheck) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
preConditions := NewUserGrantPreConditionReadModel(usergrant.UserID, usergrant.ProjectID, usergrant.ProjectGrantID, resourceOwner)
|
||||
preConditions := NewUserGrantPreConditionReadModel(usergrant.UserID, usergrant.ProjectID, usergrant.ProjectGrantID, usergrant.ResourceOwner)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, preConditions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if usergrant.ResourceOwner == "" {
|
||||
usergrant.ResourceOwner = preConditions.ProjectResourceOwner
|
||||
}
|
||||
if usergrant.ProjectGrantID == "" {
|
||||
usergrant.ProjectGrantID = preConditions.ProjectGrantID
|
||||
}
|
||||
if !preConditions.UserExists {
|
||||
return zerrors.ThrowPreconditionFailed(err, "COMMAND-4f8sg", "Errors.User.NotFound")
|
||||
}
|
||||
if usergrant.ProjectGrantID == "" && !preConditions.ProjectExists {
|
||||
projectIsOwned := usergrant.ResourceOwner == "" || usergrant.ResourceOwner == preConditions.ProjectResourceOwner
|
||||
if projectIsOwned && !preConditions.ProjectExists {
|
||||
return zerrors.ThrowPreconditionFailed(err, "COMMAND-3n77S", "Errors.Project.NotFound")
|
||||
}
|
||||
if usergrant.ProjectGrantID != "" && !preConditions.ProjectGrantExists {
|
||||
if !projectIsOwned && !preConditions.ProjectGrantExists {
|
||||
return zerrors.ThrowPreconditionFailed(err, "COMMAND-4m9ff", "Errors.Project.Grant.NotFound")
|
||||
}
|
||||
if usergrant.HasInvalidRoles(preConditions.ExistingRoleKeys) {
|
||||
return zerrors.ThrowPreconditionFailed(err, "COMMAND-mm9F4", "Errors.Project.Role.NotFound")
|
||||
}
|
||||
return nil
|
||||
if check != nil {
|
||||
return check(usergrant.ProjectID, usergrant.ProjectGrantID)(usergrant.ResourceOwner, "")
|
||||
}
|
||||
return checkExplicitProjectPermission(ctx, usergrant.ProjectGrantID, usergrant.ProjectID)
|
||||
}
|
||||
|
||||
func (c *Commands) searchProjectOwnerAndGrantID(ctx context.Context, projectID string, grantedOrgID string) (projectOwner string, grantID string, err error) {
|
||||
grantIDQuery := map[eventstore.FieldType]any{
|
||||
eventstore.FieldTypeAggregateType: project.AggregateType,
|
||||
eventstore.FieldTypeAggregateID: projectID,
|
||||
eventstore.FieldTypeFieldName: project.ProjectGrantGrantedOrgIDSearchField,
|
||||
}
|
||||
if grantedOrgID != "" {
|
||||
grantIDQuery[eventstore.FieldTypeValue] = grantedOrgID
|
||||
grantIDQuery[eventstore.FieldTypeObjectType] = project.ProjectGrantSearchType
|
||||
|
||||
}
|
||||
results, err := c.eventstore.Search(ctx, grantIDQuery)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
for _, result := range results {
|
||||
projectOwner = result.Aggregate.ResourceOwner
|
||||
if grantedOrgID != "" && grantedOrgID == projectOwner {
|
||||
return projectOwner, "", nil
|
||||
}
|
||||
if result.Object.Type == project.ProjectGrantSearchType {
|
||||
return projectOwner, result.Object.ID, nil
|
||||
}
|
||||
}
|
||||
return projectOwner, grantID, err
|
||||
}
|
||||
|
@@ -36,6 +36,7 @@ func (wm *UserGrantWriteModel) Reduce() error {
|
||||
wm.ProjectGrantID = e.ProjectGrantID
|
||||
wm.RoleKeys = e.RoleKeys
|
||||
wm.State = domain.UserGrantStateActive
|
||||
wm.ResourceOwner = e.Aggregate().ResourceOwner
|
||||
case *usergrant.UserGrantChangedEvent:
|
||||
wm.RoleKeys = e.RoleKeys
|
||||
case *usergrant.UserGrantCascadeChangedEvent:
|
||||
@@ -86,17 +87,18 @@ func UserGrantAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Agg
|
||||
type UserGrantPreConditionReadModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
UserID string
|
||||
ProjectID string
|
||||
ProjectGrantID string
|
||||
ResourceOwner string
|
||||
UserExists bool
|
||||
ProjectExists bool
|
||||
ProjectGrantExists bool
|
||||
ExistingRoleKeys []string
|
||||
UserID string
|
||||
ProjectID string
|
||||
ProjectResourceOwner string
|
||||
ProjectGrantID string
|
||||
ResourceOwner string
|
||||
UserExists bool
|
||||
ProjectExists bool
|
||||
ProjectGrantExists bool
|
||||
ExistingRoleKeys []string
|
||||
}
|
||||
|
||||
func NewUserGrantPreConditionReadModel(userID, projectID, projectGrantID, resourceOwner string) *UserGrantPreConditionReadModel {
|
||||
func NewUserGrantPreConditionReadModel(userID, projectID, projectGrantID string, resourceOwner string) *UserGrantPreConditionReadModel {
|
||||
return &UserGrantPreConditionReadModel{
|
||||
UserID: userID,
|
||||
ProjectID: projectID,
|
||||
@@ -117,15 +119,19 @@ func (wm *UserGrantPreConditionReadModel) Reduce() error {
|
||||
case *user.UserRemovedEvent:
|
||||
wm.UserExists = false
|
||||
case *project.ProjectAddedEvent:
|
||||
if wm.ProjectGrantID == "" && wm.ResourceOwner == e.Aggregate().ResourceOwner {
|
||||
if wm.ResourceOwner == "" || wm.ResourceOwner == e.Aggregate().ResourceOwner {
|
||||
wm.ProjectExists = true
|
||||
}
|
||||
wm.ProjectResourceOwner = e.Aggregate().ResourceOwner
|
||||
case *project.ProjectRemovedEvent:
|
||||
wm.ProjectExists = false
|
||||
case *project.GrantAddedEvent:
|
||||
if wm.ProjectGrantID == e.GrantID && wm.ResourceOwner == e.GrantedOrgID {
|
||||
if (wm.ProjectGrantID == e.GrantID || wm.ProjectGrantID == "") && wm.ResourceOwner != "" && wm.ResourceOwner == e.GrantedOrgID {
|
||||
wm.ProjectGrantExists = true
|
||||
wm.ExistingRoleKeys = e.RoleKeys
|
||||
if wm.ProjectGrantID == "" {
|
||||
wm.ProjectGrantID = e.GrantID
|
||||
}
|
||||
}
|
||||
case *project.GrantChangedEvent:
|
||||
if wm.ProjectGrantID == e.GrantID {
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -26,7 +26,7 @@ func (c *Commands) ImportHumanTOTP(ctx context.Context, userID, userAgentID, res
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = c.checkUserExists(ctx, userID, resourceOwner); err != nil {
|
||||
if _, err = c.checkUserExists(ctx, userID, resourceOwner); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@@ -56,7 +56,7 @@ func (c *Commands) BulkAddedUserIDPLinks(ctx context.Context, userID, resourceOw
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-Ek9s", "Errors.User.ExternalIDP.MinimumExternalIDPNeeded")
|
||||
}
|
||||
|
||||
if err := c.checkUserExists(ctx, userID, resourceOwner); err != nil {
|
||||
if _, err := c.checkUserExists(ctx, userID, resourceOwner); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -14,12 +15,25 @@ func (c *Commands) SetUserMetadata(ctx context.Context, metadata *domain.Metadat
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
userResourceOwner, err := c.checkUserExists(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.checkPermissionUpdateUser(ctx, userResourceOwner, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setMetadata, err := c.getUserMetadataModelByID(ctx, userID, userResourceOwner, metadata.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setMetadata := NewUserMetadataWriteModel(userID, resourceOwner, metadata.Key)
|
||||
userAgg := UserAggregateFromWriteModel(&setMetadata.WriteModel)
|
||||
// return if no change in the metadata
|
||||
if bytes.Equal(setMetadata.Value, metadata.Value) {
|
||||
return writeModelToUserMetadata(setMetadata), nil
|
||||
}
|
||||
|
||||
event, err := c.setUserMetadata(ctx, userAgg, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -40,20 +54,35 @@ func (c *Commands) BulkSetUserMetadata(ctx context.Context, userID, resourceOwne
|
||||
if len(metadatas) == 0 {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "META-9mm2d", "Errors.Metadata.NoData")
|
||||
}
|
||||
err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
userResourceOwner, err := c.checkUserExists(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := make([]eventstore.Command, len(metadatas))
|
||||
setMetadata := NewUserMetadataListWriteModel(userID, resourceOwner)
|
||||
if err := c.checkPermissionUpdateUser(ctx, userResourceOwner, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := make([]eventstore.Command, 0)
|
||||
setMetadata, err := c.getUserMetadataListModelByID(ctx, userID, userResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&setMetadata.WriteModel)
|
||||
for i, data := range metadatas {
|
||||
for _, data := range metadatas {
|
||||
// if no change to metadata no event has to be pushed
|
||||
if existingValue, ok := setMetadata.metadataList[data.Key]; ok && bytes.Equal(existingValue, data.Value) {
|
||||
continue
|
||||
}
|
||||
event, err := c.setUserMetadata(ctx, userAgg, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events[i] = event
|
||||
events = append(events, event)
|
||||
}
|
||||
// no changes for the metadata
|
||||
if len(events) == 0 {
|
||||
return writeModelToObjectDetails(&setMetadata.WriteModel), nil
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
||||
@@ -84,11 +113,16 @@ func (c *Commands) RemoveUserMetadata(ctx context.Context, metadataKey, userID,
|
||||
if metadataKey == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "META-2n0fs", "Errors.Metadata.Invalid")
|
||||
}
|
||||
err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
userResourceOwner, err := c.checkUserExists(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
removeMetadata, err := c.getUserMetadataModelByID(ctx, userID, resourceOwner, metadataKey)
|
||||
|
||||
if err := c.checkPermissionUpdateUser(ctx, userResourceOwner, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
removeMetadata, err := c.getUserMetadataModelByID(ctx, userID, userResourceOwner, metadataKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -116,13 +150,17 @@ func (c *Commands) BulkRemoveUserMetadata(ctx context.Context, userID, resourceO
|
||||
if len(metadataKeys) == 0 {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "META-9mm2d", "Errors.Metadata.NoData")
|
||||
}
|
||||
err = c.checkUserExists(ctx, userID, resourceOwner)
|
||||
userResourceOwner, err := c.checkUserExists(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.checkPermissionUpdateUser(ctx, userResourceOwner, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := make([]eventstore.Command, len(metadataKeys))
|
||||
removeMetadata, err := c.getUserMetadataListModelByID(ctx, userID, resourceOwner)
|
||||
removeMetadata, err := c.getUserMetadataListModelByID(ctx, userID, userResourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -153,24 +191,6 @@ func (c *Commands) BulkRemoveUserMetadata(ctx context.Context, userID, resourceO
|
||||
return writeModelToObjectDetails(&removeMetadata.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeUserMetadataFromOrg(ctx context.Context, resourceOwner string) ([]eventstore.Command, error) {
|
||||
existingUserMetadata, err := c.getUserMetadataByOrgListModelByID(ctx, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(existingUserMetadata.UserMetadata) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
events := make([]eventstore.Command, 0)
|
||||
for key, value := range existingUserMetadata.UserMetadata {
|
||||
if len(value) == 0 {
|
||||
continue
|
||||
}
|
||||
events = append(events, user.NewMetadataRemovedAllEvent(ctx, &user.NewAggregate(key, resourceOwner).Aggregate))
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func (c *Commands) removeUserMetadata(ctx context.Context, userAgg *eventstore.Aggregate, metadataKey string) (command eventstore.Command, err error) {
|
||||
command = user.NewMetadataRemovedEvent(
|
||||
ctx,
|
||||
@@ -197,12 +217,3 @@ func (c *Commands) getUserMetadataListModelByID(ctx context.Context, userID, res
|
||||
}
|
||||
return userMetadataWriteModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) getUserMetadataByOrgListModelByID(ctx context.Context, resourceOwner string) (*UserMetadataByOrgListWriteModel, error) {
|
||||
userMetadataWriteModel := NewUserMetadataByOrgListWriteModel(resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, userMetadataWriteModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return userMetadataWriteModel, nil
|
||||
}
|
||||
|
@@ -16,7 +16,8 @@ import (
|
||||
|
||||
func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
@@ -39,10 +40,10 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -60,8 +61,7 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "invalid metadata, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -78,7 +78,17 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
[]byte("value"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -93,10 +103,9 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add metadata, ok",
|
||||
name: "add metadata, no permission",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -113,6 +122,43 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadata: &domain.Metadata{
|
||||
Key: "key",
|
||||
Value: []byte("value"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add metadata, ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
@@ -121,6 +167,7 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -143,11 +190,116 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add metadata, reset, invalid",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
[]byte("value"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadata: &domain.Metadata{
|
||||
Key: "key",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add metadata, reset, ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
[]byte("value"),
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"key",
|
||||
[]byte("value2"),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadata: &domain.Metadata{
|
||||
Key: "key",
|
||||
Value: []byte("value2"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.Metadata{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "user1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
Key: "key",
|
||||
Value: []byte("value2"),
|
||||
State: domain.MetadataStateActive,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.SetUserMetadata(tt.args.ctx, tt.args.metadata, tt.args.userID, tt.args.orgID)
|
||||
if tt.res.err == nil {
|
||||
@@ -165,7 +317,8 @@ func TestCommandSide_SetUserMetadata(t *testing.T) {
|
||||
|
||||
func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
@@ -188,9 +341,7 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "empty meta data list, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -204,8 +355,7 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -225,8 +375,7 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "invalid metadata, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -243,7 +392,9 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -259,10 +410,9 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add metadata, ok",
|
||||
name: "add metadata, no permission",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -279,6 +429,43 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataList: []*domain.Metadata{
|
||||
{Key: "key", Value: []byte("value")},
|
||||
{Key: "key1", Value: []byte("value1")},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add metadata, ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
user.NewMetadataSetEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
@@ -292,6 +479,7 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -312,7 +500,8 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.BulkSetUserMetadata(tt.args.ctx, tt.args.userID, tt.args.orgID, tt.args.metadataList...)
|
||||
if tt.res.err == nil {
|
||||
@@ -330,7 +519,8 @@ func TestCommandSide_BulkSetUserMetadata(t *testing.T) {
|
||||
|
||||
func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
@@ -353,8 +543,7 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -371,9 +560,7 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
{
|
||||
name: "invalid metadata, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -388,8 +575,7 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
{
|
||||
name: "meta data not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -408,6 +594,7 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
),
|
||||
expectFilter(),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -419,11 +606,43 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
err: zerrors.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove metadata, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataKey: "key",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove metadata, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -456,6 +675,7 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -473,7 +693,8 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.RemoveUserMetadata(tt.args.ctx, tt.args.metadataKey, tt.args.userID, tt.args.orgID)
|
||||
if tt.res.err == nil {
|
||||
@@ -491,7 +712,8 @@ func TestCommandSide_UserRemoveMetadata(t *testing.T) {
|
||||
|
||||
func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
@@ -514,9 +736,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "empty meta data list, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -530,8 +750,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "user not existing, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -548,8 +767,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "remove metadata keys not existing, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -576,6 +794,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -590,8 +809,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
{
|
||||
name: "invalid metadata, pre condition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -625,6 +843,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -636,11 +855,43 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove metadata, no permission",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"",
|
||||
"firstname lastname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email@test.ch",
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
userID: "user1",
|
||||
metadataList: []string{"key", "key1"},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove metadata, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanAddedEvent(context.Background(),
|
||||
@@ -684,6 +935,7 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckAllowed(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -701,7 +953,8 @@ func TestCommandSide_BulkRemoveUserMetadata(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
checkPermission: tt.fields.checkPermission,
|
||||
}
|
||||
got, err := r.BulkRemoveUserMetadata(tt.args.ctx, tt.args.userID, tt.args.orgID, tt.args.metadataList...)
|
||||
if tt.res.err == nil {
|
||||
|
@@ -2,7 +2,6 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -150,7 +149,7 @@ func (c *Commands) RemoveUserV2(ctx context.Context, userID, resourceOwner strin
|
||||
events = append(events, user.NewUserRemovedEvent(ctx, &existingUser.Aggregate().Aggregate, existingUser.UserName, existingUser.IDPLinks, domainPolicy.UserLoginMustBeDomain))
|
||||
|
||||
for _, grantID := range cascadingGrantIDs {
|
||||
removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true)
|
||||
removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true, true, nil)
|
||||
if err != nil {
|
||||
logging.WithFields("usergrantid", grantID).WithError(err).Warn("could not cascade remove role on user grant")
|
||||
continue
|
||||
|
Reference in New Issue
Block a user