fix: new es testing (#1411)

* fix: org tests

* fix: org tests

* fix: user grant test

* fix: user grant test

* fix: project and project role test

* fix: project grant test

* fix: project grant test

* fix: project member, grant member, app changed tests

* fix: application tests

* fix: application tests

* fix: add oidc app test

* fix: add oidc app test

* fix: add api keys test

* fix: iam policies

* fix: iam and org member tests

* fix: clock skew validation

* revert crypto changes

* fix: tests

* fix project grant member commands

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi
2021-03-15 12:51:15 +01:00
committed by GitHub
parent e9eb5b7848
commit 2bd255106a
72 changed files with 14134 additions and 230 deletions

View File

@@ -32,14 +32,17 @@ func (c *Commands) AddIAMMember(ctx context.Context, member *domain.Member) (*do
}
func (c *Commands) addIAMMember(ctx context.Context, iamAgg *eventstore.Aggregate, addedMember *IAMMemberWriteModel, member *domain.Member) (eventstore.EventPusher, error) {
if !member.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-GR34U", "Errors.IAM.MemberInvalid")
if !member.IsIAMValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-GR34U", "Errors.IAM.MemberInvalid")
}
if len(domain.CheckForInvalidRoles(member.Roles, domain.IAMRolePrefix, c.zitadelRoles)) > 0 {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4m0fS", "Errors.IAM.MemberInvalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-4m0fS", "Errors.IAM.MemberInvalid")
}
err := c.eventstore.FilterToQueryReducer(ctx, addedMember)
err := c.checkUserExists(ctx, addedMember.UserID, "")
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "IAM-5N9vs", "Errors.User.NotFound")
}
err = c.eventstore.FilterToQueryReducer(ctx, addedMember)
if err != nil {
return nil, err
}
@@ -52,11 +55,11 @@ func (c *Commands) addIAMMember(ctx context.Context, iamAgg *eventstore.Aggregat
//ChangeIAMMember updates an existing member
func (c *Commands) ChangeIAMMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
if !member.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-LiaZi", "Errors.IAM.MemberInvalid")
if !member.IsIAMValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-LiaZi", "Errors.IAM.MemberInvalid")
}
if len(domain.CheckForInvalidRoles(member.Roles, domain.IAMRolePrefix, c.zitadelRoles)) > 0 {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-3m9fs", "Errors.IAM.MemberInvalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-3m9fs", "Errors.IAM.MemberInvalid")
}
existingMember, err := c.iamMemberWriteModelByID(ctx, member.UserID)
@@ -81,6 +84,9 @@ func (c *Commands) ChangeIAMMember(ctx context.Context, member *domain.Member) (
}
func (c *Commands) RemoveIAMMember(ctx context.Context, userID string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-LiaZi", "Errors.IDMissing")
}
memberWriteModel, err := c.iamMemberWriteModelByID(ctx, userID)
if err != nil && !errors.IsNotFound(err) {
return nil, err

View File

@@ -0,0 +1,552 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/member"
"github.com/caos/zitadel/internal/repository/user"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"testing"
)
func TestCommandSide_AddIAMMember(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
zitadelRoles []authz.RoleMapping
}
type args struct {
ctx context.Context
member *domain.Member
}
type res struct {
want *domain.Member
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid member, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.Member{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid roles, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.Member{
UserID: "user1",
Roles: []string{"IAM_OWNER"},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
zitadelRoles: []authz.RoleMapping{
{
Role: "IAM_OWNER",
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
UserID: "user1",
Roles: []string{"IAM_OWNER"},
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "member already exists, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilter(
eventFromEventPusher(
iam.NewMemberAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"user1",
),
),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: "IAM_OWNER",
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
UserID: "user1",
Roles: []string{"IAM_OWNER"},
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "member add uniqueconstraint err, already exists",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilter(),
expectPushFailed(caos_errs.ThrowAlreadyExists(nil, "ERROR", "internal"),
[]*repository.Event{
eventFromEventPusher(iam.NewMemberAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"user1",
[]string{"IAM_OWNER"}...,
)),
},
uniqueConstraintsFromEventConstraint(member.NewAddMemberUniqueConstraint("IAM", "user1")),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: "IAM_OWNER",
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
UserID: "user1",
Roles: []string{"IAM_OWNER"},
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "member add, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(iam.NewMemberAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"user1",
[]string{"IAM_OWNER"}...,
)),
},
uniqueConstraintsFromEventConstraint(member.NewAddMemberUniqueConstraint("IAM", "user1")),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: "IAM_OWNER",
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
UserID: "user1",
Roles: []string{"IAM_OWNER"},
},
},
res: res{
want: &domain.Member{
ObjectRoot: models.ObjectRoot{
ResourceOwner: "IAM",
AggregateID: "IAM",
},
UserID: "user1",
Roles: []string{"IAM_OWNER"},
},
},
},
}
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.AddIAMMember(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_ChangeIAMMember(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
zitadelRoles []authz.RoleMapping
}
type args struct {
ctx context.Context
member *domain.Member
}
type res struct {
want *domain.Member
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid member, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.Member{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid roles, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.Member{
UserID: "user1",
Roles: []string{"IAM_OWNER"},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "member not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
zitadelRoles: []authz.RoleMapping{
{
Role: "IAM_OWNER",
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
UserID: "user1",
Roles: []string{"IAM_OWNER"},
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "member not changed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewMemberAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"user1",
[]string{"IAM_OWNER"}...,
),
),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: domain.RoleIAMOwner,
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
UserID: "user1",
Roles: []string{"IAM_OWNER"},
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "member change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewMemberAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"user1",
[]string{"IAM_OWNER"}...,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(iam.NewMemberChangedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"user1",
[]string{"IAM_OWNER", "IAM_OWNER_VIEWER"}...,
)),
},
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: "IAM_OWNER",
},
{
Role: "IAM_OWNER_VIEWER",
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
UserID: "user1",
Roles: []string{"IAM_OWNER", "IAM_OWNER_VIEWER"},
},
},
res: res{
want: &domain.Member{
ObjectRoot: models.ObjectRoot{
ResourceOwner: "IAM",
AggregateID: "IAM",
},
UserID: "user1",
Roles: []string{"IAM_OWNER", "IAM_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.ChangeIAMMember(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_RemoveIAMMember(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
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 userid missing, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
userID: "",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "member not existing, nil result",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
},
res: res{
want: nil,
},
},
{
name: "member remove, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewMemberAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"user1",
[]string{"IAM_OWNER"}...,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(iam.NewMemberRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"user1",
)),
},
uniqueConstraintsFromEventConstraint(member.NewRemoveMemberUniqueConstraint("IAM", "user1")),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemoveIAMMember(tt.args.ctx, tt.args.userID)
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)
}
})
}
}

View File

@@ -29,6 +29,9 @@ func (c *Commands) AddDefaultLabelPolicy(ctx context.Context, policy *domain.Lab
}
func (c *Commands) addDefaultLabelPolicy(ctx context.Context, iamAgg *eventstore.Aggregate, addedPolicy *IAMLabelPolicyWriteModel, policy *domain.LabelPolicy) (eventstore.EventPusher, error) {
if !policy.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-3m9fo", "Errors.IAM.LabelPolicy.Invalid")
}
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
return nil, err
@@ -42,6 +45,9 @@ func (c *Commands) addDefaultLabelPolicy(ctx context.Context, iamAgg *eventstore
}
func (c *Commands) ChangeDefaultLabelPolicy(ctx context.Context, policy *domain.LabelPolicy) (*domain.LabelPolicy, error) {
if !policy.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-33m8f", "Errors.IAM.LabelPolicy.Invalid")
}
existingPolicy, err := c.defaultLabelPolicyWriteModelByID(ctx)
if err != nil {
return nil, err

View File

@@ -0,0 +1,288 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/policy"
"github.com/stretchr/testify/assert"
"testing"
)
func TestCommandSide_AddDefaultLabelPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.LabelPolicy
}
type res struct {
want *domain.LabelPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "labelpolicy invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.LabelPolicy{
PrimaryColor: "",
SecondaryColor: "secondary-color",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "labelpolicy already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLabelPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"primary-color",
"secondary-color",
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewLabelPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"primary-color",
"secondary-color",
),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
},
},
res: res{
want: &domain.LabelPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddDefaultLabelPolicy(tt.args.ctx, tt.args.policy)
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_ChangeDefaultLabelPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.LabelPolicy
}
type res struct {
want *domain.LabelPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "labelpolicy invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.LabelPolicy{
PrimaryColor: "",
SecondaryColor: "secondary-color",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "labelpolicy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLabelPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"primary-color",
"secondary-color",
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLabelPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"primary-color",
"secondary-color",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultLabelPolicyChangedEvent(context.Background(), "primary-color-change", "secondary-color-change"),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color-change",
SecondaryColor: "secondary-color-change",
},
},
res: res{
want: &domain.LabelPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
PrimaryColor: "primary-color-change",
SecondaryColor: "secondary-color-change",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeDefaultLabelPolicy(tt.args.ctx, tt.args.policy)
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 newDefaultLabelPolicyChangedEvent(ctx context.Context, primaryColor, secondaryColor string) *iam.LabelPolicyChangedEvent {
event, _ := iam.NewLabelPolicyChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
[]policy.LabelPolicyChanges{
policy.ChangePrimaryColor(primaryColor),
policy.ChangeSecondaryColor(secondaryColor),
},
)
return event
}

View File

