feat: setup and iam commands (#99)

* start org

* refactor(eventstore): filter in sql for querier

* feat(eventstore): Aggregate precondition

preconditions are checked right before insert. Insert is still transaction save

* feat(eventstore): check preconditions in repository

* test(eventstore): test precondition in models

* test(eventstore): precondition-tests

* start org

* refactor(eventstore): filter in sql for querier

* feat(eventstore): Aggregate precondition

preconditions are checked right before insert. Insert is still transaction save

* feat(admin): start implement org

* feat(eventstore): check preconditions in repository

* fix(eventstore): data as NULL if empty
refactor(eventstore): naming in sequence methods

* feat(admin): org command side

* feat(management): start org-repo

* feat(org): member

* fix: replace ObjectRoot.ID with ObjectRoot.AggregateID

* aggregateID

* add remove,change member

* refactor(org): namings

* refactor(eventstore): querier as type

* fix(precondition): rename validation from precondition to validation

* test(eventstore): isErr func instead of wantErr bool

* fix(tests): Data

* fix(eventstore): correct check for existing events in push,
simplify insert statement

* fix(eventstore): aggregate id public

* test(org): eventsourcing

* test(org): eventstore

* test(org): deactivate, reactivate, orgbyid

* test(org): getMemberByIDs

* tests

* running tests

* add config

* add user repo to admin

* thorw not found if no org found

* iam setup

* eventstore tests done

* setup iam

* lauft

* iam eventstore

* validate if user is already member of org

* modules

* delete unused file

* iam member

* add member validation test

* iam member

* return error if unable to validat member

* generate org id once,
set resourceowner of org

* start iam repo

* set resourceowner on unique aggregates

* setup user const

* better code

* generate files

* fix tests

* Update internal/admin/repository/eventsourcing/repository.go

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

* set ctx data

Co-authored-by: adlerhurst <silvan.reusser@gmail.com>
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi
2020-05-18 11:32:16 +02:00
committed by GitHub
parent b9c938594c
commit 8203f2dad3
30 changed files with 16472 additions and 13368 deletions

View File

@@ -0,0 +1,35 @@
package eventsourcing
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/cache"
"github.com/caos/zitadel/internal/cache/config"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
)
type IamCache struct {
iamCache cache.Cache
}
func StartCache(conf *config.CacheConfig) (*IamCache, error) {
iamCache, err := conf.Config.NewCache()
logging.Log("EVENT-9siew").OnError(err).Panic("unable to create iam cache")
return &IamCache{iamCache: iamCache}, nil
}
func (c *IamCache) getIam(ID string) *model.Iam {
user := &model.Iam{ObjectRoot: models.ObjectRoot{AggregateID: ID}}
if err := c.iamCache.Get(ID, user); err != nil {
logging.Log("EVENT-slo9x").WithError(err).Debug("error in getting cache")
}
return user
}
func (c *IamCache) cacheIam(iam *model.Iam) {
err := c.iamCache.Set(iam.AggregateID, iam)
if err != nil {
logging.Log("EVENT-os03w").WithError(err).Debug("error in setting iam cache")
}
}

View File

