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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 14134 additions and 230 deletions

View File

@ -5,3 +5,8 @@ import "context"
func NewMockContext(orgID, userID string) context.Context {
return context.WithValue(context.Background(), dataKey, CtxData{UserID: userID, OrgID: orgID})
}
func NewMockContextWithPermissions(orgID, userID string, permissions []string) context.Context {
ctx := context.WithValue(context.Background(), dataKey, CtxData{UserID: userID, OrgID: orgID})
return context.WithValue(ctx, requestPermissionsKey, permissions)
}

View File

@ -126,7 +126,7 @@ func (s *Server) ListProjectGrantMembers(ctx context.Context, req *mgmt_pb.ListP
}
func (s *Server) AddProjectGrantMember(ctx context.Context, req *mgmt_pb.AddProjectGrantMemberRequest) (*mgmt_pb.AddProjectGrantMemberResponse, error) {
member, err := s.command.AddProjectGrantMember(ctx, AddProjectGrantMemberRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
member, err := s.command.AddProjectGrantMember(ctx, AddProjectGrantMemberRequestToDomain(req))
if err != nil {
return nil, err
}
@ -140,7 +140,7 @@ func (s *Server) AddProjectGrantMember(ctx context.Context, req *mgmt_pb.AddProj
}
func (s *Server) UpdateProjectGrantMember(ctx context.Context, req *mgmt_pb.UpdateProjectGrantMemberRequest) (*mgmt_pb.UpdateProjectGrantMemberResponse, error) {
member, err := s.command.ChangeProjectGrantMember(ctx, UpdateProjectGrantMemberRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
member, err := s.command.ChangeProjectGrantMember(ctx, UpdateProjectGrantMemberRequestToDomain(req))
if err != nil {
return nil, err
}
@ -154,7 +154,7 @@ func (s *Server) UpdateProjectGrantMember(ctx context.Context, req *mgmt_pb.Upda
}
func (s *Server) RemoveProjectGrantMember(ctx context.Context, req *mgmt_pb.RemoveProjectGrantMemberRequest) (*mgmt_pb.RemoveProjectGrantMemberResponse, error) {
details, err := s.command.RemoveProjectGrantMember(ctx, req.ProjectId, req.UserId, req.GrantId, authz.GetCtxData(ctx).OrgID)
details, err := s.command.RemoveProjectGrantMember(ctx, req.ProjectId, req.UserId, req.GrantId)
if err != nil {
return nil, err
}

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

View File

@ -34,7 +34,7 @@ const (
)
func (a *APIApp) IsValid() bool {
return true
return a.AppName != ""
}
func (a *APIApp) setClientID(clientID string) {

View File

@ -113,6 +113,9 @@ const (
)
func (a *OIDCApp) IsValid() bool {
if a.AppName == "" || a.ClockSkew > time.Second*5 || a.ClockSkew < time.Second*0 {
return false
}
grantTypes := a.getRequiredGrantTypes()
for _, grantType := range grantTypes {
ok := containsOIDCGrantType(a.GrantTypes, grantType)

View File

@ -0,0 +1,185 @@
package domain
import (
"github.com/caos/zitadel/internal/eventstore/v1/models"
"testing"
"time"
)
func TestApplicationValid(t *testing.T) {
type args struct {
app *OIDCApp
}
tests := []struct {
name string
args args
result bool
}{
{
name: "no app name",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
},
},
result: false,
},
{
name: "invalid clock skew",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "AppName",
ClockSkew: time.Minute * 1,
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
},
},
result: false,
},
{
name: "invalid clock skew minus",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "AppName",
ClockSkew: time.Minute * -1,
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
},
},
result: false,
},
{
name: "valid oidc application: responsetype code",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
},
},
result: true,
},
{
name: "invalid oidc application: responsetype code",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode},
GrantTypes: []OIDCGrantType{OIDCGrantTypeImplicit},
},
},
result: false,
},
{
name: "valid oidc application: responsetype id_token",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeIDToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeImplicit},
},
},
result: true,
},
{
name: "invalid oidc application: responsetype id_token",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeIDToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
},
},
result: false,
},
{
name: "valid oidc application: responsetype token_id_token",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeIDTokenToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeImplicit},
},
},
result: true,
},
{
name: "invalid oidc application: responsetype token_id_token",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeIDTokenToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
},
},
result: false,
},
{
name: "valid oidc application: responsetype code & id_token",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode, OIDCResponseTypeIDToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode, OIDCGrantTypeImplicit},
},
},
result: true,
},
{
name: "valid oidc application: responsetype code & token_id_token",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode, OIDCResponseTypeIDTokenToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode, OIDCGrantTypeImplicit},
},
},
result: true,
},
{
name: "valid oidc application: responsetype code & id_token & token_id_token",
args: args{
app: &OIDCApp{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
AppID: "AppID",
AppName: "Name",
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode, OIDCResponseTypeIDToken, OIDCResponseTypeIDTokenToken},
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode, OIDCGrantTypeImplicit},
},
},
result: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.args.app.IsValid()
if result != tt.result {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, result)
}
})
}
}