@@ -28,11 +28,14 @@ func (c *Commands) AddDefaultLoginPolicy(ctx context.Context, policy *domain.Log
if err != nil {
return nil, err
}
_, err = c.eventstore.PushEvents(ctx, event)
pushedEvents, err := c.eventstore.PushEvents(ctx, event)
if err != nil {
return nil, err
}
err = AppendAndReduce(addedPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToLoginPolicy(&addedPolicy.LoginPolicyWriteModel), nil
}

View File

@@ -0,0 +1,283 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/policy"
"github.com/stretchr/testify/assert"
"testing"
)
func TestCommandSide_AddDefaultLoginPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.LoginPolicy
}
type res struct {
want *domain.LoginPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "loginpolicy already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
true,
true,
false,
false,
domain.PasswordlessTypeAllowed,
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.LoginPolicy{
AllowRegister: true,
AllowUsernamePassword: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewLoginPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.LoginPolicy{
AllowRegister: true,
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
},
},
res: res{
want: &domain.LoginPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
AllowRegister: true,
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddDefaultLoginPolicy(tt.args.ctx, tt.args.policy)
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_ChangeDefaultLoginPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.LoginPolicy
}
type res struct {
want *domain.LoginPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "loginpolicy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
policy: &domain.LoginPolicy{
AllowRegister: true,
AllowExternalIDP: true,
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.LoginPolicy{
AllowRegister: true,
AllowUsernamePassword: true,
AllowExternalIDP: true,
ForceMFA: true,
PasswordlessType: domain.PasswordlessTypeAllowed,
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultLoginPolicyChangedEvent(context.Background(), false, false, false, false, domain.PasswordlessTypeNotAllowed),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.LoginPolicy{
AllowRegister: false,
AllowUsernamePassword: false,
AllowExternalIDP: false,
ForceMFA: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
},
},
res: res{
want: &domain.LoginPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
AllowRegister: false,
AllowUsernamePassword: false,
AllowExternalIDP: false,
ForceMFA: false,
PasswordlessType: domain.PasswordlessTypeNotAllowed,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeDefaultLoginPolicy(tt.args.ctx, tt.args.policy)
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 newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA bool, passwordlessType domain.PasswordlessType) *iam.LoginPolicyChangedEvent {
event, _ := iam.NewLoginPolicyChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
[]policy.LoginPolicyChanges{
policy.ChangeAllowRegister(allowRegister),
policy.ChangeAllowExternalIDP(allowExternalIDP),
policy.ChangeForceMFA(forceMFA),
policy.ChangeAllowUserNamePassword(allowUsernamePassword),
policy.ChangePasswordlessType(passwordlessType),
},
)
return event
}

View File

@@ -30,7 +30,7 @@ func (c *Commands) AddDefaultMailTemplate(ctx context.Context, policy *domain.Ma
func (c *Commands) addDefaultMailTemplate(ctx context.Context, iamAgg *eventstore.Aggregate, addedPolicy *IAMMailTemplateWriteModel, policy *domain.MailTemplate) (eventstore.EventPusher, error) {
if !policy.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-fm9sd", "Errors.IAM.MailTemplate.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-fm9sd", "Errors.IAM.MailTemplate.Invalid")
}
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
@@ -45,7 +45,7 @@ func (c *Commands) addDefaultMailTemplate(ctx context.Context, iamAgg *eventstor
func (c *Commands) ChangeDefaultMailTemplate(ctx context.Context, policy *domain.MailTemplate) (*domain.MailTemplate, error) {
if !policy.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4m9ds", "Errors.IAM.MailTemplate.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-4m9ds", "Errors.IAM.MailTemplate.Invalid")
}
existingPolicy, err := c.defaultMailTemplateWriteModelByID(ctx)
if err != nil {

View File

@@ -0,0 +1,270 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/policy"
"github.com/stretchr/testify/assert"
"testing"
)
func TestCommandSide_AddDefaultMailTemplatePolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.MailTemplate
}
type res struct {
want *domain.MailTemplate
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "mailtemplate invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailTemplate{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mailtemplate already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewMailTemplateAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
[]byte("template"),
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailTemplate{
Template: []byte("template"),
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add mail template,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewMailTemplateAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
[]byte("template"),
),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailTemplate{
Template: []byte("template"),
},
},
res: res{
want: &domain.MailTemplate{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
Template: []byte("template"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddDefaultMailTemplate(tt.args.ctx, tt.args.policy)
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_ChangeDefaultMailTemplatePolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.MailTemplate
}
type res struct {
want *domain.MailTemplate
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "mailtemplate invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailTemplate{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mailtempalte not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailTemplate{
Template: []byte("template-change"),
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewMailTemplateAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
[]byte("template"),
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailTemplate{
Template: []byte("template"),
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewMailTemplateAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
[]byte("template"),
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultMailTemplatePolicyChangedEvent(context.Background(), []byte("template-change")),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailTemplate{
Template: []byte("template-change"),
},
},
res: res{
want: &domain.MailTemplate{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
Template: []byte("template-change"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeDefaultMailTemplate(tt.args.ctx, tt.args.policy)
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 newDefaultMailTemplatePolicyChangedEvent(ctx context.Context, template []byte) *iam.MailTemplateChangedEvent {
event, _ := iam.NewMailTemplateChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
[]policy.MailTemplateChanges{
policy.ChangeTemplate(template),
},
)
return event
}

View File

@@ -30,7 +30,7 @@ func (c *Commands) AddDefaultMailText(ctx context.Context, policy *domain.MailTe
func (c *Commands) addDefaultMailText(ctx context.Context, iamAgg *eventstore.Aggregate, addedPolicy *IAMMailTextWriteModel, mailText *domain.MailText) (eventstore.EventPusher, error) {
if !mailText.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-3n8fs", "Errors.IAM.MailText.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-3n8fs", "Errors.IAM.MailText.Invalid")
}
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
@@ -55,7 +55,7 @@ func (c *Commands) addDefaultMailText(ctx context.Context, iamAgg *eventstore.Ag
func (c *Commands) ChangeDefaultMailText(ctx context.Context, mailText *domain.MailText) (*domain.MailText, error) {
if !mailText.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-kd9fs", "Errors.IAM.MailText.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-kd9fs", "Errors.IAM.MailText.Invalid")
}
existingPolicy, err := c.defaultMailTextWriteModelByID(ctx, mailText.MailTextType, mailText.Language)
if err != nil {

View File

@@ -0,0 +1,366 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/policy"
"github.com/stretchr/testify/assert"
"testing"
)
func TestCommandSide_AddDefaultMailTextPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.MailText
}
type res struct {
want *domain.MailText
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "mail text invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mail text already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewMailTextAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add mail template,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewMailTextAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
},
uniqueConstraintsFromEventConstraint(policy.NewAddMailTextUniqueConstraint("IAM", "mail-text-type", "de")),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
want: &domain.MailText{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
State: domain.PolicyStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddDefaultMailText(tt.args.ctx, tt.args.policy)
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_ChangeDefaultMailTextPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.MailText
}
type res struct {
want *domain.MailText
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "mailtext invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mail text not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewMailTextAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewMailTextAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultMailTextPolicyChangedEvent(
context.Background(),
"mail-text-type",
"de",
"title-change",
"pre-header-change",
"subject-change",
"greeting-change",
"text-change",
"button-text-change"),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title-change",
PreHeader: "pre-header-change",
Subject: "subject-change",
Greeting: "greeting-change",
Text: "text-change",
ButtonText: "button-text-change",
},
},
res: res{
want: &domain.MailText{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
MailTextType: "mail-text-type",
Language: "de",
Title: "title-change",
PreHeader: "pre-header-change",
Subject: "subject-change",
Greeting: "greeting-change",
Text: "text-change",
ButtonText: "button-text-change",
State: domain.PolicyStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeDefaultMailText(tt.args.ctx, tt.args.policy)
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 newDefaultMailTextPolicyChangedEvent(ctx context.Context, mailTextType, language, title, preHeader, subject, greeting, text, buttonText string) *iam.MailTextChangedEvent {
event, _ := iam.NewMailTextChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
mailTextType,
language,
[]policy.MailTextChanges{
policy.ChangeTitle(title),
policy.ChangePreHeader(preHeader),
policy.ChangeSubject(subject),
policy.ChangeGreeting(greeting),
policy.ChangeText(text),
policy.ChangeButtonText(buttonText),
},
)
return event
}

View File

@@ -0,0 +1,240 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/policy"
"github.com/stretchr/testify/assert"
"testing"
)
func TestCommandSide_AddDefaultOrgIAMPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.OrgIAMPolicy
}
type res struct {
want *domain.OrgIAMPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "orgiam policy already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewOrgIAMPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.OrgIAMPolicy{
UserLoginMustBeDomain: true,
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewOrgIAMPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
true,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.OrgIAMPolicy{
UserLoginMustBeDomain: true,
},
},
res: res{
want: &domain.OrgIAMPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
UserLoginMustBeDomain: true,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddDefaultOrgIAMPolicy(tt.args.ctx, tt.args.policy)
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_ChangeDefaultOrgIAMPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.OrgIAMPolicy
}
type res struct {
want *domain.OrgIAMPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "orgiampolicy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
policy: &domain.OrgIAMPolicy{
UserLoginMustBeDomain: true,
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewOrgIAMPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.OrgIAMPolicy{
UserLoginMustBeDomain: true,
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewOrgIAMPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultOrgIAMPolicyChangedEvent(context.Background(), false),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.OrgIAMPolicy{
UserLoginMustBeDomain: false,
},
},
res: res{
want: &domain.OrgIAMPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
UserLoginMustBeDomain: false,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeDefaultOrgIAMPolicy(tt.args.ctx, tt.args.policy)
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 newDefaultOrgIAMPolicyChangedEvent(ctx context.Context, userLoginMustBeDomain bool) *iam.OrgIAMPolicyChangedEvent {
event, _ := iam.NewOrgIAMPolicyChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
[]policy.OrgIAMPolicyChanges{
policy.ChangeUserLoginMustBeDomain(userLoginMustBeDomain),
},
)
return event
}

View File

@@ -0,0 +1,252 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/policy"
"github.com/stretchr/testify/assert"
"testing"
)
func TestCommandSide_AddDefaultPasswordAgePolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.PasswordAgePolicy
}
type res struct {
want *domain.PasswordAgePolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "password age policy already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewPasswordAgePolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
365,
10,
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordAgePolicy{
MaxAgeDays: 365,
ExpireWarnDays: 10,
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewPasswordAgePolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
365,
10,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordAgePolicy{
ExpireWarnDays: 365,
MaxAgeDays: 10,
},
},
res: res{
want: &domain.PasswordAgePolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
ExpireWarnDays: 365,
MaxAgeDays: 10,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddDefaultPasswordAgePolicy(tt.args.ctx, tt.args.policy)
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_ChangeDefaultPasswordAgePolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.PasswordAgePolicy
}
type res struct {
want *domain.PasswordAgePolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "password age policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordAgePolicy{
MaxAgeDays: 365,
ExpireWarnDays: 10,
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewPasswordAgePolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
365,
10,
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordAgePolicy{
ExpireWarnDays: 365,
MaxAgeDays: 10,
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewPasswordAgePolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
365,
10,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultPasswordAgePolicyChangedEvent(context.Background(), 125, 5),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordAgePolicy{
MaxAgeDays: 125,
ExpireWarnDays: 5,
},
},
res: res{
want: &domain.PasswordAgePolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
MaxAgeDays: 125,
ExpireWarnDays: 5,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeDefaultPasswordAgePolicy(tt.args.ctx, tt.args.policy)
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 newDefaultPasswordAgePolicyChangedEvent(ctx context.Context, maxAgeDays, expiryWarnDays uint64) *iam.PasswordAgePolicyChangedEvent {
event, _ := iam.NewPasswordAgePolicyChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
[]policy.PasswordAgePolicyChanges{
policy.ChangeExpireWarnDays(expiryWarnDays),
policy.ChangeMaxAgeDays(maxAgeDays),
},
)
return event
}

View File

@@ -0,0 +1,318 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/policy"
"github.com/stretchr/testify/assert"
"testing"
)
func TestCommandSide_AddDefaultPasswordComplexityPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.PasswordComplexityPolicy
}
type res struct {
want *domain.PasswordComplexityPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid policy, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordComplexityPolicy{
MinLength: 0,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "password complexity policy already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
8,
true, true, true, true,
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordComplexityPolicy{
MinLength: 8,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
8,
true, true, true, true,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordComplexityPolicy{
MinLength: 8,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
},
},
res: res{
want: &domain.PasswordComplexityPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
MinLength: 8,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddDefaultPasswordComplexityPolicy(tt.args.ctx, tt.args.policy)
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_ChangeDefaultPasswordComplexityPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.PasswordComplexityPolicy
}
type res struct {
want *domain.PasswordComplexityPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid policy, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordComplexityPolicy{
MinLength: 0,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "password complexity policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordComplexityPolicy{
MinLength: 8,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
8,
true, true, true, true,
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordComplexityPolicy{
MinLength: 8,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
8,
true, true, true, true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultPasswordComplexityPolicyChangedEvent(context.Background(), 10, false, false, false, false),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordComplexityPolicy{
MinLength: 10,
HasUppercase: false,
HasLowercase: false,
HasNumber: false,
HasSymbol: false,
},
},
res: res{
want: &domain.PasswordComplexityPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
MinLength: 10,
HasUppercase: false,
HasLowercase: false,
HasNumber: false,
HasSymbol: false,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeDefaultPasswordComplexityPolicy(tt.args.ctx, tt.args.policy)
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 newDefaultPasswordComplexityPolicyChangedEvent(ctx context.Context, minLength uint64, hasUpper, hasLower, hasNumber, hasSymbol bool) *iam.PasswordComplexityPolicyChangedEvent {
event, _ := iam.NewPasswordComplexityPolicyChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
[]policy.PasswordComplexityPolicyChanges{
policy.ChangeMinLength(minLength),
policy.ChangeHasUppercase(hasUpper),
policy.ChangeHasLowercase(hasLower),
policy.ChangeHasSymbol(hasNumber),
policy.ChangeHasNumber(hasSymbol),
},
)
return event
}

View File

@@ -0,0 +1,252 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/policy"
"github.com/stretchr/testify/assert"
"testing"
)
func TestCommandSide_AddDefaultPasswordLockoutPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.PasswordLockoutPolicy
}
type res struct {
want *domain.PasswordLockoutPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "password lockout policy already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewPasswordLockoutPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
10,
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
ShowLockOutFailures: true,
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewPasswordLockoutPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
10,
true,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
ShowLockOutFailures: true,
},
},
res: res{
want: &domain.PasswordLockoutPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
MaxAttempts: 10,
ShowLockOutFailures: true,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddDefaultPasswordLockoutPolicy(tt.args.ctx, tt.args.policy)
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_ChangeDefaultPasswordLockoutPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.PasswordLockoutPolicy
}
type res struct {
want *domain.PasswordLockoutPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "password lockout policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
ShowLockOutFailures: true,
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewPasswordLockoutPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
10,
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
ShowLockOutFailures: true,
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewPasswordLockoutPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
10,
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultPasswordLockoutPolicyChangedEvent(context.Background(), 20, false),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 20,
ShowLockOutFailures: false,
},
},
res: res{
want: &domain.PasswordLockoutPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
MaxAttempts: 20,
ShowLockOutFailures: false,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeDefaultPasswordLockoutPolicy(tt.args.ctx, tt.args.policy)
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 newDefaultPasswordLockoutPolicyChangedEvent(ctx context.Context, maxAttempts uint64, showLockoutFailure bool) *iam.PasswordLockoutPolicyChangedEvent {
event, _ := iam.NewPasswordLockoutPolicyChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
[]policy.PasswordLockoutPolicyChanges{
policy.ChangeMaxAttempts(maxAttempts),
policy.ChangeShowLockOutFailures(showLockoutFailure),
},
)
return event
}

View File

@@ -0,0 +1,168 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/repository/mock"
iam_repo "github.com/caos/zitadel/internal/repository/iam"
key_repo "github.com/caos/zitadel/internal/repository/keypair"
"github.com/caos/zitadel/internal/repository/org"
proj_repo "github.com/caos/zitadel/internal/repository/project"
usr_repo "github.com/caos/zitadel/internal/repository/user"
"github.com/caos/zitadel/internal/repository/usergrant"
"github.com/golang/mock/gomock"
"testing"
"time"
)
type expect func(mockRepository *mock.MockRepository)
func eventstoreExpect(t *testing.T, expects ...expect) *eventstore.Eventstore {
m := mock.NewRepo(t)
for _, e := range expects {
e(m)
}
es := eventstore.NewEventstore(m)
iam_repo.RegisterEventMappers(es)
org.RegisterEventMappers(es)
usr_repo.RegisterEventMappers(es)
proj_repo.RegisterEventMappers(es)
usergrant.RegisterEventMappers(es)
key_repo.RegisterEventMappers(es)
return es
}
func eventPusherToEvents(eventsPushes ...eventstore.EventPusher) []*repository.Event {
events := make([]*repository.Event, len(eventsPushes))
for i, event := range eventsPushes {
data, err := eventstore.EventData(event)
if err != nil {
return nil
}
events[i] = &repository.Event{
AggregateID: event.Aggregate().ID,
AggregateType: repository.AggregateType(event.Aggregate().Typ),
ResourceOwner: event.Aggregate().ResourceOwner,
EditorService: event.EditorService(),
EditorUser: event.EditorUser(),
Type: repository.EventType(event.Type()),
Version: repository.Version(event.Aggregate().Version),
Data: data,
}
}
return events
}
type testRepo struct {
events []*repository.Event
uniqueConstraints []*repository.UniqueConstraint
sequence uint64
err error
t *testing.T
}
func (repo *testRepo) Health(ctx context.Context) error {
return nil
}
func (repo *testRepo) Push(ctx context.Context, events []*repository.Event, uniqueConstraints ...*repository.UniqueConstraint) error {
repo.events = append(repo.events, events...)
repo.uniqueConstraints = append(repo.uniqueConstraints, uniqueConstraints...)
return nil
}
func (repo *testRepo) Filter(ctx context.Context, searchQuery *repository.SearchQuery) ([]*repository.Event, error) {
events := make([]*repository.Event, 0, len(repo.events))
for _, event := range repo.events {
for _, filter := range searchQuery.Filters {
if filter.Field == repository.FieldAggregateType {
if event.AggregateType != filter.Value {
continue
}
}
}
events = append(events, event)
}
return repo.events, nil
}
func filterAggregateType(aggregateType string) {
}
func (repo *testRepo) LatestSequence(ctx context.Context, queryFactory *repository.SearchQuery) (uint64, error) {
if repo.err != nil {
return 0, repo.err
}
return repo.sequence, nil
}
func expectPush(events []*repository.Event, uniqueConstraints ...*repository.UniqueConstraint) expect {
return func(m *mock.MockRepository) {
m.ExpectPush(events, uniqueConstraints...)
}
}
func expectPushFailed(err error, events []*repository.Event, uniqueConstraints ...*repository.UniqueConstraint) expect {
return func(m *mock.MockRepository) {
m.ExpectPushFailed(err, events, uniqueConstraints...)
}
}
func expectFilter(events ...*repository.Event) expect {
return func(m *mock.MockRepository) {
m.ExpectFilterEvents(events...)
}
}
func expectFilterOrgDomainNotFound() expect {
return func(m *mock.MockRepository) {
m.ExpectFilterNoEventsNoError()
}
}
func expectFilterOrgMemberNotFound() expect {
return func(m *mock.MockRepository) {
m.ExpectFilterNoEventsNoError()
}
}
func eventFromEventPusher(event eventstore.EventPusher) *repository.Event {
data, _ := eventstore.EventData(event)
return &repository.Event{
ID: "",
Sequence: 0,
PreviousSequence: 0,
CreationDate: time.Time{},
Type: repository.EventType(event.Type()),
Data: data,
EditorService: event.EditorService(),
EditorUser: event.EditorUser(),
Version: repository.Version(event.Aggregate().Version),
AggregateID: event.Aggregate().ID,
AggregateType: repository.AggregateType(event.Aggregate().Typ),
ResourceOwner: event.Aggregate().ResourceOwner,
}
}
func uniqueConstraintsFromEventConstraint(constraint *eventstore.EventUniqueConstraint) *repository.UniqueConstraint {
return &repository.UniqueConstraint{
UniqueType: constraint.UniqueType,
UniqueField: constraint.UniqueField,
ErrorMessage: constraint.ErrorMessage,
Action: repository.UniqueConstraintAction(constraint.Action)}
}
func GetMockSecretGenerator(t *testing.T) crypto.Generator {
ctrl := gomock.NewController(t)
alg := crypto.CreateMockEncryptionAlg(ctrl)
generator := crypto.NewMockGenerator(ctrl)
generator.EXPECT().Length().Return(uint(1)).AnyTimes()
generator.EXPECT().Runes().Return([]rune("aa")).AnyTimes()
generator.EXPECT().Alg().Return(alg).AnyTimes()
generator.EXPECT().Expiry().Return(time.Hour * 1).AnyTimes()
return generator
}

View File

@@ -82,7 +82,7 @@ func (c *Commands) DeactivateOrg(ctx context.Context, orgID string) (*domain.Obj
return nil, caos_errs.ThrowNotFound(nil, "ORG-oL9nT", "Errors.Org.NotFound")
}
if orgWriteModel.State == domain.OrgStateInactive {
return nil, caos_errs.ThrowInvalidArgument(nil, "EVENT-Dbs2g", "Errors.Org.AlreadyDeactivated")
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Dbs2g", "Errors.Org.AlreadyDeactivated")
}
orgAgg := OrgAggregateFromWriteModel(&orgWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewOrgDeactivatedEvent(ctx, orgAgg))
@@ -105,7 +105,7 @@ func (c *Commands) ReactivateOrg(ctx context.Context, orgID string) (*domain.Obj
return nil, caos_errs.ThrowNotFound(nil, "ORG-Dgf3g", "Errors.Org.NotFound")
}
if orgWriteModel.State == domain.OrgStateActive {
return nil, caos_errs.ThrowInvalidArgument(nil, "EVENT-bfnrh", "Errors.Org.AlreadyActive")
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-bfnrh", "Errors.Org.AlreadyActive")
}
orgAgg := OrgAggregateFromWriteModel(&orgWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewOrgReactivatedEvent(ctx, orgAgg))

View File

@@ -19,7 +19,6 @@ func (c *Commands) AddOrgMember(ctx context.Context, member *domain.Member) (*do
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx, event)
if err != nil {
return nil, err
@@ -33,13 +32,16 @@ func (c *Commands) AddOrgMember(ctx context.Context, member *domain.Member) (*do
func (c *Commands) addOrgMember(ctx context.Context, orgAgg *eventstore.Aggregate, addedMember *OrgMemberWriteModel, member *domain.Member) (eventstore.EventPusher, error) {
if !member.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-W8m4l", "Errors.Org.MemberInvalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-W8m4l", "Errors.Org.MemberInvalid")
}
if len(domain.CheckForInvalidRoles(member.Roles, domain.OrgRolePrefix, c.zitadelRoles)) > 0 {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-3m9fs", "Errors.Org.MemberInvalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-3m9fs", "Errors.Org.MemberInvalid")
}
err := c.eventstore.FilterToQueryReducer(ctx, addedMember)
err := c.checkUserExists(ctx, addedMember.UserID, "")
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "ORG-9cmsd", "Errors.User.NotFound")
}
err = c.eventstore.FilterToQueryReducer(ctx, addedMember)
if err != nil {
return nil, err
}
@@ -53,10 +55,10 @@ func (c *Commands) addOrgMember(ctx context.Context, orgAgg *eventstore.Aggregat
//ChangeOrgMember updates an existing member
func (c *Commands) ChangeOrgMember(ctx context.Context, member *domain.Member) (*domain.Member, error) {
if !member.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-LiaZi", "Errors.Org.MemberInvalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-LiaZi", "Errors.Org.MemberInvalid")
}
if len(domain.CheckForInvalidRoles(member.Roles, domain.OrgRolePrefix, c.zitadelRoles)) > 0 {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-m9fG8", "Errors.Org.MemberInvalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-m9fG8", "Errors.Org.MemberInvalid")
}
existingMember, err := c.orgMemberWriteModelByID(ctx, member.AggregateID, member.UserID)

View File

@@ -0,0 +1,615 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/member"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/project"
"github.com/caos/zitadel/internal/repository/user"
)
func TestCommandSide_AddOrgMember(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
zitadelRoles []authz.RoleMapping
}
type args struct {
ctx context.Context
member *domain.Member
}
type res struct {
want *domain.Member
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid member, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid roles, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
},
UserID: "user1",
Roles: []string{"ORG_OWNER"},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
zitadelRoles: []authz.RoleMapping{
{
Role: domain.RoleOrgOwner,
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
},
UserID: "user1",
Roles: []string{domain.RoleOrgOwner},
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "member already exists, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewMemberAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"user1",
),
),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: domain.RoleOrgOwner,
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
},
UserID: "user1",
Roles: []string{"ORG_OWNER"},
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "member add uniqueconstraint err, already exists",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilter(),
expectPushFailed(caos_errs.ThrowAlreadyExists(nil, "ERROR", "internal"),
[]*repository.Event{
eventFromEventPusher(org.NewMemberAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"user1",
[]string{"ORG_OWNER"}...,
)),
},
uniqueConstraintsFromEventConstraint(member.NewAddMemberUniqueConstraint("org1", "user1")),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: domain.RoleOrgOwner,
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
},
UserID: "user1",
Roles: []string{"ORG_OWNER"},
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "member add, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(org.NewMemberAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"user1",
[]string{"ORG_OWNER"}...,
)),
},
uniqueConstraintsFromEventConstraint(member.NewAddMemberUniqueConstraint("org1", "user1")),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: domain.RoleOrgOwner,
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "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},
},
},
},
}
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.AddOrgMember(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_ChangeOrgMember(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
zitadelRoles []authz.RoleMapping
}
type args struct {
ctx context.Context
member *domain.Member
}
type res struct {
want *domain.Member
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid member, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid roles, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
},
UserID: "user1",
Roles: []string{"PROJECT_OWNER"},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "member not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
zitadelRoles: []authz.RoleMapping{
{
Role: domain.RoleOrgOwner,
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
},
UserID: "user1",
Roles: []string{"ORG_OWNER"},
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "member not changed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMemberAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"user1",
[]string{"ORG_OWNER"}...,
),
),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: domain.RoleOrgOwner,
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
},
UserID: "user1",
Roles: []string{"ORG_OWNER"},
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "member change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMemberAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"user1",
[]string{"ORG_OWNER"}...,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(org.NewMemberChangedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"user1",
[]string{"ORG_OWNER", "ORG_OWNER_VIEWER"}...,
)),
},
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: "ORG_OWNER",
},
{
Role: "ORG_OWNER_VIEWER",
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "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: caos_errs.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: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "member not existing, nil result",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
userID: "user1",
resourceOwner: "org1",
},
res: res{
want: nil,
},
},
{
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(
[]*repository.Event{
eventFromEventPusher(project.NewProjectMemberRemovedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
)),
},
uniqueConstraintsFromEventConstraint(member.NewRemoveMemberUniqueConstraint("project1", "user1")),
),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
userID: "user1",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemoveProjectMember(tt.args.ctx, tt.args.projectID, tt.args.userID, tt.args.resourceOwner)
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)
}
})
}
}