@@ -0,0 +1,190 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/cache/config"
sd "github.com/caos/zitadel/internal/config/systemdefaults"
caos_errs "github.com/caos/zitadel/internal/errors"
es_int "github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/models"
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
)
type IamEventstore struct {
es_int.Eventstore
iamCache *IamCache
}
type IamConfig struct {
es_int.Eventstore
Cache *config.CacheConfig
}
func StartIam(conf IamConfig, systemDefaults sd.SystemDefaults) (*IamEventstore, error) {
iamCache, err := StartCache(conf.Cache)
if err != nil {
return nil, err
}
return &IamEventstore{
Eventstore: conf.Eventstore,
iamCache: iamCache,
}, nil
}
func (es *IamEventstore) IamByID(ctx context.Context, id string) (*iam_model.Iam, error) {
iam := es.iamCache.getIam(id)
query, err := IamByIDQuery(iam.AggregateID, iam.Sequence)
if err != nil {
return nil, err
}
err = es_sdk.Filter(ctx, es.FilterEvents, iam.AppendEvents, query)
if err != nil && caos_errs.IsNotFound(err) && iam.Sequence == 0 {
return nil, err
}
es.iamCache.cacheIam(iam)
return model.IamToModel(iam), nil
}
func (es *IamEventstore) StartSetup(ctx context.Context, iamID string) (*iam_model.Iam, error) {
iam, err := es.IamByID(ctx, iamID)
if err != nil && !caos_errs.IsNotFound(err) {
return nil, err
}
if iam != nil && iam.SetUpStarted {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9so34", "Setup already started")
}
repoIam := &model.Iam{ObjectRoot: models.ObjectRoot{AggregateID: iamID}}
createAggregate := IamSetupStartedAggregate(es.AggregateCreator(), repoIam)
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, createAggregate)
if err != nil {
return nil, err
}
es.iamCache.cacheIam(repoIam)
return model.IamToModel(repoIam), nil
}
func (es *IamEventstore) SetupDone(ctx context.Context, iamID string) (*iam_model.Iam, error) {
iam, err := es.IamByID(ctx, iamID)
if err != nil {
return nil, err
}
repoIam := model.IamFromModel(iam)
createAggregate := IamSetupDoneAggregate(es.AggregateCreator(), repoIam)
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, createAggregate)
if err != nil {
return nil, err
}
es.iamCache.cacheIam(repoIam)
return model.IamToModel(repoIam), nil
}
func (es *IamEventstore) SetGlobalOrg(ctx context.Context, iamID, globalOrg string) (*iam_model.Iam, error) {
iam, err := es.IamByID(ctx, iamID)
if err != nil {
return nil, err
}
repoIam := model.IamFromModel(iam)
createAggregate := IamSetGlobalOrgAggregate(es.AggregateCreator(), repoIam, globalOrg)
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, createAggregate)
if err != nil {
return nil, err
}
es.iamCache.cacheIam(repoIam)
return model.IamToModel(repoIam), nil
}
func (es *IamEventstore) SetIamProject(ctx context.Context, iamID, iamProjectID string) (*iam_model.Iam, error) {
iam, err := es.IamByID(ctx, iamID)
if err != nil {
return nil, err
}
repoIam := model.IamFromModel(iam)
createAggregate := IamSetIamProjectAggregate(es.AggregateCreator(), repoIam, iamProjectID)
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, createAggregate)
if err != nil {
return nil, err
}
es.iamCache.cacheIam(repoIam)
return model.IamToModel(repoIam), nil
}
func (es *IamEventstore) AddIamMember(ctx context.Context, member *iam_model.IamMember) (*iam_model.IamMember, error) {
if !member.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-89osr", "UserID and Roles are required")
}
existing, err := es.IamByID(ctx, member.AggregateID)
if err != nil {
return nil, err
}
if _, m := existing.GetMember(member.UserID); m != nil {
return nil, caos_errs.ThrowAlreadyExists(nil, "EVENT-idke6", "User is already member of this Iam")
}
repoIam := model.IamFromModel(existing)
repoMember := model.IamMemberFromModel(member)
addAggregate := IamMemberAddedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoMember)
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, addAggregate)
if err != nil {
return nil, err
}
es.iamCache.cacheIam(repoIam)
if _, m := model.GetIamMember(repoIam.Members, member.UserID); m != nil {
return model.IamMemberToModel(m), nil
}
return nil, caos_errs.ThrowInternal(nil, "EVENT-s90pw", "Could not find member in list")
}
func (es *IamEventstore) ChangeIamMember(ctx context.Context, member *iam_model.IamMember) (*iam_model.IamMember, error) {
if !member.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-s9ipe", "UserID and Roles are required")
}
existing, err := es.IamByID(ctx, member.AggregateID)
if err != nil {
return nil, err
}
if _, m := existing.GetMember(member.UserID); m == nil {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-s7ucs", "User is not member of this project")
}
repoIam := model.IamFromModel(existing)
repoMember := model.IamMemberFromModel(member)
projectAggregate := IamMemberChangedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoMember)
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, projectAggregate)
es.iamCache.cacheIam(repoIam)
if _, m := model.GetIamMember(repoIam.Members, member.UserID); m != nil {
return model.IamMemberToModel(m), nil
}
return nil, caos_errs.ThrowInternal(nil, "EVENT-29cws", "Could not find member in list")
}
func (es *IamEventstore) RemoveIamMember(ctx context.Context, member *iam_model.IamMember) error {
if member.UserID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-0pors", "UserID and Roles are required")
}
existing, err := es.IamByID(ctx, member.AggregateID)
if err != nil {
return err
}
if _, m := existing.GetMember(member.UserID); m == nil {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-29skr", "User is not member of this project")
}
repoIam := model.IamFromModel(existing)
repoMember := model.IamMemberFromModel(member)
projectAggregate := IamMemberRemovedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoMember)
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, projectAggregate)
es.iamCache.cacheIam(repoIam)
return err
}

View File