View File

@ -25,6 +25,10 @@ func (i *Member) IsValid() bool {
return i.AggregateID != "" && i.UserID != "" && len(i.Roles) != 0
}
func (i *Member) IsIAMValid() bool {
return i.UserID != "" && len(i.Roles) != 0
}
type MemberState int32
const (

View File

@ -11,3 +11,7 @@ type LabelPolicy struct {
PrimaryColor string
SecondaryColor string
}
func (p *LabelPolicy) IsValid() bool {
return p.PrimaryColor != "" && p.SecondaryColor != ""
}

View File

@ -7,12 +7,8 @@ import (
type Project struct {
models.ObjectRoot
State ProjectState
Name string
Members []*Member
Roles []*ProjectRole
//Applications []*Application
//Grants []*ProjectGrant
State ProjectState
Name string
ProjectRoleAssertion bool
ProjectRoleCheck bool
}

View File

@ -29,21 +29,21 @@ func (p *ProjectGrant) IsValid() bool {
return p.GrantedOrgID != ""
}
func GetRemovedRoles(existingRoles, newRoles []string) []string {
removed := make([]string, 0)
for _, role := range existingRoles {
if !containsKey(newRoles, role) {
removed = append(removed, role)
}
}
return removed
}
func containsKey(roles []string, key string) bool {
for _, role := range roles {
if role == key {
func (g *ProjectGrant) HasInvalidRoles(validRoles []string) bool {
for _, roleKey := range g.RoleKeys {
if !containsRoleKey(roleKey, validRoles) {
return true
}
}
return false
}
func GetRemovedRoles(existingRoles, newRoles []string) []string {
removed := make([]string, 0)
for _, role := range existingRoles {
if !containsRoleKey(role, newRoles) {
removed = append(removed, role)
}
}
return removed
}

View File

@ -27,3 +27,12 @@ func NewProjectRole(projectID, key string) *ProjectRole {
func (p *ProjectRole) IsValid() bool {
return p.AggregateID != "" && p.Key != ""
}
func containsRoleKey(roleKey string, validRoles []string) bool {
for _, validRole := range validRoles {
if roleKey == validRole {
return true
}
}
return false
}

View File

@ -24,3 +24,12 @@ const (
func (u *UserGrant) IsValid() bool {
return u.ProjectID != "" && u.UserID != ""
}
func (g *UserGrant) HasInvalidRoles(validRoles []string) bool {
for _, roleKey := range g.RoleKeys {
if !containsRoleKey(roleKey, validRoles) {
return true
}
}
return false
}

View File

@ -60,7 +60,7 @@ func (es *Eventstore) PushEvents(ctx context.Context, pushEvents ...EventPusher)
func eventsToRepository(pushEvents []EventPusher) (events []*repository.Event, constraints []*repository.UniqueConstraint, err error) {
events = make([]*repository.Event, len(pushEvents))
for i, event := range pushEvents {
data, err := eventData(event)
data, err := EventData(event)
if err != nil {
return nil, nil, err
}
@ -195,7 +195,7 @@ func (es *Eventstore) RegisterFilterEventMapper(eventType EventType, mapper func
return es
}
func eventData(event EventPusher) ([]byte, error) {
func EventData(event EventPusher) ([]byte, error) {
switch data := event.Data().(type) {
case nil:
return nil, nil

View File

@ -350,13 +350,13 @@ func Test_eventData(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := eventData(tt.args.event)
got, err := EventData(tt.args.event)
if (err != nil) != tt.res.wantErr {
t.Errorf("eventData() error = %v, wantErr %v", err, tt.res.wantErr)
t.Errorf("EventData() error = %v, wantErr %v", err, tt.res.wantErr)
return
}
if !reflect.DeepEqual(got, tt.res.jsonText) {
t.Errorf("eventData() = %v, want %v", string(got), string(tt.res.jsonText))
t.Errorf("EventData() = %v, want %v", string(got), string(tt.res.jsonText))
}
})
}

View File

@ -0,0 +1,3 @@
package mock
//go:generate mockgen -package mock -destination ./repository.mock.go github.com/caos/zitadel/internal/eventstore/repository Repository

View File

@ -0,0 +1,98 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/caos/zitadel/internal/eventstore/repository (interfaces: Repository)
// Package mock is a generated GoMock package.
package mock
import (
context "context"
repository "github.com/caos/zitadel/internal/eventstore/repository"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockRepository is a mock of Repository interface
type MockRepository struct {
ctrl *gomock.Controller
recorder *MockRepositoryMockRecorder
}
// MockRepositoryMockRecorder is the mock recorder for MockRepository
type MockRepositoryMockRecorder struct {
mock *MockRepository
}
// NewMockRepository creates a new mock instance
func NewMockRepository(ctrl *gomock.Controller) *MockRepository {
mock := &MockRepository{ctrl: ctrl}
mock.recorder = &MockRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
return m.recorder
}
// Filter mocks base method
func (m *MockRepository) Filter(arg0 context.Context, arg1 *repository.SearchQuery) ([]*repository.Event, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Filter", arg0, arg1)
ret0, _ := ret[0].([]*repository.Event)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Filter indicates an expected call of Filter
func (mr *MockRepositoryMockRecorder) Filter(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filter", reflect.TypeOf((*MockRepository)(nil).Filter), arg0, arg1)
}
// Health mocks base method
func (m *MockRepository) Health(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Health", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Health indicates an expected call of Health
func (mr *MockRepositoryMockRecorder) Health(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Health", reflect.TypeOf((*MockRepository)(nil).Health), arg0)
}
// LatestSequence mocks base method
func (m *MockRepository) LatestSequence(arg0 context.Context, arg1 *repository.SearchQuery) (uint64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LatestSequence", arg0, arg1)
ret0, _ := ret[0].(uint64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LatestSequence indicates an expected call of LatestSequence
func (mr *MockRepositoryMockRecorder) LatestSequence(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LatestSequence", reflect.TypeOf((*MockRepository)(nil).LatestSequence), arg0, arg1)
}
// Push mocks base method
func (m *MockRepository) Push(arg0 context.Context, arg1 []*repository.Event, arg2 ...*repository.UniqueConstraint) error {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Push", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// Push indicates an expected call of Push
func (mr *MockRepositoryMockRecorder) Push(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Push", reflect.TypeOf((*MockRepository)(nil).Push), varargs...)
}

View File

@ -0,0 +1,53 @@
package mock
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/eventstore/repository"
)
func NewRepo(t *testing.T) *MockRepository {
return NewMockRepository(gomock.NewController(t))
}
func (m *MockRepository) ExpectFilterNoEventsNoError() *MockRepository {
m.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(nil, nil)
return m
}
func (m *MockRepository) ExpectFilterEvents(events ...*repository.Event) *MockRepository {
m.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(events, nil)
return m
}
func (m *MockRepository) ExpectPush(expectedEvents []*repository.Event, expectedUniqueConstraints ...*repository.UniqueConstraint) *MockRepository {
m.EXPECT().Push(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
func(ctx context.Context, events []*repository.Event, uniqueConstraints ...*repository.UniqueConstraint) error {
assert.Equal(m.ctrl.T, expectedEvents, events)
if expectedUniqueConstraints == nil {
expectedUniqueConstraints = []*repository.UniqueConstraint{}
}
assert.Equal(m.ctrl.T, expectedUniqueConstraints, uniqueConstraints)
return nil
},
)
return m
}
func (m *MockRepository) ExpectPushFailed(err error, expectedEvents []*repository.Event, expectedUniqueConstraints ...*repository.UniqueConstraint) *MockRepository {
m.EXPECT().Push(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
func(ctx context.Context, events []*repository.Event, uniqueConstraints ...*repository.UniqueConstraint) error {
assert.Equal(m.ctrl.T, expectedEvents, events)
if expectedUniqueConstraints == nil {
expectedUniqueConstraints = []*repository.UniqueConstraint{}
}
assert.Equal(m.ctrl.T, expectedUniqueConstraints, uniqueConstraints)
return err
},
)
return m
}

View File

@ -0,0 +1,27 @@
package mock
import (
"testing"
"github.com/golang/mock/gomock"
)
func NewIDGenerator(t *testing.T) *MockGenerator {
m := NewMockGenerator(gomock.NewController(t))
m.EXPECT().Next().Return("1", nil)
return m
}
func NewIDGeneratorExpectIDs(t *testing.T, ids ...string) *MockGenerator {
m := NewMockGenerator(gomock.NewController(t))
for _, id := range ids {
m.EXPECT().Next().Return(id, nil)
}
return m
}
func ExpectID(t *testing.T, id string) *MockGenerator {
m := NewMockGenerator(gomock.NewController(t))
m.EXPECT().Next().Return(id, nil)
return m
}

View File

@ -1,6 +1,7 @@
package iam
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
@ -16,3 +17,14 @@ const (
type Aggregate struct {
eventstore.Aggregate
}
func NewAggregate() *Aggregate {
return &Aggregate{
Aggregate: eventstore.Aggregate{
Typ: AggregateType,
Version: AggregateVersion,
ID: domain.IAMID,
ResourceOwner: domain.IAMID,
},
}
}

View File

@ -15,6 +15,7 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(LoginPolicyAddedEventType, LoginPolicyAddedEventMapper).
RegisterFilterEventMapper(LoginPolicyChangedEventType, LoginPolicyChangedEventMapper).
RegisterFilterEventMapper(OrgIAMPolicyAddedEventType, OrgIAMPolicyAddedEventMapper).
RegisterFilterEventMapper(OrgIAMPolicyChangedEventType, OrgIAMPolicyChangedEventMapper).
RegisterFilterEventMapper(PasswordAgePolicyAddedEventType, PasswordAgePolicyAddedEventMapper).
RegisterFilterEventMapper(PasswordAgePolicyChangedEventType, PasswordAgePolicyChangedEventMapper).
RegisterFilterEventMapper(PasswordComplexityPolicyAddedEventType, PasswordComplexityPolicyAddedEventMapper).

View File

@ -16,3 +16,14 @@ const (
type Aggregate struct {
eventstore.Aggregate
}
func NewAggregate(id, resourceOwner string) *Aggregate {
return &Aggregate{
Aggregate: eventstore.Aggregate{
Typ: AggregateType,
Version: AggregateVersion,
ID: id,
ResourceOwner: resourceOwner,
},
}
}

View File

@ -12,3 +12,14 @@ const (
type Aggregate struct {
eventstore.Aggregate
}
func NewAggregate(id, resourceOwner string) *Aggregate {
return &Aggregate{
Aggregate: eventstore.Aggregate{
Typ: AggregateType,
Version: AggregateVersion,
ID: id,
ResourceOwner: resourceOwner,
},
}
}

View File

@ -36,9 +36,8 @@ func NewRemoveApplicationUniqueConstraint(name, projectID string) *eventstore.Ev
type ApplicationAddedEvent struct {
eventstore.BaseEvent `json:"-"`
AppID string `json:"appId,omitempty"`
Name string `json:"name,omitempty"`
projectID string
AppID string `json:"appId,omitempty"`
Name string `json:"name,omitempty"`
}
func (e *ApplicationAddedEvent) Data() interface{} {
@ -46,15 +45,14 @@ func (e *ApplicationAddedEvent) Data() interface{} {
}
func (e *ApplicationAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewAddApplicationUniqueConstraint(e.Name, e.projectID)}
return []*eventstore.EventUniqueConstraint{NewAddApplicationUniqueConstraint(e.Name, e.Aggregate().ID)}
}
func NewApplicationAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
appID,
name,
projectID string,
name string,
) *ApplicationAddedEvent {
return &ApplicationAddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@ -62,9 +60,8 @@ func NewApplicationAddedEvent(
aggregate,
ApplicationAddedType,
),
AppID: appID,
Name: name,
projectID: projectID,
AppID: appID,
Name: name,
}
}
@ -84,10 +81,9 @@ func ApplicationAddedEventMapper(event *repository.Event) (eventstore.EventReade
type ApplicationChangedEvent struct {
eventstore.BaseEvent `json:"-"`
AppID string `json:"appId,omitempty"`
Name string `json:"name,omitempty"`
oldName string
projectID string
AppID string `json:"appId,omitempty"`
Name string `json:"name,omitempty"`
oldName string
}
func (e *ApplicationChangedEvent) Data() interface{} {
@ -96,8 +92,8 @@ func (e *ApplicationChangedEvent) Data() interface{} {
func (e *ApplicationChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{
NewRemoveApplicationUniqueConstraint(e.oldName, e.projectID),
NewAddApplicationUniqueConstraint(e.Name, e.projectID),
NewRemoveApplicationUniqueConstraint(e.oldName, e.Aggregate().ID),
NewAddApplicationUniqueConstraint(e.Name, e.Aggregate().ID),
}
}
@ -106,8 +102,7 @@ func NewApplicationChangedEvent(
aggregate *eventstore.Aggregate,
appID,
oldName,
newName,
projectID string,
newName string,
) *ApplicationChangedEvent {
return &ApplicationChangedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@ -115,10 +110,9 @@ func NewApplicationChangedEvent(
aggregate,
ApplicationChangedType,
),
AppID: appID,
Name: newName,
oldName: oldName,
projectID: projectID,
AppID: appID,
Name: newName,
oldName: oldName,
}
}
@ -222,9 +216,8 @@ func ApplicationReactivatedEventMapper(event *repository.Event) (eventstore.Even
type ApplicationRemovedEvent struct {
eventstore.BaseEvent `json:"-"`
AppID string `json:"appId,omitempty"`
name string
projectID string
AppID string `json:"appId,omitempty"`
name string
}
func (e *ApplicationRemovedEvent) Data() interface{} {
@ -232,15 +225,14 @@ func (e *ApplicationRemovedEvent) Data() interface{} {
}
func (e *ApplicationRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewRemoveApplicationUniqueConstraint(e.name, e.projectID)}
return []*eventstore.EventUniqueConstraint{NewRemoveApplicationUniqueConstraint(e.name, e.Aggregate().ID)}
}
func NewApplicationRemovedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
appID,
name,
projectID string,
name string,
) *ApplicationRemovedEvent {
return &ApplicationRemovedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@ -248,9 +240,8 @@ func NewApplicationRemovedEvent(
aggregate,
ApplicationRemovedType,
),
AppID: appID,
name: name,
projectID: projectID,
AppID: appID,
name: name,
}
}

View File

@ -40,7 +40,6 @@ type GrantAddedEvent struct {
GrantID string `json:"grantId,omitempty"`
GrantedOrgID string `json:"grantedOrgId,omitempty"`
RoleKeys []string `json:"roleKeys,omitempty"`
projectID string
}
func (e *GrantAddedEvent) Data() interface{} {
@ -48,15 +47,14 @@ func (e *GrantAddedEvent) Data() interface{} {
}
func (e *GrantAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewAddProjectGrantUniqueConstraint(e.GrantedOrgID, e.projectID)}
return []*eventstore.EventUniqueConstraint{NewAddProjectGrantUniqueConstraint(e.GrantedOrgID, e.Aggregate().ID)}
}
func NewGrantAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
grantID,
grantedOrgID,
projectID string,
grantedOrgID string,
roleKeys []string,
) *GrantAddedEvent {
return &GrantAddedEvent{
@ -68,7 +66,6 @@ func NewGrantAddedEvent(
GrantID: grantID,
GrantedOrgID: grantedOrgID,
RoleKeys: roleKeys,
projectID: projectID,
}
}
@ -264,7 +261,6 @@ type GrantRemovedEvent struct {
GrantID string `json:"grantId,omitempty"`
grantedOrgID string
projectID string
}
func (e *GrantRemovedEvent) Data() interface{} {
@ -272,15 +268,14 @@ func (e *GrantRemovedEvent) Data() interface{} {
}
func (e *GrantRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewRemoveProjectGrantUniqueConstraint(e.grantedOrgID, e.projectID)}
return []*eventstore.EventUniqueConstraint{NewRemoveProjectGrantUniqueConstraint(e.grantedOrgID, e.Aggregate().ID)}
}
func NewGrantRemovedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
grantID,
grantedOrgID,
projectID string,
grantedOrgID string,
) *GrantRemovedEvent {
return &GrantRemovedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@ -289,7 +284,6 @@ func NewGrantRemovedEvent(
GrantRemovedType,
),
GrantID: grantID,
projectID: projectID,
grantedOrgID: grantedOrgID,
}
}

View File

@ -35,10 +35,9 @@ func NewRemoveProjectGrantMemberUniqueConstraint(projectID, userID, grantID stri
type GrantMemberAddedEvent struct {
eventstore.BaseEvent `json:"-"`
Roles []string `json:"roles"`
UserID string `json:"userId"`
GrantID string `json:"grantId"`
projectID string
Roles []string `json:"roles"`
UserID string `json:"userId"`
GrantID string `json:"grantId"`
}
func (e *GrantMemberAddedEvent) Data() interface{} {
@ -46,13 +45,12 @@ func (e *GrantMemberAddedEvent) Data() interface{} {
}
func (e *GrantMemberAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewAddProjectGrantMemberUniqueConstraint(e.projectID, e.UserID, e.GrantID)}
return []*eventstore.EventUniqueConstraint{NewAddProjectGrantMemberUniqueConstraint(e.Aggregate().ID, e.UserID, e.GrantID)}
}
func NewProjectGrantMemberAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
projectID,
userID,
grantID string,
roles ...string,
@ -63,10 +61,9 @@ func NewProjectGrantMemberAddedEvent(
aggregate,
GrantMemberAddedType,
),
projectID: projectID,
UserID: userID,
GrantID: grantID,
Roles: roles,
UserID: userID,
GrantID: grantID,
Roles: roles,
}
}
@ -134,9 +131,8 @@ func GrantMemberChangedEventMapper(event *repository.Event) (eventstore.EventRea
type GrantMemberRemovedEvent struct {
eventstore.BaseEvent `json:"-"`
UserID string `json:"userId"`
GrantID string `json:"grantId"`
projectID string
UserID string `json:"userId"`
GrantID string `json:"grantId"`
}
func (e *GrantMemberRemovedEvent) Data() interface{} {
@ -144,13 +140,12 @@ func (e *GrantMemberRemovedEvent) Data() interface{} {
}
func (e *GrantMemberRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewRemoveProjectGrantMemberUniqueConstraint(e.projectID, e.UserID, e.GrantID)}
return []*eventstore.EventUniqueConstraint{NewRemoveProjectGrantMemberUniqueConstraint(e.Aggregate().ID, e.UserID, e.GrantID)}
}
func NewProjectGrantMemberRemovedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
projectID,
userID,
grantID string,
) *GrantMemberRemovedEvent {
@ -160,9 +155,8 @@ func NewProjectGrantMemberRemovedEvent(
aggregate,
GrantMemberRemovedType,
),
UserID: userID,
GrantID: grantID,
projectID: projectID,
UserID: userID,
GrantID: grantID,
}
}

View File

@ -37,7 +37,6 @@ type RoleAddedEvent struct {
Key string `json:"key,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Group string `json:"group,omitempty"`
projectID string
}
func (e *RoleAddedEvent) Data() interface{} {
@ -45,7 +44,7 @@ func (e *RoleAddedEvent) Data() interface{} {
}
func (e *RoleAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewAddProjectRoleUniqueConstraint(e.Key, e.projectID)}
return []*eventstore.EventUniqueConstraint{NewAddProjectRoleUniqueConstraint(e.Key, e.Aggregate().ID)}
}
func NewRoleAddedEvent(
@ -53,8 +52,7 @@ func NewRoleAddedEvent(
aggregate *eventstore.Aggregate,
key,
displayName,
group,
projectID string,
group string,
) *RoleAddedEvent {
return &RoleAddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@ -65,7 +63,6 @@ func NewRoleAddedEvent(
Key: key,
DisplayName: displayName,
Group: group,
projectID: projectID,
}
}
@ -101,6 +98,7 @@ func (e *RoleChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstrai
func NewRoleChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
key string,
changes []RoleChanges,
) (*RoleChangedEvent, error) {
if len(changes) == 0 {
@ -112,6 +110,7 @@ func NewRoleChangedEvent(
aggregate,
RoleChangedType,
),
Key: key,
}
for _, change := range changes {
change(changeEvent)
@ -154,8 +153,7 @@ func RoleChangedEventMapper(event *repository.Event) (eventstore.EventReader, er
type RoleRemovedEvent struct {
eventstore.BaseEvent `json:"-"`
Key string `json:"key,omitempty"`
projectID string `json:"-"`
Key string `json:"key,omitempty"`
}
func (e *RoleRemovedEvent) Data() interface{} {
@ -163,22 +161,20 @@ func (e *RoleRemovedEvent) Data() interface{} {
}
func (e *RoleRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewRemoveProjectRoleUniqueConstraint(e.Key, e.projectID)}
return []*eventstore.EventUniqueConstraint{NewRemoveProjectRoleUniqueConstraint(e.Key, e.Aggregate().ID)}
}
func NewRoleRemovedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
key,
projectID string) *RoleRemovedEvent {
key string) *RoleRemovedEvent {
return &RoleRemovedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
RoleRemovedType,
),
Key: key,
projectID: projectID,
Key: key,
}
}

View File

@ -12,3 +12,14 @@ const (
type Aggregate struct {
eventstore.Aggregate
}
func NewAggregate(id, resourceOwner string) *Aggregate {
return &Aggregate{
Aggregate: eventstore.Aggregate{
Typ: AggregateType,
Version: AggregateVersion,
ID: id,
ResourceOwner: resourceOwner,
},
}
}

View File

@ -12,3 +12,14 @@ const (
type Aggregate struct {
eventstore.Aggregate
}
func NewAggregate(id, resourceOwner string) *Aggregate {
return &Aggregate{
Aggregate: eventstore.Aggregate{
Typ: AggregateType,
Version: AggregateVersion,
ID: id,
ResourceOwner: resourceOwner,
},
}
}

View File

@ -2860,14 +2860,7 @@ message BulkRemoveUserGrantRequest {
repeated string grant_id = 1;
}
message BulkRemoveUserGrantResponse {
message RemovedUserGrant {
zitadel.v1.ObjectDetails details = 1;
string grant_id = 2;
}
repeated RemovedUserGrant result = 1;
}
message BulkRemoveUserGrantResponse {}
message GetOrgIAMPolicyRequest {}