View File

@@ -29,6 +29,8 @@ func (wm *OrgWriteModel) Reduce() error {
case *org.OrgAddedEvent:
wm.Name = e.Name
wm.State = domain.OrgStateActive
case *org.OrgDeactivatedEvent:
wm.State = domain.OrgStateInactive
case *org.OrgChangedEvent:
wm.Name = e.Name
case *org.DomainPrimarySetEvent:

View File

@@ -0,0 +1,663 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/id"
id_mock "github.com/caos/zitadel/internal/id/mock"
"github.com/caos/zitadel/internal/repository/member"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/user"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"testing"
)
func TestCommandSide_AddOrg(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
iamDomain string
zitadelRoles []authz.RoleMapping
}
type args struct {
ctx context.Context
name string
userID string
resourceOwner string
}
type res struct {
want *domain.Org
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid org, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user removed, error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilterOrgDomainNotFound(),
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
eventFromEventPusher(
user.NewUserRemovedEvent(
context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
true,
),
),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "org2"),
},
args: args{
ctx: context.Background(),
name: "Org",
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "push failed unique constraint, error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilterOrgDomainNotFound(),
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilterOrgMemberNotFound(),
expectPushFailed(caos_errs.ThrowAlreadyExists(nil, "id", "internal"),
[]*repository.Event{
eventFromEventPusher(org.NewOrgAddedEvent(
context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
"Org")),
eventFromEventPusher(org.NewDomainAddedEvent(
context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
"org.iam-domain")),
eventFromEventPusher(org.NewDomainVerifiedEvent(
context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
"org.iam-domain")),
eventFromEventPusher(org.NewDomainPrimarySetEvent(
context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
"org.iam-domain")),
eventFromEventPusher(org.NewMemberAddedEvent(
context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
"user1", domain.RoleOrgOwner)),
},
uniqueConstraintsFromEventConstraint(org.NewAddOrgNameUniqueConstraint("Org")),
uniqueConstraintsFromEventConstraint(org.NewAddOrgDomainUniqueConstraint("org.iam-domain")),
uniqueConstraintsFromEventConstraint(member.NewAddMemberUniqueConstraint("org2", "user1")),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "org2"),
iamDomain: "iam-domain",
zitadelRoles: []authz.RoleMapping{
{
Role: "ORG_OWNER",
},
},
},
args: args{
ctx: context.Background(),
name: "Org",
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "push failed, error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilterOrgDomainNotFound(),
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilterOrgMemberNotFound(),
expectPushFailed(caos_errs.ThrowInternal(nil, "id", "internal"),
[]*repository.Event{
eventFromEventPusher(org.NewOrgAddedEvent(
context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
"Org")),
eventFromEventPusher(org.NewDomainAddedEvent(
context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
"org.iam-domain")),
eventFromEventPusher(org.NewDomainVerifiedEvent(
context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
"org.iam-domain")),
eventFromEventPusher(org.NewDomainPrimarySetEvent(
context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
"org.iam-domain")),
eventFromEventPusher(org.NewMemberAddedEvent(
context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
"user1", domain.RoleOrgOwner)),
},
uniqueConstraintsFromEventConstraint(org.NewAddOrgNameUniqueConstraint("Org")),
uniqueConstraintsFromEventConstraint(org.NewAddOrgDomainUniqueConstraint("org.iam-domain")),
uniqueConstraintsFromEventConstraint(member.NewAddMemberUniqueConstraint("org2", "user1")),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "org2"),
iamDomain: "iam-domain",
zitadelRoles: []authz.RoleMapping{
{
Role: "ORG_OWNER",
},
},
},
args: args{
ctx: context.Background(),
name: "Org",
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsInternal,
},
},
{
name: "add org, no error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilterOrgDomainNotFound(),
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilterOrgMemberNotFound(),
expectPush(
[]*repository.Event{
eventFromEventPusher(org.NewOrgAddedEvent(context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
"Org",
)),
eventFromEventPusher(org.NewDomainAddedEvent(context.Background(),
&org.NewAggregate("org2", "org2").Aggregate, "org.iam-domain",
)),
eventFromEventPusher(org.NewDomainVerifiedEvent(context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
"org.iam-domain",
)),
eventFromEventPusher(org.NewDomainPrimarySetEvent(context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
"org.iam-domain",
)),
eventFromEventPusher(org.NewMemberAddedEvent(context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
"user1",
domain.RoleOrgOwner,
)),
},
uniqueConstraintsFromEventConstraint(org.NewAddOrgNameUniqueConstraint("Org")),
uniqueConstraintsFromEventConstraint(org.NewAddOrgDomainUniqueConstraint("org.iam-domain")),
uniqueConstraintsFromEventConstraint(member.NewAddMemberUniqueConstraint("org2", "user1")),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "org2"),
iamDomain: "iam-domain",
zitadelRoles: []authz.RoleMapping{
{
Role: "ORG_OWNER",
},
},
},
args: args{
ctx: context.Background(),
name: "Org",
userID: "user1",
resourceOwner: "org1",
},
res: res{
want: &domain.Org{
ObjectRoot: models.ObjectRoot{
AggregateID: "org2",
ResourceOwner: "org2",
},
Name: "Org",
State: domain.OrgStateActive,
PrimaryDomain: "org.iam-domain",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
iamDomain: tt.fields.iamDomain,
zitadelRoles: tt.fields.zitadelRoles,
}
got, err := r.AddOrg(tt.args.ctx, tt.args.name, tt.args.userID, tt.args.resourceOwner)
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_DeactivateOrg(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
iamDomain string
}
type args struct {
ctx context.Context
orgID string
}
type res struct {
want *domain.Org
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org not found, error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "org already inactive, error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"org"),
),
eventFromEventPusher(
org.NewOrgDeactivatedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "push failed, error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"org"),
),
),
expectPushFailed(
caos_errs.ThrowInternal(nil, "id", "message"),
[]*repository.Event{
eventFromEventPusher(org.NewOrgDeactivatedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate)),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
err: caos_errs.IsInternal,
},
},
{
name: "deactivate org",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"org"),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(org.NewOrgDeactivatedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
)),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
}
_, err := r.DeactivateOrg(tt.args.ctx, tt.args.orgID)
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)
}
})
}
}
func TestCommandSide_ReactivateOrg(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
iamDomain string
}
type args struct {
ctx context.Context
orgID string
}
type res struct {
want *domain.Org
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org not found, error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "org already active, error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"org"),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "push failed, error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"org"),
),
eventFromEventPusher(
org.NewOrgDeactivatedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
),
),
),
expectPushFailed(
caos_errs.ThrowInternal(nil, "id", "message"),
[]*repository.Event{
eventFromEventPusher(org.NewOrgReactivatedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
)),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
err: caos_errs.IsInternal,
},
},
{
name: "reactivate org",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"org"),
),
eventFromEventPusher(
org.NewOrgDeactivatedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(org.NewOrgReactivatedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate)),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
}
_, err := r.ReactivateOrg(tt.args.ctx, tt.args.orgID)
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)
}
})
}
}