@@ -0,0 +1,74 @@
package eventsourcing
import (
"encoding/json"
mock_cache "github.com/caos/zitadel/internal/cache/mock"
"github.com/caos/zitadel/internal/eventstore/mock"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/golang/mock/gomock"
)
func GetMockedEventstore(ctrl *gomock.Controller, mockEs *mock.MockEventstore) *IamEventstore {
return &IamEventstore{
Eventstore: mockEs,
iamCache: GetMockCache(ctrl),
}
}
func GetMockCache(ctrl *gomock.Controller) *IamCache {
mockCache := mock_cache.NewMockCache(ctrl)
mockCache.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockCache.EXPECT().Set(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
return &IamCache{iamCache: mockCache}
}
func GetMockIamByIDOK(ctrl *gomock.Controller) *IamEventstore {
data, _ := json.Marshal(model.Iam{GlobalOrgID: "GlobalOrgID"})
events := []*es_models.Event{
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.IamSetupStarted},
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.GlobalOrgSet, Data: data},
}
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
return GetMockedEventstore(ctrl, mockEs)
}
func GetMockIamByIDNoEvents(ctrl *gomock.Controller) *IamEventstore {
events := []*es_models.Event{}
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
return GetMockedEventstore(ctrl, mockEs)
}
func GetMockManipulateIam(ctrl *gomock.Controller) *IamEventstore {
events := []*es_models.Event{
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.IamSetupStarted},
}
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
return GetMockedEventstore(ctrl, mockEs)
}
func GetMockManipulateIamWithMember(ctrl *gomock.Controller) *IamEventstore {
memberData, _ := json.Marshal(model.IamMember{UserID: "UserID", Roles: []string{"Role"}})
events := []*es_models.Event{
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.IamSetupStarted},
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.IamMemberAdded, Data: memberData},
}
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
return GetMockedEventstore(ctrl, mockEs)
}
func GetMockManipulateIamNotExisting(ctrl *gomock.Controller) *IamEventstore {
events := []*es_models.Event{}
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
return GetMockedEventstore(ctrl, mockEs)
}

View File