View File

@@ -27,7 +27,7 @@ func (c *Commands) AddProject(ctx context.Context, project *domain.Project, reso
func (c *Commands) addProject(ctx context.Context, projectAdd *domain.Project, resourceOwner, ownerUserID string) (_ []eventstore.EventPusher, _ *ProjectWriteModel, err error) {
if !projectAdd.IsValid() {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-IOVCC", "Errors.Project.Invalid")
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-IOVCC", "Errors.Project.Invalid")
}
projectAdd.AggregateID, err = c.idGenerator.Next()
if err != nil {
@@ -74,8 +74,8 @@ func (c *Commands) checkProjectExists(ctx context.Context, projectID, resourceOw
}
func (c *Commands) ChangeProject(ctx context.Context, projectChange *domain.Project, resourceOwner string) (*domain.Project, error) {
if !projectChange.IsValid() && projectChange.AggregateID != "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4m9vS", "Errors.Project.Invalid")
if !projectChange.IsValid() || projectChange.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.Invalid")
}
existingProject, err := c.getProjectWriteModelByID(ctx, projectChange.AggregateID, resourceOwner)
@@ -107,7 +107,7 @@ func (c *Commands) ChangeProject(ctx context.Context, projectChange *domain.Proj
func (c *Commands) DeactivateProject(ctx context.Context, projectID string, resourceOwner string) (*domain.ObjectDetails, error) {
if projectID == "" || resourceOwner == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-88iF0", "Errors.Project.ProjectIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-88iF0", "Errors.Project.ProjectIDMissing")
}
existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
@@ -135,7 +135,7 @@ func (c *Commands) DeactivateProject(ctx context.Context, projectID string, reso
func (c *Commands) ReactivateProject(ctx context.Context, projectID string, resourceOwner string) (*domain.ObjectDetails, error) {
if projectID == "" || resourceOwner == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4m9vS", "Errors.Project.ProjectIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.ProjectIDMissing")
}
existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)
@@ -146,7 +146,7 @@ func (c *Commands) ReactivateProject(ctx context.Context, projectID string, reso
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.NotFound")
}
if existingProject.State != domain.ProjectStateInactive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M9bs", "Errors.Project.NotInctive")
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M9bs", "Errors.Project.NotInactive")
}
projectAgg := ProjectAggregateFromWriteModel(&existingProject.WriteModel)
@@ -163,7 +163,7 @@ func (c *Commands) ReactivateProject(ctx context.Context, projectID string, reso
func (c *Commands) RemoveProject(ctx context.Context, projectID, resourceOwner string, cascadingUserGrantIDs ...string) (*domain.ObjectDetails, error) {
if projectID == "" || resourceOwner == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-66hM9", "Errors.Project.ProjectIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-66hM9", "Errors.Project.ProjectIDMissing")
}
existingProject, err := c.getProjectWriteModelByID(ctx, projectID, resourceOwner)

View File

@@ -8,8 +8,8 @@ import (
)
func (c *Commands) ChangeApplication(ctx context.Context, projectID string, appChange domain.Application, resourceOwner string) (*domain.ObjectDetails, error) {
if appChange.GetAppID() == "" || appChange.GetApplicationName() == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4m9vS", "Errors.Project.App.Invalid")
if projectID == "" || appChange.GetAppID() == "" || appChange.GetApplicationName() == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.App.Invalid")
}
existingApp, err := c.getApplicationWriteModel(ctx, projectID, appChange.GetAppID(), resourceOwner)
@@ -25,7 +25,7 @@ func (c *Commands) ChangeApplication(ctx context.Context, projectID string, appC
projectAgg := ProjectAggregateFromWriteModel(&existingApp.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(
ctx,
project.NewApplicationChangedEvent(ctx, projectAgg, appChange.GetAppID(), existingApp.Name, appChange.GetApplicationName(), projectID))
project.NewApplicationChangedEvent(ctx, projectAgg, appChange.GetAppID(), existingApp.Name, appChange.GetApplicationName()))
if err != nil {
return nil, err
}
@@ -38,7 +38,7 @@ func (c *Commands) ChangeApplication(ctx context.Context, projectID string, appC
func (c *Commands) DeactivateApplication(ctx context.Context, projectID, appID, resourceOwner string) (*domain.ObjectDetails, error) {
if projectID == "" || appID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-88fi0", "Errors.IDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-88fi0", "Errors.IDMissing")
}
existingApp, err := c.getApplicationWriteModel(ctx, projectID, appID, resourceOwner)
@@ -65,7 +65,7 @@ func (c *Commands) DeactivateApplication(ctx context.Context, projectID, appID,
func (c *Commands) ReactivateApplication(ctx context.Context, projectID, appID, resourceOwner string) (*domain.ObjectDetails, error) {
if projectID == "" || appID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-983dF", "Errors.IDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-983dF", "Errors.IDMissing")
}
existingApp, err := c.getApplicationWriteModel(ctx, projectID, appID, resourceOwner)
@@ -93,7 +93,7 @@ func (c *Commands) ReactivateApplication(ctx context.Context, projectID, appID,
func (c *Commands) RemoveApplication(ctx context.Context, projectID, appID, resourceOwner string) (*domain.ObjectDetails, error) {
if projectID == "" || appID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-1b7Jf", "Errors.IDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-1b7Jf", "Errors.IDMissing")
}
existingApp, err := c.getApplicationWriteModel(ctx, projectID, appID, resourceOwner)
@@ -105,7 +105,7 @@ func (c *Commands) RemoveApplication(ctx context.Context, projectID, appID, reso
}
projectAgg := ProjectAggregateFromWriteModel(&existingApp.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, project.NewApplicationRemovedEvent(ctx, projectAgg, appID, existingApp.Name, projectID))
pushedEvents, err := c.eventstore.PushEvents(ctx, project.NewApplicationRemovedEvent(ctx, projectAgg, appID, existingApp.Name))
if err != nil {
return nil, err
}

View File

@@ -9,9 +9,12 @@ import (
)
func (c *Commands) AddAPIApplication(ctx context.Context, application *domain.APIApp, resourceOwner string) (_ *domain.APIApp, err error) {
if application == nil || application.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Application.Invalid")
}
project, err := c.getProjectByID(ctx, application.AggregateID, resourceOwner)
if err != nil {
return nil, err
return nil, caos_errs.ThrowPreconditionFailed(err, "PROJECT-9fnsf", "Errors.Project.NotFound")
}
addedApplication := NewAPIApplicationWriteModel(application.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
@@ -35,7 +38,7 @@ func (c *Commands) AddAPIApplication(ctx context.Context, application *domain.AP
func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore.Aggregate, proj *domain.Project, apiAppApp *domain.APIApp, resourceOwner string) (events []eventstore.EventPusher, stringPW string, err error) {
if !apiAppApp.IsValid() {
return nil, "", caos_errs.ThrowPreconditionFailed(nil, "PROJECT-Bff2g", "Errors.Application.Invalid")
return nil, "", caos_errs.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Application.Invalid")
}
apiAppApp.AppID, err = c.idGenerator.Next()
if err != nil {
@@ -43,7 +46,7 @@ func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore
}
events = []eventstore.EventPusher{
project.NewApplicationAddedEvent(ctx, projectAgg, apiAppApp.AppID, apiAppApp.AppName, resourceOwner),
project.NewApplicationAddedEvent(ctx, projectAgg, apiAppApp.AppID, apiAppApp.AppName),
}
var stringPw string
@@ -66,8 +69,8 @@ func (c *Commands) addAPIApplication(ctx context.Context, projectAgg *eventstore
}
func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (*domain.APIApp, error) {
if !apiApp.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-1m900", "Errors.Project.App.APIConfigInvalid")
if !apiApp.IsValid() || apiApp.AppID == "" || apiApp.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-1m900", "Errors.Project.App.APIConfigInvalid")
}
existingAPI, err := c.getAPIAppWriteModel(ctx, apiApp.AggregateID, apiApp.AppID, resourceOwner)
@@ -99,13 +102,12 @@ func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIA
return nil, err
}
result := apiWriteModelToAPIConfig(existingAPI)
return result, nil
return apiWriteModelToAPIConfig(existingAPI), nil
}
func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string) (*domain.APIApp, error) {
if projectID == "" || appID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-99i83", "Errors.IDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing")
}
existingAPI, err := c.getAPIAppWriteModel(ctx, projectID, appID, resourceOwner)

View File

@@ -0,0 +1,660 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/id"
id_mock "github.com/caos/zitadel/internal/id/mock"
"github.com/caos/zitadel/internal/repository/project"
"github.com/stretchr/testify/assert"
"testing"
)
func TestCommandSide_AddAPIApplication(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
secretGenerator crypto.Generator
}
type args struct {
ctx context.Context
apiApp *domain.APIApp
resourceOwner string
}
type res struct {
want *domain.APIApp
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "no aggregate id, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "project not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "app",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "invalid app, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"project"),
),
),
),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "create api app basic, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"project"),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
domain.APIAuthMethodTypeBasic),
),
},
uniqueConstraintsFromEventConstraint(project.NewAddApplicationUniqueConstraint("app", "project1")),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1"),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypeBasic,
},
resourceOwner: "org1",
},
res: res{
want: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
ClientID: "client1@project",
ClientSecretString: "a",
AuthMethodType: domain.APIAuthMethodTypeBasic,
State: domain.AppStateActive,
},
},
},
{
name: "create api app jwt, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"project"),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
nil,
domain.APIAuthMethodTypePrivateKeyJWT),
),
},
uniqueConstraintsFromEventConstraint(project.NewAddApplicationUniqueConstraint("app", "project1")),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1"),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
resourceOwner: "org1",
},
res: res{
want: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
ClientID: "client1@project",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
State: domain.AppStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
applicationSecretGenerator: tt.fields.secretGenerator,
}
got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
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_ChangeAPIApplication(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
apiApp *domain.APIApp
resourceOwner string
}
type res struct {
want *domain.APIApp
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid app, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "missing appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "",
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "missing aggregateid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "",
},
AppID: "appid",
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "app",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
nil,
domain.APIAuthMethodTypePrivateKeyJWT),
),
),
),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change api app, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
domain.APIAuthMethodTypeBasic),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAPIAppChangedEvent(context.Background(),
"app1",
"project1",
"org1",
domain.APIAuthMethodTypePrivateKeyJWT),
),
},
),
),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
resourceOwner: "org1",
},
res: res{
want: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
ClientID: "client1@project",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
State: domain.AppStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
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_ChangeAPIApplicationSecret(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
}
type args struct {
ctx context.Context
appID string
projectID string
resourceOwner string
}
type res struct {
want *domain.APIApp
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "no projectid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
appID: "app1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "no appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "change secret, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
domain.APIAuthMethodTypeBasic),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
project.NewAPIConfigSecretChangedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
}),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
want: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
ClientID: "client1@project",
ClientSecretString: "a",
AuthMethodType: domain.APIAuthMethodTypeBasic,
State: domain.AppStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
applicationSecretGenerator: tt.fields.secretGenerator,
}
got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
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 newAPIAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner string, authMethodType domain.APIAuthMethodType) *project.APIConfigChangedEvent {
changes := []project.APIConfigChanges{
project.ChangeAPIAuthMethodType(authMethodType),
}
event, _ := project.NewAPIConfigChangedEvent(ctx,
&project.NewAggregate(projectID, resourceOwner).Aggregate,
appID,
changes,
)
return event
}

View File

@@ -9,6 +9,9 @@ import (
)
func (c *Commands) AddApplicationKey(ctx context.Context, key *domain.ApplicationKey, resourceOwner string) (_ *domain.ApplicationKey, err error) {
if key.AggregateID == "" || key.ApplicationID == "" {
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-55m9fs", "Errors.IDMissing")
}
application, err := c.getApplicationWriteModel(ctx, key.AggregateID, key.ApplicationID, resourceOwner)
if err != nil {
return nil, err

View File

@@ -0,0 +1,215 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/id"
id_mock "github.com/caos/zitadel/internal/id/mock"
"github.com/caos/zitadel/internal/repository/project"
"github.com/stretchr/testify/assert"
"testing"
)
func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
secretGenerator crypto.Generator
keySize int
}
type args struct {
ctx context.Context
key *domain.ApplicationKey
resourceOwner string
}
type res struct {
want *domain.APIApp
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "no aggregateid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
key: &domain.ApplicationKey{
ApplicationID: "app1",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "no appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
key: &domain.ApplicationKey{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
key: &domain.ApplicationKey{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
ApplicationID: "app1",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "create key not allowed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
),
expectFilter(
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
domain.APIAuthMethodTypeBasic),
),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "key1"),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
key: &domain.ApplicationKey{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
ApplicationID: "app1",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "create key not allowed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
),
expectFilter(
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
domain.APIAuthMethodTypeBasic),
),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "key1"),
secretGenerator: GetMockSecretGenerator(t),
keySize: 10,
},
args: args{
ctx: context.Background(),
key: &domain.ApplicationKey{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
ApplicationID: "app1",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
applicationSecretGenerator: tt.fields.secretGenerator,
applicationKeySize: tt.fields.keySize,
}
got, err := r.AddApplicationKey(tt.args.ctx, tt.args.key, tt.args.resourceOwner)
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)
}
})
}
}

View File

@@ -14,9 +14,12 @@ import (
)
func (c *Commands) AddOIDCApplication(ctx context.Context, application *domain.OIDCApp, resourceOwner string) (_ *domain.OIDCApp, err error) {
if application == nil || application.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Application.Invalid")
}
project, err := c.getProjectByID(ctx, application.AggregateID, resourceOwner)
if err != nil {
return nil, err
return nil, caos_errs.ThrowPreconditionFailed(err, "PROJECT-3m9ss", "Errors.Project.NotFound")
}
addedApplication := NewOIDCApplicationWriteModel(application.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
@@ -41,7 +44,7 @@ func (c *Commands) AddOIDCApplication(ctx context.Context, application *domain.O
func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstore.Aggregate, proj *domain.Project, oidcApp *domain.OIDCApp, resourceOwner string) (events []eventstore.EventPusher, stringPW string, err error) {
if !oidcApp.IsValid() {
return nil, "", caos_errs.ThrowPreconditionFailed(nil, "PROJECT-Bff2g", "Errors.Application.Invalid")
return nil, "", caos_errs.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Application.Invalid")
}
oidcApp.AppID, err = c.idGenerator.Next()
if err != nil {
@@ -49,7 +52,7 @@ func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstor
}
events = []eventstore.EventPusher{
project.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName, resourceOwner),
project.NewApplicationAddedEvent(ctx, projectAgg, oidcApp.AppID, oidcApp.AppName),
}
var stringPw string
@@ -84,8 +87,8 @@ func (c *Commands) addOIDCApplication(ctx context.Context, projectAgg *eventstor
}
func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCApp, resourceOwner string) (*domain.OIDCApp, error) {
if !oidc.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-1m900", "Errors.Project.App.OIDCConfigInvalid")
if !oidc.IsValid() || oidc.AppID == "" || oidc.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5m9fs", "Errors.Project.App.OIDCConfigInvalid")
}
existingOIDC, err := c.getOIDCAppWriteModel(ctx, oidc.AggregateID, oidc.AppID, resourceOwner)
@@ -136,7 +139,7 @@ func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCA
func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, appID, resourceOwner string) (*domain.OIDCApp, error) {
if projectID == "" || appID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-99i83", "Errors.IDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-99i83", "Errors.IDMissing")
}
existingOIDC, err := c.getOIDCAppWriteModel(ctx, projectID, appID, resourceOwner)

View File

@@ -0,0 +1,734 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/id"
id_mock "github.com/caos/zitadel/internal/id/mock"
"github.com/caos/zitadel/internal/repository/project"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
func TestCommandSide_AddOIDCApplication(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
secretGenerator crypto.Generator
}
type args struct {
ctx context.Context
oidcApp *domain.OIDCApp
resourceOwner string
}
type res struct {
want *domain.OIDCApp
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "no aggregate id, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
oidcApp: &domain.OIDCApp{},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "project not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
oidcApp: &domain.OIDCApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "app",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "invalid app, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"project"),
),
),
),
},
args: args{
ctx: context.Background(),
oidcApp: &domain.OIDCApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "create oidc app basic, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"project"),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
eventFromEventPusher(
project.NewOIDCConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
domain.OIDCVersionV1,
"app1",
"client1@project",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
[]string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
domain.OIDCApplicationTypeWeb,
domain.OIDCAuthMethodTypePost,
[]string{"https://test.ch/logout"},
true,
domain.OIDCTokenTypeBearer,
true,
true,
true,
time.Second*1),
),
},
uniqueConstraintsFromEventConstraint(project.NewAddApplicationUniqueConstraint("app", "project1")),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1"),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
oidcApp: &domain.OIDCApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppName: "app",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
RedirectUris: []string{"https://test.ch"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: true,
IDTokenRoleAssertion: true,
IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1,
},
resourceOwner: "org1",
},
res: res{
want: &domain.OIDCApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
ClientID: "client1@project",
ClientSecretString: "a",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
RedirectUris: []string{"https://test.ch"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: true,
IDTokenRoleAssertion: true,
IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1,
State: domain.AppStateActive,
Compliance: &domain.Compliance{},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
applicationSecretGenerator: tt.fields.secretGenerator,
}
got, err := r.AddOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner)
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_ChangeOIDCApplication(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
oidcApp *domain.OIDCApp
resourceOwner string
}
type res struct {
want *domain.OIDCApp
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid app, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
oidcApp: &domain.OIDCApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "missing appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
oidcApp: &domain.OIDCApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "",
AppName: "app",
AuthMethodType: domain.OIDCAuthMethodTypePost,
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "missing aggregateid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
oidcApp: &domain.OIDCApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "",
},
AppID: "appid",
AppName: "app",
AuthMethodType: domain.OIDCAuthMethodTypePost,
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
oidcApp: &domain.OIDCApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "app",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
eventFromEventPusher(
project.NewOIDCConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
domain.OIDCVersionV1,
"app1",
"client1@project",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
[]string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
domain.OIDCApplicationTypeWeb,
domain.OIDCAuthMethodTypePost,
[]string{"https://test.ch/logout"},
true,
domain.OIDCTokenTypeBearer,
true,
true,
true,
time.Second*1),
),
),
),
},
args: args{
ctx: context.Background(),
oidcApp: &domain.OIDCApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "app",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
RedirectUris: []string{"https://test.ch"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: true,
IDTokenRoleAssertion: true,
IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1,
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change oidc app, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
eventFromEventPusher(
project.NewOIDCConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
domain.OIDCVersionV1,
"app1",
"client1@project",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
[]string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
domain.OIDCApplicationTypeWeb,
domain.OIDCAuthMethodTypePost,
[]string{"https://test.ch/logout"},
false,
domain.OIDCTokenTypeBearer,
true,
true,
true,
time.Second*1),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newOIDCAppChangedEvent(context.Background(),
"app1",
"project1",
"org1"),
),
},
),
),
},
args: args{
ctx: context.Background(),
oidcApp: &domain.OIDCApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "app",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
RedirectUris: []string{"https://test-change.ch"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
PostLogoutRedirectUris: []string{"https://test-change.ch/logout"},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeJWT,
AccessTokenRoleAssertion: false,
IDTokenRoleAssertion: false,
IDTokenUserinfoAssertion: false,
ClockSkew: time.Second * 2,
},
resourceOwner: "org1",
},
res: res{
want: &domain.OIDCApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
ClientID: "client1@project",
AppName: "app",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
RedirectUris: []string{"https://test-change.ch"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
PostLogoutRedirectUris: []string{"https://test-change.ch/logout"},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeJWT,
AccessTokenRoleAssertion: false,
IDTokenRoleAssertion: false,
IDTokenUserinfoAssertion: false,
ClockSkew: time.Second * 2,
Compliance: &domain.Compliance{},
State: domain.AppStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner)
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_ChangeOIDCApplicationSecret(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
}
type args struct {
ctx context.Context
appID string
projectID string
resourceOwner string
}
type res struct {
want *domain.OIDCApp
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "no projectid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
appID: "app1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "no appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "change secret, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
eventFromEventPusher(
project.NewOIDCConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
domain.OIDCVersionV1,
"app1",
"client1@project",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
[]string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
domain.OIDCApplicationTypeWeb,
domain.OIDCAuthMethodTypePost,
[]string{"https://test.ch/logout"},
true,
domain.OIDCTokenTypeBearer,
true,
true,
true,
time.Second*1),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
project.NewOIDCConfigSecretChangedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
}),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
want: &domain.OIDCApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
ClientID: "client1@project",
ClientSecretString: "a",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
RedirectUris: []string{"https://test.ch"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: true,
IDTokenRoleAssertion: true,
IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1,
State: domain.AppStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
applicationSecretGenerator: tt.fields.secretGenerator,
}
got, err := r.ChangeOIDCApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
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 newOIDCAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner string) *project.OIDCConfigChangedEvent {
changes := []project.OIDCConfigChanges{
project.ChangeRedirectURIs([]string{"https://test-change.ch"}),
project.ChangePostLogoutRedirectURIs([]string{"https://test-change.ch/logout"}),
project.ChangeDevMode(true),
project.ChangeAccessTokenType(domain.OIDCTokenTypeJWT),
project.ChangeAccessTokenRoleAssertion(false),
project.ChangeIDTokenRoleAssertion(false),
project.ChangeIDTokenUserinfoAssertion(false),
project.ChangeClockSkew(time.Second * 2),
}
event, _ := project.NewOIDCConfigChangedEvent(ctx,
&project.NewAggregate(projectID, resourceOwner).Aggregate,
appID,
changes,
)
return event
}

View File

@@ -0,0 +1,636 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/repository/project"
"github.com/stretchr/testify/assert"
"testing"
)
func TestCommandSide_ChangeApplication(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
projectID string
app *domain.ChangeApp
resourceOwner string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid app missing projectid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
projectID: "",
app: &domain.ChangeApp{
AppID: "app1",
AppName: "app",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid app missing appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
app: &domain.ChangeApp{
AppName: "app",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid app missing name, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
app: &domain.ChangeApp{
AppID: "app1",
AppName: "",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
app: &domain.ChangeApp{
AppID: "app1",
AppName: "app",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "app name not changed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
)),
),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
app: &domain.ChangeApp{
AppID: "app1",
AppName: "app",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "app changed, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
)),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(project.NewApplicationChangedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
"app changed",
)),
},
uniqueConstraintsFromEventConstraint(project.NewRemoveApplicationUniqueConstraint("app", "project1")),
uniqueConstraintsFromEventConstraint(project.NewAddApplicationUniqueConstraint("app changed", "project1")),
),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
app: &domain.ChangeApp{
AppID: "app1",
AppName: "app changed",
},
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeApplication(tt.args.ctx, tt.args.projectID, tt.args.app, tt.args.resourceOwner)
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_DeactivateApplication(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
projectID string
appID string
resourceOwner string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "missing projectid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
projectID: "",
appID: "app1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "missing appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "app already inactive, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
)),
eventFromEventPusher(project.NewApplicationDeactivatedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
)),
),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "app deactivate, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
)),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(project.NewApplicationDeactivatedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
)),
},
),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.DeactivateApplication(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
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_ReactivateApplication(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
projectID string
appID string
resourceOwner string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "missing projectid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
projectID: "",
appID: "app1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "missing appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "app already active, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
)),
),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "app reactivate, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
)),
eventFromEventPusher(project.NewApplicationDeactivatedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
)),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(project.NewApplicationReactivatedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
)),
},
),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ReactivateApplication(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
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_RemoveApplication(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
projectID string
appID string
resourceOwner string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "missing projectid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
projectID: "",
appID: "app1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "missing appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "app remove, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
)),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(project.NewApplicationRemovedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
)),
},
uniqueConstraintsFromEventConstraint(project.NewRemoveApplicationUniqueConstraint("app", "project1")),
),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemoveApplication(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
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)
}
})
}
}