@@ -0,0 +1,637 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
caos_errs "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/golang/mock/gomock"
"testing"
)
func TestIamByID(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *IamEventstore
iam *model.Iam
}
type res struct {
iam *model.Iam
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "iam from events, ok",
args: args{
es: GetMockIamByIDOK(ctrl),
iam: &model.Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
},
res: res{
iam: &model.Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
},
},
{
name: "iam from events, no events",
args: args{
es: GetMockIamByIDNoEvents(ctrl),
iam: &model.Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
},
res: res{
errFunc: caos_errs.IsNotFound,
},
},
{
name: "iam from events, no id",
args: args{
es: GetMockIamByIDNoEvents(ctrl),
iam: &model.Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "", Sequence: 1}},
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.IamByID(nil, tt.args.iam.AggregateID)
if tt.res.errFunc == nil && result.AggregateID != tt.res.iam.AggregateID {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.iam.AggregateID, result.AggregateID)
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestSetUpStarted(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *IamEventstore
ctx context.Context
iamID string
}
type res struct {
iam *model.Iam
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "setup started iam, ok",
args: args{
es: GetMockManipulateIamNotExisting(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
iamID: "iamID",
},
res: res{
iam: &model.Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "iamID", Sequence: 1}, SetUpStarted: true},
},
},
{
name: "setup already started",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
iamID: "iamID",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "setup iam no id",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.StartSetup(tt.args.ctx, tt.args.iamID)
if tt.res.errFunc == nil && result.AggregateID == "" {
t.Errorf("result has no id")
}
if tt.res.errFunc == nil && result.SetUpStarted != tt.res.iam.SetUpStarted {
t.Errorf("got wrong result setupStarted: expected: %v, actual: %v ", tt.res.iam.SetUpStarted, result.SetUpStarted)
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestSetUpDone(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *IamEventstore
ctx context.Context
iamID string
}
type res struct {
iam *model.Iam
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "setup done iam, ok",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
iamID: "iamID",
},
res: res{
iam: &model.Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "iamID", Sequence: 1}, SetUpStarted: true, SetUpDone: true},
},
},
{
name: "setup iam no id",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "iam not found",
args: args{
es: GetMockManipulateIamNotExisting(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
iamID: "iamID",
},
res: res{
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.SetupDone(tt.args.ctx, tt.args.iamID)
if tt.res.errFunc == nil && result.AggregateID == "" {
t.Errorf("result has no id")
}
if tt.res.errFunc == nil && result.SetUpDone != tt.res.iam.SetUpDone {
t.Errorf("got wrong result SetUpDone: expected: %v, actual: %v ", tt.res.iam.SetUpDone, result.SetUpDone)
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestSetGlobalOrg(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *IamEventstore
ctx context.Context
iamID string
globalOrg string
}
type res struct {
iam *model.Iam
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "global org set, ok",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
iamID: "iamID",
globalOrg: "globalOrg",
},
res: res{
iam: &model.Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "iamID", Sequence: 1}, SetUpStarted: true, GlobalOrgID: "globalOrg"},
},
},
{
name: "no iam id",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
globalOrg: "",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "no global org",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
iamID: "iamID",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "iam not found",
args: args{
es: GetMockManipulateIamNotExisting(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
iamID: "iamID",
globalOrg: "globalOrg",
},
res: res{
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.SetGlobalOrg(tt.args.ctx, tt.args.iamID, tt.args.globalOrg)
if tt.res.errFunc == nil && result.AggregateID == "" {
t.Errorf("result has no id")
}
if tt.res.errFunc == nil && result.GlobalOrgID != tt.res.iam.GlobalOrgID {
t.Errorf("got wrong result GlobalOrgID: expected: %v, actual: %v ", tt.res.iam.GlobalOrgID, result.GlobalOrgID)
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestSetIamProjectID(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *IamEventstore
ctx context.Context
iamID string
iamProjectID string
}
type res struct {
iam *model.Iam
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "iam project set, ok",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
iamID: "iamID",
iamProjectID: "iamProjectID",
},
res: res{
iam: &model.Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "iamID", Sequence: 1}, SetUpStarted: true, IamProjectID: "iamProjectID"},
},
},
{
name: "no iam id",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
iamProjectID: "",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "no global org",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
iamID: "iamID",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "iam not found",
args: args{
es: GetMockManipulateIamNotExisting(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
iamID: "iamID",
iamProjectID: "iamProjectID",
},
res: res{
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.SetIamProject(tt.args.ctx, tt.args.iamID, tt.args.iamProjectID)
if tt.res.errFunc == nil && result.AggregateID == "" {
t.Errorf("result has no id")
}
if tt.res.errFunc == nil && result.IamProjectID != tt.res.iam.IamProjectID {
t.Errorf("got wrong result IamProjectID: expected: %v, actual: %v ", tt.res.iam.IamProjectID, result.IamProjectID)
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestAddIamMember(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *IamEventstore
ctx context.Context
member *iam_model.IamMember
}
type res struct {
result *iam_model.IamMember
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "add iam member, ok",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
member: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
},
res: res{
result: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
},
},
{
name: "no userid",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
member: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, Roles: []string{"Roles"}},
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "no roles",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
member: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID"},
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "member already existing",
args: args{
es: GetMockManipulateIamWithMember(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
member: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
},
res: res{
errFunc: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "existing iam not found",
args: args{
es: GetMockManipulateIamNotExisting(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
member: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
},
res: res{
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.AddIamMember(tt.args.ctx, tt.args.member)
if tt.res.errFunc == nil && result.AggregateID == "" {
t.Errorf("result has no id")
}
if tt.res.errFunc == nil && result.UserID != tt.res.result.UserID {
t.Errorf("got wrong result userid: expected: %v, actual: %v ", tt.res.result.UserID, result.UserID)
}
if tt.res.errFunc == nil && len(result.Roles) != len(tt.res.result.Roles) {
t.Errorf("got wrong result roles: expected: %v, actual: %v ", tt.res.result.Roles, result.Roles)
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestChangeIamMember(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *IamEventstore
ctx context.Context
member *iam_model.IamMember
}
type res struct {
result *iam_model.IamMember
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "add iam member, ok",
args: args{
es: GetMockManipulateIamWithMember(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
member: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID", Roles: []string{"ChangeRoles"}},
},
res: res{
result: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
},
},
{
name: "no userid",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
member: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, Roles: []string{"ChangeRoles"}},
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "no roles",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
member: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID"},
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "member not existing",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
member: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing not found",
args: args{
es: GetMockManipulateIamNotExisting(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
member: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID", Roles: []string{"ChangeRoles"}},
},
res: res{
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.ChangeIamMember(tt.args.ctx, tt.args.member)
if tt.res.errFunc == nil && result.AggregateID == "" {
t.Errorf("result has no id")
}
if tt.res.errFunc == nil && result.UserID != tt.res.result.UserID {
t.Errorf("got wrong result userid: expected: %v, actual: %v ", tt.res.result.UserID, result.UserID)
}
if tt.res.errFunc == nil && len(result.Roles) != len(tt.res.result.Roles) {
t.Errorf("got wrong result roles: expected: %v, actual: %v ", tt.res.result.Roles, result.Roles)
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestRemoveIamMember(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *IamEventstore
ctx context.Context
existing *model.Iam
member *iam_model.IamMember
}
type res struct {
result *iam_model.IamMember
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "remove iam member, ok",
args: args{
es: GetMockManipulateIamWithMember(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Iam{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1},
Members: []*model.IamMember{&model.IamMember{UserID: "UserID", Roles: []string{"Roles"}}},
},
member: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID"},
},
res: res{
result: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
},
},
{
name: "no userid",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Iam{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1},
Members: []*model.IamMember{&model.IamMember{UserID: "UserID", Roles: []string{"Roles"}}},
},
member: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, Roles: []string{"ChangeRoles"}},
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "member not existing",
args: args{
es: GetMockManipulateIam(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Iam{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1},
},
member: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing not found",
args: args{
es: GetMockManipulateIamNotExisting(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
member: &iam_model.IamMember{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID", Roles: []string{"ChangeRoles"}},
},
res: res{
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.es.RemoveIamMember(tt.args.ctx, tt.args.member)
if tt.res.errFunc == nil && err != nil {
t.Errorf("should not get err")
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@@ -0,0 +1,124 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
)
func IamByIDQuery(id string, latestSequence uint64) (*es_models.SearchQuery, error) {
if id == "" {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-0soe4", "id should be filled")
}
return IamQuery(latestSequence).
AggregateIDFilter(id), nil
}
func IamQuery(latestSequence uint64) *es_models.SearchQuery {
return es_models.NewSearchQuery().
AggregateTypeFilter(model.IamAggregate).
LatestSequenceFilter(latestSequence)
}
func IamAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, iam *model.Iam) (*es_models.Aggregate, error) {
if iam == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-lo04e", "existing iam should not be nil")
}
return aggCreator.NewAggregate(ctx, iam.AggregateID, model.IamAggregate, model.IamVersion, iam.Sequence)
}
func IamAggregateOverwriteContext(ctx context.Context, aggCreator *es_models.AggregateCreator, iam *model.Iam, resourceOwnerID string, userID string) (*es_models.Aggregate, error) {
if iam == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dis83", "existing iam should not be nil")
}
return aggCreator.NewAggregate(ctx, iam.AggregateID, model.IamAggregate, model.IamVersion, iam.Sequence, es_models.OverwriteResourceOwner(resourceOwnerID), es_models.OverwriteEditorUser(userID))
}
func IamSetupStartedAggregate(aggCreator *es_models.AggregateCreator, iam *model.Iam) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
agg, err := IamAggregate(ctx, aggCreator, iam)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.IamSetupStarted, nil)
}
}
func IamSetupDoneAggregate(aggCreator *es_models.AggregateCreator, iam *model.Iam) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
agg, err := IamAggregate(ctx, aggCreator, iam)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.IamSetupDone, nil)
}
}
func IamSetGlobalOrgAggregate(aggCreator *es_models.AggregateCreator, iam *model.Iam, globalOrg string) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if globalOrg == "" {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-8siwa", "globalOrg must be set")
}
agg, err := IamAggregate(ctx, aggCreator, iam)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.GlobalOrgSet, &model.Iam{GlobalOrgID: globalOrg})
}
}
func IamSetIamProjectAggregate(aggCreator *es_models.AggregateCreator, iam *model.Iam, projectID string) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if projectID == "" {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-sjuw3", "projectID must be set")
}
agg, err := IamAggregate(ctx, aggCreator, iam)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.IamProjectSet, &model.Iam{IamProjectID: projectID})
}
}
func IamMemberAddedAggregate(aggCreator *es_models.AggregateCreator, existing *model.Iam, member *model.IamMember) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if member == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-9sope", "member should not be nil")
}
agg, err := IamAggregate(ctx, aggCreator, existing)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.IamMemberAdded, member)
}
}
func IamMemberChangedAggregate(aggCreator *es_models.AggregateCreator, existing *model.Iam, member *model.IamMember) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if member == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-38skf", "member should not be nil")
}
agg, err := IamAggregate(ctx, aggCreator, existing)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.IamMemberChanged, member)
}
}
func IamMemberRemovedAggregate(aggCreator *es_models.AggregateCreator, existing *model.Iam, member *model.IamMember) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if member == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-90lsw", "member should not be nil")
}
agg, err := IamAggregate(ctx, aggCreator, existing)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.IamMemberRemoved, member)
}
}