View File

@@ -38,7 +38,6 @@ func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.O
AppName: writeModel.AppName,
State: writeModel.State,
ClientID: writeModel.ClientID,
ClientSecret: writeModel.ClientSecret,
RedirectUris: writeModel.RedirectUris,
ResponseTypes: writeModel.ResponseTypes,
GrantTypes: writeModel.GrantTypes,
@@ -62,7 +61,6 @@ func apiWriteModelToAPIConfig(writeModel *APIApplicationWriteModel) *domain.APIA
AppName: writeModel.AppName,
State: writeModel.State,
ClientID: writeModel.ClientID,
ClientSecret: writeModel.ClientSecret,
AuthMethodType: writeModel.AuthMethodType,
}
}

View File

@@ -13,25 +13,21 @@ import (
func (c *Commands) AddProjectGrant(ctx context.Context, grant *domain.ProjectGrant, resourceOwner string) (_ *domain.ProjectGrant, err error) {
if !grant.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-Bff2g", "Errors.Project.Grant.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Project.Grant.Invalid")
}
err = c.checkProjectGrantPreCondition(ctx, grant)
if err != nil {
return nil, err
}
grant.GrantID, err = c.idGenerator.Next()
if err != nil {
return nil, err
}
err = c.checkProjectExists(ctx, grant.AggregateID, resourceOwner)
if err != nil {
return nil, err
}
err = c.checkOrgExists(ctx, grant.GrantedOrgID)
if err != nil {
return nil, err
}
addedGrant := NewProjectGrantWriteModel(grant.GrantID, grant.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedGrant.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(
ctx,
project.NewGrantAddedEvent(ctx, projectAgg, grant.GrantID, grant.GrantedOrgID, grant.AggregateID, grant.RoleKeys))
project.NewGrantAddedEvent(ctx, projectAgg, grant.GrantID, grant.GrantedOrgID, grant.RoleKeys))
if err != nil {
return nil, err
}
@@ -44,13 +40,14 @@ func (c *Commands) AddProjectGrant(ctx context.Context, grant *domain.ProjectGra
func (c *Commands) ChangeProjectGrant(ctx context.Context, grant *domain.ProjectGrant, resourceOwner string, cascadeUserGrantIDs ...string) (_ *domain.ProjectGrant, err error) {
if grant.GrantID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-1j83s", "Errors.IDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-1j83s", "Errors.IDMissing")
}
err = c.checkProjectExists(ctx, grant.AggregateID, resourceOwner)
existingGrant, err := c.projectGrantWriteModelByID(ctx, grant.GrantID, grant.AggregateID, resourceOwner)
if err != nil {
return nil, err
}
existingGrant, err := c.projectGrantWriteModelByID(ctx, grant.GrantID, grant.AggregateID, resourceOwner)
grant.GrantedOrgID = existingGrant.GrantedOrgID
err = c.checkProjectGrantPreCondition(ctx, grant)
if err != nil {
return nil, err
}
@@ -96,7 +93,7 @@ func (c *Commands) ChangeProjectGrant(ctx context.Context, grant *domain.Project
}
func (c *Commands) removeRoleFromProjectGrant(ctx context.Context, projectAgg *eventstore.Aggregate, projectID, projectGrantID, roleKey string, cascade bool) (_ eventstore.EventPusher, _ *ProjectGrantWriteModel, err error) {
existingProjectGrant, err := c.projectGrantWriteModelByID(ctx, projectID, projectGrantID, "")
existingProjectGrant, err := c.projectGrantWriteModelByID(ctx, projectGrantID, projectID, "")
if err != nil {
return nil, nil, err
}
@@ -127,7 +124,7 @@ func (c *Commands) removeRoleFromProjectGrant(ctx context.Context, projectAgg *e
func (c *Commands) DeactivateProjectGrant(ctx context.Context, projectID, grantID, resourceOwner string) (details *domain.ObjectDetails, err error) {
if grantID == "" || projectID == "" {
return details, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-p0s4V", "Errors.IDMissing")
return details, caos_errs.ThrowInvalidArgument(nil, "PROJECT-p0s4V", "Errors.IDMissing")
}
err = c.checkProjectExists(ctx, projectID, resourceOwner)
if err != nil {
@@ -155,7 +152,7 @@ func (c *Commands) DeactivateProjectGrant(ctx context.Context, projectID, grantI
func (c *Commands) ReactivateProjectGrant(ctx context.Context, projectID, grantID, resourceOwner string) (details *domain.ObjectDetails, err error) {
if grantID == "" || projectID == "" {
return details, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-p0s4V", "Errors.IDMissing")
return details, caos_errs.ThrowInvalidArgument(nil, "PROJECT-p0s4V", "Errors.IDMissing")
}
err = c.checkProjectExists(ctx, projectID, resourceOwner)
if err != nil {
@@ -182,11 +179,11 @@ func (c *Commands) ReactivateProjectGrant(ctx context.Context, projectID, grantI
func (c *Commands) RemoveProjectGrant(ctx context.Context, projectID, grantID, resourceOwner string, cascadeUserGrantIDs ...string) (details *domain.ObjectDetails, err error) {
if grantID == "" || projectID == "" {
return details, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-1m9fJ", "Errors.IDMissing")
return details, caos_errs.ThrowInvalidArgument(nil, "PROJECT-1m9fJ", "Errors.IDMissing")
}
err = c.checkProjectExists(ctx, projectID, resourceOwner)
if err != nil {
return details, err
return details, caos_errs.ThrowPreconditionFailed(err, "PROJECT-6mf9s", "Errors.Project.NotFound")
}
existingGrant, err := c.projectGrantWriteModelByID(ctx, grantID, projectID, resourceOwner)
if err != nil {
@@ -194,7 +191,7 @@ func (c *Commands) RemoveProjectGrant(ctx context.Context, projectID, grantID, r
}
events := make([]eventstore.EventPusher, 0)
projectAgg := ProjectAggregateFromWriteModel(&existingGrant.WriteModel)
events = append(events, project.NewGrantRemovedEvent(ctx, projectAgg, grantID, existingGrant.GrantedOrgID, projectID))
events = append(events, project.NewGrantRemovedEvent(ctx, projectAgg, grantID, existingGrant.GrantedOrgID))
for _, userGrantID := range cascadeUserGrantIDs {
event, _, err := c.removeUserGrant(ctx, userGrantID, "", true)
@@ -231,3 +228,21 @@ func (c *Commands) projectGrantWriteModelByID(ctx context.Context, grantID, proj
return writeModel, nil
}
func (c *Commands) checkProjectGrantPreCondition(ctx context.Context, projectGrant *domain.ProjectGrant) error {
preConditions := NewProjectGrantPreConditionReadModel(projectGrant.AggregateID, projectGrant.GrantedOrgID)
err := c.eventstore.FilterToQueryReducer(ctx, preConditions)
if err != nil {
return err
}
if !preConditions.ProjectExists {
return caos_errs.ThrowPreconditionFailed(err, "COMMAND-m9gsd", "Errors.Project.NotFound")
}
if !preConditions.GrantedOrgExists {
return caos_errs.ThrowPreconditionFailed(err, "COMMAND-3m9gg", "Errors.Org.NotFound")
}
if projectGrant.HasInvalidRoles(preConditions.ExistingRoleKeys) {
return caos_errs.ThrowPreconditionFailed(err, "COMMAND-6m9gd", "Errors.Project.Role.NotFound")
}
return nil
}

View File

@@ -11,9 +11,12 @@ import (
"github.com/caos/zitadel/internal/telemetry/tracing"
)
func (c *Commands) AddProjectGrantMember(ctx context.Context, member *domain.ProjectGrantMember, resourceOwner string) (*domain.ProjectGrantMember, error) {
func (c *Commands) AddProjectGrantMember(ctx context.Context, member *domain.ProjectGrantMember) (*domain.ProjectGrantMember, error) {
if !member.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-8fi7G", "Errors.Project.Member.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-8fi7G", "Errors.Project.Grant.Member.Invalid")
}
if len(domain.CheckForInvalidRoles(member.Roles, domain.ProjectGrantRolePrefix, c.zitadelRoles)) > 0 {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-m9gKK", "Errors.Project.Grant.Member.Invalid")
}
err := c.checkUserExists(ctx, member.UserID, "")
if err != nil {
@@ -25,12 +28,12 @@ func (c *Commands) AddProjectGrantMember(ctx context.Context, member *domain.Pro
return nil, err
}
if addedMember.State == domain.MemberStateActive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-16dVN", "Errors.Project.Member.AlreadyExists")
return nil, caos_errs.ThrowAlreadyExists(nil, "PROJECT-16dVN", "Errors.Project.Member.AlreadyExists")
}
projectAgg := ProjectAggregateFromWriteModel(&addedMember.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(
ctx,
project.NewProjectGrantMemberAddedEvent(ctx, projectAgg, member.AggregateID, member.UserID, member.GrantID, member.Roles...))
project.NewProjectGrantMemberAddedEvent(ctx, projectAgg, member.UserID, member.GrantID, member.Roles...))
if err != nil {
return nil, err
}
@@ -43,12 +46,12 @@ func (c *Commands) AddProjectGrantMember(ctx context.Context, member *domain.Pro
}
//ChangeProjectGrantMember updates an existing member
func (c *Commands) ChangeProjectGrantMember(ctx context.Context, member *domain.ProjectGrantMember, resourceOwner string) (*domain.ProjectGrantMember, error) {
func (c *Commands) ChangeProjectGrantMember(ctx context.Context, member *domain.ProjectGrantMember) (*domain.ProjectGrantMember, error) {
if !member.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-109fs", "Errors.Project.Member.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-109fs", "Errors.Project.Member.Invalid")
}
if len(domain.CheckForInvalidRoles(member.Roles, domain.ProjectGrantRolePrefix, c.zitadelRoles)) > 0 {
return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-m0sDf", "Errors.Project.Member.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-m0sDf", "Errors.Project.Member.Invalid")
}
existingMember, err := c.projectGrantMemberWriteModelByID(ctx, member.AggregateID, member.UserID, member.GrantID)
@@ -74,14 +77,17 @@ func (c *Commands) ChangeProjectGrantMember(ctx context.Context, member *domain.
return memberWriteModelToProjectGrantMember(existingMember), nil
}
func (c *Commands) RemoveProjectGrantMember(ctx context.Context, projectID, userID, grantID, resourceOwner string) (*domain.ObjectDetails, error) {
func (c *Commands) RemoveProjectGrantMember(ctx context.Context, projectID, userID, grantID string) (*domain.ObjectDetails, error) {
if projectID == "" || userID == "" || grantID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-66mHd", "Errors.Project.Member.Invalid")
}
m, err := c.projectGrantMemberWriteModelByID(ctx, projectID, userID, grantID)
if err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModel(&m.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, project.NewProjectGrantMemberRemovedEvent(ctx, projectAgg, projectID, userID, grantID))
pushedEvents, err := c.eventstore.PushEvents(ctx, project.NewProjectGrantMemberRemovedEvent(ctx, projectAgg, userID, grantID))
if err != nil {
return nil, err
}

View File

@@ -0,0 +1,646 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/project"
"github.com/caos/zitadel/internal/repository/user"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"testing"
)
func TestCommandSide_AddProjectGrantMember(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
zitadelRoles []authz.RoleMapping
}
type args struct {
ctx context.Context
member *domain.ProjectGrantMember
}
type res struct {
want *domain.ProjectGrantMember
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid member, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.ProjectGrantMember{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid roles, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.ProjectGrantMember{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
GrantID: "projectgrant1",
UserID: "user1",
Roles: []string{"PROJECT_GRANT_OWNER"},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
zitadelRoles: []authz.RoleMapping{
{
Role: "PROJECT_GRANT_OWNER",
},
},
},
args: args{
ctx: context.Background(),
member: &domain.ProjectGrantMember{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
GrantID: "projectgrant1",
UserID: "user1",
Roles: []string{"PROJECT_GRANT_OWNER"},
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "member already exists, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
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.NewProjectGrantMemberAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
"projectgrant1",
),
),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: "PROJECT_GRANT_OWNER",
},
},
},
args: args{
ctx: context.Background(),
member: &domain.ProjectGrantMember{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
GrantID: "projectgrant1",
UserID: "user1",
Roles: []string{"PROJECT_GRANT_OWNER"},
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "member add uniqueconstraint err, already exists",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilter(),
expectPushFailed(caos_errs.ThrowAlreadyExists(nil, "ERROR", "internal"),
[]*repository.Event{
eventFromEventPusher(project.NewProjectGrantMemberAddedEvent(context.Background(),
&project.NewAggregate("project1", "").Aggregate,
"user1",
"projectgrant1",
[]string{"PROJECT_GRANT_OWNER"}...,
)),
},
uniqueConstraintsFromEventConstraint(project.NewAddProjectGrantMemberUniqueConstraint("project1", "user1", "projectgrant1")),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: "PROJECT_GRANT_OWNER",
},
},
},
args: args{
ctx: context.Background(),
member: &domain.ProjectGrantMember{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
GrantID: "projectgrant1",
UserID: "user1",
Roles: []string{"PROJECT_GRANT_OWNER"},
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "member add, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(project.NewProjectGrantMemberAddedEvent(context.Background(),
&project.NewAggregate("project1", "").Aggregate,
"user1",
"projectgrant1",
[]string{"PROJECT_GRANT_OWNER"}...,
)),
},
uniqueConstraintsFromEventConstraint(project.NewAddProjectGrantMemberUniqueConstraint("project1", "user1", "projectgrant1")),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: "PROJECT_GRANT_OWNER",
},
},
},
args: args{
ctx: context.Background(),
member: &domain.ProjectGrantMember{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
UserID: "user1",
GrantID: "projectgrant1",
Roles: []string{"PROJECT_GRANT_OWNER"},
},
},
res: res{
want: &domain.ProjectGrantMember{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
GrantID: "projectgrant1",
UserID: "user1",
Roles: []string{"PROJECT_GRANT_OWNER"},
},
},
},
}
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.AddProjectGrantMember(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_ChangeProjectGrantMember(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
zitadelRoles []authz.RoleMapping
}
type args struct {
ctx context.Context
member *domain.ProjectGrantMember
}
type res struct {
want *domain.ProjectGrantMember
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid member, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.ProjectGrantMember{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid roles, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.ProjectGrantMember{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
GrantID: "projectgrant1",
UserID: "user1",
Roles: []string{"PROJECT_OWNER"},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "member not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
zitadelRoles: []authz.RoleMapping{
{
Role: "PROJECT_GRANT_OWNER",
},
},
},
args: args{
ctx: context.Background(),
member: &domain.ProjectGrantMember{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
GrantID: "projectgrant1",
UserID: "user1",
Roles: []string{"PROJECT_GRANT_OWNER"},
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "member not changed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewProjectGrantMemberAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
"projectgrant1",
[]string{"PROJECT_GRANT_OWNER"}...,
),
),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: "PROJECT_GRANT_OWNER",
},
},
},
args: args{
ctx: context.Background(),
member: &domain.ProjectGrantMember{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
GrantID: "projectgrant1",
UserID: "user1",
Roles: []string{"PROJECT_GRANT_OWNER"},
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "member change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewProjectGrantMemberAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
"projectgrant1",
[]string{"PROJECT_GRANT_OWNER"}...,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(project.NewProjectGrantMemberChangedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
"projectgrant1",
[]string{"PROJECT_GRANT_OWNER", "PROJECT_GRANT_VIEWER"}...,
)),
},
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: "PROJECT_GRANT_OWNER",
},
{
Role: "PROJECT_GRANT_VIEWER",
},
},
},
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"},
},
},
res: res{
want: &domain.ProjectGrantMember{
ObjectRoot: models.ObjectRoot{
ResourceOwner: "org1",
AggregateID: "project1",
},
GrantID: "projectgrant1",
UserID: "user1",
Roles: []string{"PROJECT_GRANT_OWNER", "PROJECT_GRANT_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.ChangeProjectGrantMember(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_RemoveProjectGrantMember(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
projectID string
grantID 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 projectid missing, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
projectID: "",
userID: "user1",
grantID: "projectgrant1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid member userid missing, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
userID: "",
grantID: "projectgrant1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid member grantid missing, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
userID: "user1",
grantID: "",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "member not existing, not found err",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
userID: "user1",
grantID: "projectgrant1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "member remove, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewProjectGrantMemberAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
"projectgrant1",
[]string{"PROJECT_OWNER"}...,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(project.NewProjectGrantMemberRemovedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
"projectgrant1",
)),
},
uniqueConstraintsFromEventConstraint(project.NewRemoveProjectGrantMemberUniqueConstraint("project1", "user1", "projectgrant1")),
),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
userID: "user1",
grantID: "projectgrant1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemoveProjectGrantMember(tt.args.ctx, tt.args.projectID, tt.args.userID, tt.args.grantID)
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)
}
})
}
}

View File

@@ -3,6 +3,7 @@ package command
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/project"
)
@@ -105,3 +106,60 @@ func (wm *ProjectGrantWriteModel) Query() *eventstore.SearchQueryBuilder {
}
return query
}
type ProjectGrantPreConditionReadModel struct {
eventstore.WriteModel
ProjectID string
GrantedOrgID string
ProjectExists bool
GrantedOrgExists bool
ExistingRoleKeys []string
}
func NewProjectGrantPreConditionReadModel(projectID, grantedOrgID string) *ProjectGrantPreConditionReadModel {
return &ProjectGrantPreConditionReadModel{
ProjectID: projectID,
GrantedOrgID: grantedOrgID,
}
}
func (wm *ProjectGrantPreConditionReadModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *project.ProjectAddedEvent:
wm.ProjectExists = true
case *project.ProjectRemovedEvent:
wm.ProjectExists = false
case *project.RoleAddedEvent:
wm.ExistingRoleKeys = append(wm.ExistingRoleKeys, e.Key)
case *project.RoleRemovedEvent:
for i, key := range wm.ExistingRoleKeys {
if key == e.Key {
copy(wm.ExistingRoleKeys[i:], wm.ExistingRoleKeys[i+1:])
wm.ExistingRoleKeys[len(wm.ExistingRoleKeys)-1] = ""
wm.ExistingRoleKeys = wm.ExistingRoleKeys[:len(wm.ExistingRoleKeys)-1]
continue
}
}
case *org.OrgAddedEvent:
wm.GrantedOrgExists = true
case *org.OrgRemovedEvent:
wm.GrantedOrgExists = false
}
}
return wm.WriteModel.Reduce()
}
func (wm *ProjectGrantPreConditionReadModel) Query() *eventstore.SearchQueryBuilder {
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, org.AggregateType, project.AggregateType).
AggregateIDs(wm.ProjectID, wm.GrantedOrgID).
EventTypes(
org.OrgAddedEventType,
org.OrgRemovedEventType,
project.ProjectAddedType,
project.ProjectRemovedType,
project.RoleAddedType,
project.RoleRemovedType)
return query
}

File diff suppressed because it is too large Load Diff

View File

@@ -34,10 +34,10 @@ func (c *Commands) AddProjectMember(ctx context.Context, member *domain.Member,
func (c *Commands) addProjectMember(ctx context.Context, projectAgg *eventstore.Aggregate, addedMember *ProjectMemberWriteModel, member *domain.Member) (eventstore.EventPusher, error) {
if !member.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-W8m4l", "Errors.Project.Member.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-W8m4l", "Errors.Project.Member.Invalid")
}
if len(domain.CheckForInvalidRoles(member.Roles, domain.ProjectRolePrefix, c.zitadelRoles)) > 0 {
return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-3m9ds", "Errors.Project.Member.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-3m9ds", "Errors.Project.Member.Invalid")
}
err := c.checkUserExists(ctx, addedMember.UserID, "")
@@ -58,10 +58,10 @@ func (c *Commands) addProjectMember(ctx context.Context, projectAgg *eventstore.
//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, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-LiaZi", "Errors.Project.Member.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-LiaZi", "Errors.Project.Member.Invalid")
}
if len(domain.CheckForInvalidRoles(member.Roles, domain.ProjectRolePrefix, c.zitadelRoles)) > 0 {
return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-3m9d", "Errors.Project.Member.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-3m9d", "Errors.Project.Member.Invalid")
}
existingMember, err := c.projectMemberWriteModelByID(ctx, member.AggregateID, member.UserID, resourceOwner)
@@ -87,6 +87,9 @@ func (c *Commands) ChangeProjectMember(ctx context.Context, member *domain.Membe
}
func (c *Commands) RemoveProjectMember(ctx context.Context, projectID, userID, resourceOwner string) (*domain.ObjectDetails, error) {
if projectID == "" || userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-66mHd", "Errors.Project.Member.Invalid")
}
m, err := c.projectMemberWriteModelByID(ctx, projectID, userID, resourceOwner)
if err != nil && !errors.IsNotFound(err) {
return nil, err

View File

@@ -0,0 +1,625 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/member"
"github.com/caos/zitadel/internal/repository/project"
"github.com/caos/zitadel/internal/repository/user"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"testing"
)
func TestCommandSide_AddProjectMember(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
zitadelRoles []authz.RoleMapping
}
type args struct {
ctx context.Context
member *domain.Member
resourceOwner string
}
type res struct {
want *domain.Member
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid member, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid roles, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
UserID: "user1",
Roles: []string{"PROJECT_OWNER"},
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
zitadelRoles: []authz.RoleMapping{
{
Role: domain.RoleProjectOwner,
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
UserID: "user1",
Roles: []string{"PROJECT_OWNER"},
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "member already exists, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
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.NewProjectMemberAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
),
),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: domain.RoleProjectOwner,
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
UserID: "user1",
Roles: []string{"PROJECT_OWNER"},
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "member add uniqueconstraint err, already exists",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilter(),
expectPushFailed(caos_errs.ThrowAlreadyExists(nil, "ERROR", "internal"),
[]*repository.Event{
eventFromEventPusher(project.NewProjectMemberAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
[]string{"PROJECT_OWNER"}...,
)),
},
uniqueConstraintsFromEventConstraint(member.NewAddMemberUniqueConstraint("project1", "user1")),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: domain.RoleProjectOwner,
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
UserID: "user1",
Roles: []string{"PROJECT_OWNER"},
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "member add, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username1",
"firstname1",
"lastname1",
"nickname1",
"displayname1",
language.German,
domain.GenderMale,
"email1",
true,
),
),
),
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(project.NewProjectMemberAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
[]string{"PROJECT_OWNER"}...,
)),
},
uniqueConstraintsFromEventConstraint(member.NewAddMemberUniqueConstraint("project1", "user1")),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: domain.RoleProjectOwner,
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "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},
},
},
},
}
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.AddProjectMember(tt.args.ctx, tt.args.member, tt.args.resourceOwner)
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_ChangeProjectMember(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
zitadelRoles []authz.RoleMapping
}
type args struct {
ctx context.Context
member *domain.Member
resourceOwner string
}
type res struct {
want *domain.Member
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid member, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid roles, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
UserID: "user1",
Roles: []string{"PROJECT_OWNER"},
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "member not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
zitadelRoles: []authz.RoleMapping{
{
Role: domain.RoleProjectOwner,
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
UserID: "user1",
Roles: []string{"PROJECT_OWNER"},
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "member not changed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewProjectMemberAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
[]string{"PROJECT_OWNER"}...,
),
),
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: domain.RoleProjectOwner,
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
UserID: "user1",
Roles: []string{"PROJECT_OWNER"},
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "member change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
project.NewProjectMemberAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
[]string{"PROJECT_OWNER"}...,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(project.NewProjectMemberChangedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
[]string{"PROJECT_OWNER", "PROJECT_VIEWER"}...,
)),
},
),
),
zitadelRoles: []authz.RoleMapping{
{
Role: domain.RoleProjectOwner,
},
{
Role: "PROJECT_VIEWER",
},
},
},
args: args{
ctx: context.Background(),
member: &domain.Member{
ObjectRoot: models.ObjectRoot{
AggregateID: "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"},
},
},
},
}
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.ChangeProjectMember(tt.args.ctx, tt.args.member, tt.args.resourceOwner)
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_RemoveProjectMember(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: caos_errs.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: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "member not existing, nil result",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
userID: "user1",
resourceOwner: "org1",
},
res: res{
want: nil,
},
},
{
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(
[]*repository.Event{
eventFromEventPusher(project.NewProjectMemberRemovedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"user1",
)),
},
uniqueConstraintsFromEventConstraint(member.NewRemoveMemberUniqueConstraint("project1", "user1")),
),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
userID: "user1",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemoveProjectMember(tt.args.ctx, tt.args.projectID, tt.args.userID, tt.args.resourceOwner)
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)
}
})
}
}

View File