View File

@@ -0,0 +1,510 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"testing"
)
func TestSetUpStartedAggregate(t *testing.T) {
type args struct {
ctx context.Context
iam *model.Iam
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "setupstarted aggregate ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
iam: &model.Iam{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.IamSetupStarted,
},
},
{
name: "iam nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
iam: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.IamSetupStarted,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := IamSetupStartedAggregate(tt.args.aggCreator, tt.args.iam)(tt.args.ctx)
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if tt.res.errFunc == nil && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestSetUpDoneAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *model.Iam
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "setup done aggregate ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Iam{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.IamSetupDone,
},
},
{
name: "existing iam nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.IamSetupDone,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := IamSetupDoneAggregate(tt.args.aggCreator, tt.args.existing)(tt.args.ctx)
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if tt.res.errFunc == nil && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestGlobalOrgAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *model.Iam
orgID string
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "global org set aggregate ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Iam{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
orgID: "orgID",
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.GlobalOrgSet,
},
},
{
name: "existing iam nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: nil,
orgID: "orgID",
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "global org empty",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Iam{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := IamSetGlobalOrgAggregate(tt.args.aggCreator, tt.args.existing, tt.args.orgID)(tt.args.ctx)
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if tt.res.errFunc == nil && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestIamProjectAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *model.Iam
projectID string
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "iam project id set aggregate ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Iam{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
projectID: "projectID",
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.IamProjectSet,
},
},
{
name: "existing iam nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: nil,
projectID: "projectID",
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "project id empty",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Iam{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := IamSetIamProjectAggregate(tt.args.aggCreator, tt.args.existing, tt.args.projectID)(tt.args.ctx)
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if tt.res.errFunc == nil && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestIamMemberAddedAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *model.Iam
new *model.IamMember
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "iammember added ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Iam{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
new: &model.IamMember{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}, UserID: "UserID", Roles: []string{"Roles"}},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.IamMemberAdded,
},
},
{
name: "existing iam nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.IamMemberAdded,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "member nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Iam{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
new: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.IamMemberAdded,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := IamMemberAddedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if tt.res.errFunc == nil && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if tt.res.errFunc == nil && agg.Events[0].Data == nil {
t.Errorf("should have data in event")
}
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestIamMemberChangedAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *model.Iam
new *model.IamMember
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "iammember changed ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Iam{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
new: &model.IamMember{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}, UserID: "UserID", Roles: []string{"Roles"}},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.IamMemberChanged,
},
},
{
name: "existing iam nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.IamMemberChanged,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "member nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Iam{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
new: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.IamMemberChanged,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := IamMemberChangedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if !tt.res.wantErr && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if !tt.res.wantErr && agg.Events[0].Data == nil {
t.Errorf("should have data in event")
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestIamMemberRemovedAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *model.Iam
new *model.IamMember
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "iammember removed ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Iam{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
new: &model.IamMember{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}, UserID: "UserID", Roles: []string{"Roles"}},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.IamMemberRemoved,
},
},
{
name: "existing iam nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.IamMemberRemoved,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "member nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Iam{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
new: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.IamMemberRemoved,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := IamMemberRemovedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if !tt.res.wantErr && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if !tt.res.wantErr && agg.Events[0].Data == nil {
t.Errorf("should have data in event")
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@@ -0,0 +1,86 @@
package model
import (
"encoding/json"
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/model"
)
const (
IamVersion = "v1"
)
type Iam struct {
es_models.ObjectRoot
SetUpStarted bool `json:"-"`
SetUpDone bool `json:"-"`
GlobalOrgID string `json:"globalOrgId,omitempty"`
IamProjectID string `json:"iamProjectId,omitempty"`
Members []*IamMember `json:"-"`
}
func IamFromModel(iam *model.Iam) *Iam {
members := IamMembersFromModel(iam.Members)
converted := &Iam{
ObjectRoot: iam.ObjectRoot,
SetUpStarted: iam.SetUpStarted,
SetUpDone: iam.SetUpDone,
GlobalOrgID: iam.GlobalOrgID,
IamProjectID: iam.IamProjectID,
Members: members,
}
return converted
}
func IamToModel(iam *Iam) *model.Iam {
members := IamMembersToModel(iam.Members)
converted := &model.Iam{
ObjectRoot: iam.ObjectRoot,
SetUpStarted: iam.SetUpStarted,
SetUpDone: iam.SetUpDone,
GlobalOrgID: iam.GlobalOrgID,
IamProjectID: iam.IamProjectID,
Members: members,
}
return converted
}
func (i *Iam) AppendEvents(events ...*es_models.Event) error {
for _, event := range events {
if err := i.AppendEvent(event); err != nil {
return err
}
}
return nil
}
func (i *Iam) AppendEvent(event *es_models.Event) (err error) {
i.ObjectRoot.AppendEvent(event)
switch event.Type {
case IamSetupStarted:
i.SetUpStarted = true
case IamSetupDone:
i.SetUpDone = true
case IamProjectSet,
GlobalOrgSet:
err = i.setData(event)
case IamMemberAdded:
err = i.appendAddMemberEvent(event)
case IamMemberChanged:
err = i.appendChangeMemberEvent(event)
case IamMemberRemoved:
err = i.appendRemoveMemberEvent(event)
}
return err
}
func (i *Iam) setData(event *es_models.Event) error {
i.ObjectRoot.AppendEvent(event)
if err := json.Unmarshal(event.Data, i); err != nil {
logging.Log("EVEN-9sie4").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(err, "MODEL-slwi3", "could not unmarshal event")
}
return nil
}

View File

@@ -0,0 +1,101 @@
package model
import (
"encoding/json"
"github.com/caos/logging"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/model"
)
type IamMember struct {
es_models.ObjectRoot
UserID string `json:"userId,omitempty"`
Roles []string `json:"roles,omitempty"`
}
func GetIamMember(members []*IamMember, id string) (int, *IamMember) {
for i, m := range members {
if m.UserID == id {
return i, m
}
}
return -1, nil
}
func IamMembersToModel(members []*IamMember) []*model.IamMember {
convertedMembers := make([]*model.IamMember, len(members))
for i, m := range members {
convertedMembers[i] = IamMemberToModel(m)
}
return convertedMembers
}
func IamMembersFromModel(members []*model.IamMember) []*IamMember {
convertedMembers := make([]*IamMember, len(members))
for i, m := range members {
convertedMembers[i] = IamMemberFromModel(m)
}
return convertedMembers
}
func IamMemberFromModel(member *model.IamMember) *IamMember {
return &IamMember{
ObjectRoot: member.ObjectRoot,
UserID: member.UserID,
Roles: member.Roles,
}
}
func IamMemberToModel(member *IamMember) *model.IamMember {
return &model.IamMember{
ObjectRoot: member.ObjectRoot,
UserID: member.UserID,
Roles: member.Roles,
}
}
func (iam *Iam) appendAddMemberEvent(event *es_models.Event) error {
member := &IamMember{}
err := member.setData(event)
if err != nil {
return err
}
member.ObjectRoot.CreationDate = event.CreationDate
iam.Members = append(iam.Members, member)
return nil
}
func (iam *Iam) appendChangeMemberEvent(event *es_models.Event) error {
member := &IamMember{}
err := member.setData(event)
if err != nil {
return err
}
if i, m := GetIamMember(iam.Members, member.UserID); m != nil {
iam.Members[i] = member
}
return nil
}
func (iam *Iam) appendRemoveMemberEvent(event *es_models.Event) error {
member := &IamMember{}
err := member.setData(event)
if err != nil {
return err
}
if i, m := GetIamMember(iam.Members, member.UserID); m != nil {
iam.Members[i] = iam.Members[len(iam.Members)-1]
iam.Members[len(iam.Members)-1] = nil
iam.Members = iam.Members[:len(iam.Members)-1]
}
return nil
}
func (m *IamMember) setData(event *es_models.Event) error {
m.ObjectRoot.AppendEvent(event)
if err := json.Unmarshal(event.Data, m); err != nil {
logging.Log("EVEN-e4dkp").WithError(err).Error("could not unmarshal event data")
return err
}
return nil
}

View File

@@ -0,0 +1,118 @@
package model
import (
"encoding/json"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"testing"
)
func TestAppendAddMemberEvent(t *testing.T) {
type args struct {
iam *Iam
member *IamMember
event *es_models.Event
}
tests := []struct {
name string
args args
result *Iam
}{
{
name: "append add member event",
args: args{
iam: &Iam{},
member: &IamMember{UserID: "UserID", Roles: []string{"Role"}},
event: &es_models.Event{},
},
result: &Iam{Members: []*IamMember{&IamMember{UserID: "UserID", Roles: []string{"Role"}}}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.member != nil {
data, _ := json.Marshal(tt.args.member)
tt.args.event.Data = data
}
tt.args.iam.appendAddMemberEvent(tt.args.event)
if len(tt.args.iam.Members) != 1 {
t.Errorf("got wrong result should have one member actual: %v ", len(tt.args.iam.Members))
}
if tt.args.iam.Members[0] == tt.result.Members[0] {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.Members[0], tt.args.iam.Members[0])
}
})
}
}
func TestAppendChangeMemberEvent(t *testing.T) {
type args struct {
iam *Iam
member *IamMember
event *es_models.Event
}
tests := []struct {
name string
args args
result *Iam
}{
{
name: "append change member event",
args: args{
iam: &Iam{Members: []*IamMember{&IamMember{UserID: "UserID", Roles: []string{"Role"}}}},
member: &IamMember{UserID: "UserID", Roles: []string{"ChangedRole"}},
event: &es_models.Event{},
},
result: &Iam{Members: []*IamMember{&IamMember{UserID: "UserID", Roles: []string{"ChangedRole"}}}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.member != nil {
data, _ := json.Marshal(tt.args.member)
tt.args.event.Data = data
}
tt.args.iam.appendChangeMemberEvent(tt.args.event)
if len(tt.args.iam.Members) != 1 {
t.Errorf("got wrong result should have one member actual: %v ", len(tt.args.iam.Members))
}
if tt.args.iam.Members[0] == tt.result.Members[0] {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.Members[0], tt.args.iam.Members[0])
}
})
}
}
func TestAppendRemoveMemberEvent(t *testing.T) {
type args struct {
iam *Iam
member *IamMember
event *es_models.Event
}
tests := []struct {
name string
args args
result *Iam
}{
{
name: "append remove member event",
args: args{
iam: &Iam{Members: []*IamMember{&IamMember{UserID: "UserID", Roles: []string{"Role"}}}},
member: &IamMember{UserID: "UserID"},
event: &es_models.Event{},
},
result: &Iam{Members: []*IamMember{}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.member != nil {
data, _ := json.Marshal(tt.args.member)
tt.args.event.Data = data
}
tt.args.iam.appendRemoveMemberEvent(tt.args.event)
if len(tt.args.iam.Members) != 0 {
t.Errorf("got wrong result should have no member actual: %v ", len(tt.args.iam.Members))
}
})
}
}

View File

@@ -0,0 +1,74 @@
package model
import (
"encoding/json"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"testing"
)
func mockIamData(iam *Iam) []byte {
data, _ := json.Marshal(iam)
return data
}
func TestProjectRoleAppendEvent(t *testing.T) {
type args struct {
event *es_models.Event
iam *Iam
}
tests := []struct {
name string
args args
result *Iam
}{
{
name: "append set up start event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: IamSetupStarted, ResourceOwner: "OrgID"},
iam: &Iam{},
},
result: &Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SetUpStarted: true},
},
{
name: "append set up done event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: IamSetupDone, ResourceOwner: "OrgID"},
iam: &Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SetUpStarted: true},
},
result: &Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SetUpStarted: true, SetUpDone: true},
},
{
name: "append globalorg event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: GlobalOrgSet, ResourceOwner: "OrgID", Data: mockIamData(&Iam{GlobalOrgID: "GlobalOrg"})},
iam: &Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SetUpStarted: true},
},
result: &Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SetUpStarted: true, GlobalOrgID: "GlobalOrg"},
},
{
name: "append iamproject event",
args: args{
event: &es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: IamProjectSet, ResourceOwner: "OrgID", Data: mockIamData(&Iam{IamProjectID: "IamProject"})},
iam: &Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SetUpStarted: true},
},
result: &Iam{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SetUpStarted: true, IamProjectID: "IamProject"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.iam.AppendEvent(tt.args.event)
if tt.args.iam.AggregateID != tt.result.AggregateID {
t.Errorf("got wrong result AggregateID: expected: %v, actual: %v ", tt.result.AggregateID, tt.args.iam.AggregateID)
}
if tt.args.iam.SetUpDone != tt.result.SetUpDone {
t.Errorf("got wrong result SetUpDone: expected: %v, actual: %v ", tt.result.SetUpDone, tt.args.iam.SetUpDone)
}
if tt.args.iam.GlobalOrgID != tt.result.GlobalOrgID {
t.Errorf("got wrong result GlobalOrgID: expected: %v, actual: %v ", tt.result.GlobalOrgID, tt.args.iam.GlobalOrgID)
}
if tt.args.iam.IamProjectID != tt.result.IamProjectID {
t.Errorf("got wrong result IamProjectID: expected: %v, actual: %v ", tt.result.IamProjectID, tt.args.iam.IamProjectID)
}
})
}
}

View File

@@ -0,0 +1,15 @@
package model
import "github.com/caos/zitadel/internal/eventstore/models"
const (
IamAggregate models.AggregateType = "iam"
IamSetupStarted models.EventType = "iam.setup.started"
IamSetupDone models.EventType = "iam.setup.done"
GlobalOrgSet models.EventType = "iam.global.org.set"
IamProjectSet models.EventType = "iam.project.iam.set"
IamMemberAdded models.EventType = "iam.member.added"
IamMemberChanged models.EventType = "iam.member.changed"
IamMemberRemoved models.EventType = "iam.member.removed"
)