@@ -17,7 +17,7 @@ func (c *Commands) AddProjectRole(ctx context.Context, projectRole *domain.Proje
roleWriteModel := NewProjectRoleWriteModelWithKey(projectRole.Key, projectRole.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&roleWriteModel.WriteModel)
events, err := c.addProjectRoles(ctx, projectAgg, projectRole.AggregateID, projectRole)
events, err := c.addProjectRoles(ctx, projectAgg, projectRole)
if err != nil {
return nil, err
}
@@ -40,7 +40,7 @@ func (c *Commands) BulkAddProjectRole(ctx context.Context, projectID, resourceOw
roleWriteModel := NewProjectRoleWriteModel(projectID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&roleWriteModel.WriteModel)
events, err := c.addProjectRoles(ctx, projectAgg, projectID, projectRoles...)
events, err := c.addProjectRoles(ctx, projectAgg, projectRoles...)
if err != nil {
return details, err
}
@@ -56,11 +56,12 @@ func (c *Commands) BulkAddProjectRole(ctx context.Context, projectID, resourceOw
return writeModelToObjectDetails(&roleWriteModel.WriteModel), nil
}
func (c *Commands) addProjectRoles(ctx context.Context, projectAgg *eventstore.Aggregate, projectID string, projectRoles ...*domain.ProjectRole) ([]eventstore.EventPusher, error) {
func (c *Commands) addProjectRoles(ctx context.Context, projectAgg *eventstore.Aggregate, projectRoles ...*domain.ProjectRole) ([]eventstore.EventPusher, error) {
var events []eventstore.EventPusher
for _, projectRole := range projectRoles {
projectRole.AggregateID = projectAgg.ID
if !projectRole.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4m9vS", "Errors.Project.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.Role.Invalid")
}
events = append(events, project.NewRoleAddedEvent(
ctx,
@@ -68,7 +69,6 @@ func (c *Commands) addProjectRoles(ctx context.Context, projectAgg *eventstore.A
projectRole.Key,
projectRole.DisplayName,
projectRole.Group,
projectID,
))
}
@@ -77,7 +77,7 @@ func (c *Commands) addProjectRoles(ctx context.Context, projectAgg *eventstore.A
func (c *Commands) ChangeProjectRole(ctx context.Context, projectRole *domain.ProjectRole, resourceOwner string) (_ *domain.ProjectRole, err error) {
if !projectRole.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4m9vS", "Errors.Project.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.Invalid")
}
err = c.checkProjectExists(ctx, projectRole.AggregateID, resourceOwner)
if err != nil {
@@ -115,7 +115,7 @@ func (c *Commands) ChangeProjectRole(ctx context.Context, projectRole *domain.Pr
func (c *Commands) RemoveProjectRole(ctx context.Context, projectID, key, resourceOwner string, cascadingProjectGrantIds []string, cascadeUserGrantIDs ...string) (details *domain.ObjectDetails, err error) {
if projectID == "" || key == "" {
return details, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4m9vS", "Errors.Project.Role.Invalid")
return details, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.Role.Invalid")
}
existingRole, err := c.getProjectRoleWriteModelByID(ctx, key, projectID, resourceOwner)
if err != nil {
@@ -126,7 +126,7 @@ func (c *Commands) RemoveProjectRole(ctx context.Context, projectID, key, resour
}
projectAgg := ProjectAggregateFromWriteModel(&existingRole.WriteModel)
events := []eventstore.EventPusher{
project.NewRoleRemovedEvent(ctx, projectAgg, key, projectID),
project.NewRoleRemovedEvent(ctx, projectAgg, key),
}
for _, projectGrantID := range cascadingProjectGrantIds {

View File

@@ -101,7 +101,6 @@ func (wm *ProjectRoleWriteModel) NewProjectRoleChangedEvent(
) (*project.RoleChangedEvent, bool, error) {
changes := make([]project.RoleChanges, 0)
var err error
changes = append(changes, project.ChangeKey(key))
if wm.DisplayName != displayName {
changes = append(changes, project.ChangeDisplayName(displayName))
@@ -112,7 +111,7 @@ func (wm *ProjectRoleWriteModel) NewProjectRoleChangedEvent(
if len(changes) == 0 {
return nil, false, nil
}
changeEvent, err := project.NewRoleChangedEvent(ctx, aggregate, changes)
changeEvent, err := project.NewRoleChangedEvent(ctx, aggregate, key, changes)
if err != nil {
return nil, false, err
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -34,13 +34,9 @@ func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant
return nil, nil, err
}
if !userGrant.IsValid() {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0fs", "Errors.UserGrant.Invalid")
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0fs", "Errors.UserGrant.Invalid")
}
err = c.checkUserExists(ctx, userGrant.UserID, "")
if err != nil {
return nil, nil, err
}
err = c.checkProjectExists(ctx, userGrant.ProjectID, resourceOwner)
err = c.checkUserGrantPreCondition(ctx, userGrant)
if err != nil {
return nil, nil, err
}
@@ -83,10 +79,14 @@ func (c *Commands) changeUserGrant(ctx context.Context, userGrant *domain.UserGr
if err != nil {
return nil, nil, err
}
if userGrant.IsValid() {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0sd", "Errors.UserGrant.Invalid")
if !userGrant.IsValid() || userGrant.AggregateID == "" {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-3M0sd", "Errors.UserGrant.Invalid")
}
err = c.checkUserGrantPreCondition(ctx, userGrant)
if err != nil {
return nil, nil, err
}
existingUserGrant, err := c.userGrantWriteModelByID(ctx, userGrant.AggregateID, userGrant.ResourceOwner)
if err != nil {
return nil, nil, err
@@ -130,7 +130,7 @@ func (c *Commands) removeRoleFromUserGrant(ctx context.Context, userGrantID stri
if !keyExists {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5m8g9", "Errors.UserGrant.RoleKeyNotFound")
}
changedUserGrant := NewUserGrantWriteModel(userGrantID, "")
changedUserGrant := NewUserGrantWriteModel(userGrantID, existingUserGrant.ResourceOwner)
userGrantAgg := UserGrantAggregateFromWriteModel(&changedUserGrant.WriteModel)
if cascade {
@@ -142,22 +142,22 @@ func (c *Commands) removeRoleFromUserGrant(ctx context.Context, userGrantID stri
func (c *Commands) DeactivateUserGrant(ctx context.Context, grantID, resourceOwner string) (objectDetails *domain.ObjectDetails, err error) {
if grantID == "" || resourceOwner == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-M0dsf", "Errors.UserGrant.IDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-M0dsf", "Errors.UserGrant.IDMissing")
}
existingUserGrant, err := c.userGrantWriteModelByID(ctx, grantID, resourceOwner)
if err != nil {
return nil, err
}
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
if err != nil {
return nil, err
}
if existingUserGrant.State == domain.UserGrantStateUnspecified || existingUserGrant.State == domain.UserGrantStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.UserGrant.NotFound")
}
if existingUserGrant.State != domain.UserGrantStateActive {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-1S9gx", "Errors.UserGrant.NotActive")
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-1S9gx", "Errors.UserGrant.NotActive")
}
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
if err != nil {
return nil, err
}
deactivateUserGrant := NewUserGrantWriteModel(grantID, resourceOwner)
@@ -175,24 +175,23 @@ func (c *Commands) DeactivateUserGrant(ctx context.Context, grantID, resourceOwn
func (c *Commands) ReactivateUserGrant(ctx context.Context, grantID, resourceOwner string) (objectDetails *domain.ObjectDetails, err error) {
if grantID == "" || resourceOwner == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Qxy8v", "Errors.UserGrant.IDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Qxy8v", "Errors.UserGrant.IDMissing")
}
existingUserGrant, err := c.userGrantWriteModelByID(ctx, grantID, resourceOwner)
if err != nil {
return nil, err
}
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
if err != nil {
return nil, err
}
if existingUserGrant.State == domain.UserGrantStateUnspecified || existingUserGrant.State == domain.UserGrantStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Lp0gs", "Errors.UserGrant.NotFound")
}
if existingUserGrant.State != domain.UserGrantStateInactive {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-1ML0v", "Errors.UserGrant.NotInactive")
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-1ML0v", "Errors.UserGrant.NotInactive")
}
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
if err != nil {
return nil, err
}
deactivateUserGrant := NewUserGrantWriteModel(grantID, resourceOwner)
userGrantAgg := UserGrantAggregateFromWriteModel(&deactivateUserGrant.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, usergrant.NewUserGrantReactivatedEvent(ctx, userGrantAgg))
@@ -224,11 +223,14 @@ func (c *Commands) RemoveUserGrant(ctx context.Context, grantID, resourceOwner s
}
func (c *Commands) BulkRemoveUserGrant(ctx context.Context, grantIDs []string, resourceOwner string) (err error) {
if len(grantIDs) == 0 {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0sd", "Errors.UserGrant.IDMissing")
}
events := make([]eventstore.EventPusher, len(grantIDs))
for i, grantID := range grantIDs {
event, _, err := c.removeUserGrant(ctx, grantID, resourceOwner, false)
if err != nil {
return nil
return err
}
events[i] = event
}
@@ -238,13 +240,16 @@ func (c *Commands) BulkRemoveUserGrant(ctx context.Context, grantIDs []string, r
func (c *Commands) removeUserGrant(ctx context.Context, grantID, resourceOwner string, cascade bool) (_ eventstore.EventPusher, writeModel *UserGrantWriteModel, err error) {
if grantID == "" {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-J9sc5", "Errors.UserGrant.IDMissing")
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-J9sc5", "Errors.UserGrant.IDMissing")
}
existingUserGrant, err := c.userGrantWriteModelByID(ctx, grantID, resourceOwner)
if err != nil {
return nil, nil, err
}
if existingUserGrant.State == domain.UserGrantStateUnspecified || existingUserGrant.State == domain.UserGrantStateRemoved {
return nil, nil, caos_errs.ThrowNotFound(nil, "COMMAND-1My0t", "Errors.UserGrant.NotFound")
}
if !cascade {
err = checkExplicitProjectPermission(ctx, existingUserGrant.ProjectGrantID, existingUserGrant.ProjectID)
if err != nil {
@@ -252,11 +257,7 @@ func (c *Commands) removeUserGrant(ctx context.Context, grantID, resourceOwner s
}
}
if existingUserGrant.State == domain.UserGrantStateUnspecified || existingUserGrant.State == domain.UserGrantStateRemoved {
return nil, nil, caos_errs.ThrowNotFound(nil, "COMMAND-1My0t", "Errors.UserGrant.NotFound")
}
removeUserGrant := NewUserGrantWriteModel(grantID, resourceOwner)
removeUserGrant := NewUserGrantWriteModel(grantID, existingUserGrant.ResourceOwner)
userGrantAgg := UserGrantAggregateFromWriteModel(&removeUserGrant.WriteModel)
if cascade {
return usergrant.NewUserGrantCascadeRemovedEvent(
@@ -273,6 +274,7 @@ func (c *Commands) removeUserGrant(ctx context.Context, grantID, resourceOwner s
existingUserGrant.ProjectID,
existingUserGrant.ProjectGrantID), existingUserGrant, nil
}
func (c *Commands) userGrantWriteModelByID(ctx context.Context, userGrantID, resourceOwner string) (writeModel *UserGrantWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -284,3 +286,24 @@ func (c *Commands) userGrantWriteModelByID(ctx context.Context, userGrantID, res
}
return writeModel, nil
}
func (c *Commands) checkUserGrantPreCondition(ctx context.Context, usergrant *domain.UserGrant) error {
preConditions := NewUserGrantPreConditionReadModel(usergrant.UserID, usergrant.ProjectID, usergrant.ProjectGrantID)
err := c.eventstore.FilterToQueryReducer(ctx, preConditions)
if err != nil {
return err
}
if !preConditions.UserExists {
return caos_errs.ThrowPreconditionFailed(err, "COMMAND-4f8sg", "Errors.User.NotFound")
}
if !preConditions.ProjectExists {
return caos_errs.ThrowPreconditionFailed(err, "COMMAND-3n77S", "Errors.Project.NotFound")
}
if usergrant.ProjectGrantID != "" && !preConditions.ProjectGrantExists {
return caos_errs.ThrowPreconditionFailed(err, "COMMAND-4m9ff", "Errors.Project.Grant.NotFound")
}
if usergrant.HasInvalidRoles(preConditions.ExistingRoleKeys) {
return caos_errs.ThrowPreconditionFailed(err, "COMMAND-mm9F4", "Errors.Project.Role.NotFound")
}
return nil
}

View File

@@ -3,6 +3,8 @@ package command
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/project"
"github.com/caos/zitadel/internal/repository/user"
"github.com/caos/zitadel/internal/repository/usergrant"
)
@@ -76,3 +78,85 @@ func (wm *UserGrantWriteModel) Query() *eventstore.SearchQueryBuilder {
func UserGrantAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
return eventstore.AggregateFromWriteModel(wm, usergrant.AggregateType, usergrant.AggregateVersion)
}
type UserGrantPreConditionReadModel struct {
eventstore.WriteModel
UserID string
ProjectID string
ProjectGrantID string
UserExists bool
ProjectExists bool
ProjectGrantExists bool
ExistingRoleKeys []string
}
func NewUserGrantPreConditionReadModel(userID, projectID, projectGrantID string) *UserGrantPreConditionReadModel {
return &UserGrantPreConditionReadModel{
UserID: userID,
ProjectID: projectID,
ProjectGrantID: projectGrantID,
}
}
func (wm *UserGrantPreConditionReadModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *user.HumanAddedEvent:
wm.UserExists = true
case *user.MachineAddedEvent:
wm.UserExists = true
case *user.UserRemovedEvent:
wm.UserExists = false
case *project.ProjectAddedEvent:
wm.ProjectExists = true
case *project.ProjectRemovedEvent:
wm.ProjectExists = false
case *project.GrantAddedEvent:
if wm.ProjectGrantID == e.GrantID {
wm.ProjectGrantExists = true
}
wm.ExistingRoleKeys = e.RoleKeys
case *project.GrantChangedEvent:
wm.ExistingRoleKeys = e.RoleKeys
case *project.GrantRemovedEvent:
if wm.ProjectGrantID == e.GrantID {
wm.ProjectGrantExists = false
}
wm.ExistingRoleKeys = []string{}
case *project.RoleAddedEvent:
if wm.ProjectGrantID != "" {
continue
}
wm.ExistingRoleKeys = append(wm.ExistingRoleKeys, e.Key)
case *project.RoleRemovedEvent:
if wm.ProjectGrantID != "" {
continue
}
for i, key := range wm.ExistingRoleKeys {
if key == e.Key {
copy(wm.ExistingRoleKeys[i:], wm.ExistingRoleKeys[i+1:])
wm.ExistingRoleKeys[len(wm.ExistingRoleKeys)-1] = ""
wm.ExistingRoleKeys = wm.ExistingRoleKeys[:len(wm.ExistingRoleKeys)-1]
continue
}
}
}
}
return wm.WriteModel.Reduce()
}
func (wm *UserGrantPreConditionReadModel) Query() *eventstore.SearchQueryBuilder {
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType, project.AggregateType).
AggregateIDs(wm.UserID, wm.ProjectID).
EventTypes(user.HumanAddedType,
user.MachineAddedEventType,
user.UserRemovedType,
project.ProjectAddedType,
project.ProjectRemovedType,
project.GrantAddedType,
project.GrantRemovedType,
project.RoleAddedType,
project.RoleRemovedType)
return query
}

File diff suppressed because it is too large Load Diff