From 2758bf30b1f82c752ab28045ae827e77a4be09bb Mon Sep 17 00:00:00 2001 From: Fabi <38692350+fgerschwiler@users.noreply.github.com> Date: Thu, 28 May 2020 06:53:12 +0200 Subject: [PATCH] fix: Project checks (#141) * project validations * unique project on resourceowner --- .../internal/repository/sql/filter.go | 4 +- internal/eventstore/models/field.go | 1 + internal/eventstore/models/search_query.go | 4 + .../repository/eventsourcing/eventstore.go | 58 +++++++- .../repository/eventsourcing/model/project.go | 9 ++ .../repository/eventsourcing/project.go | 128 ++++++++++++++++-- .../repository/eventsourcing/model/types.go | 1 + 7 files changed, 191 insertions(+), 14 deletions(-) diff --git a/internal/eventstore/internal/repository/sql/filter.go b/internal/eventstore/internal/repository/sql/filter.go index 033cf6ed2d..71321d2dc1 100644 --- a/internal/eventstore/internal/repository/sql/filter.go +++ b/internal/eventstore/internal/repository/sql/filter.go @@ -126,7 +126,7 @@ func prepareWhere(searchQuery *es_models.SearchQuery) (clause string, values []i for i, filter := range searchQuery.Filters { value := filter.GetValue() switch value.(type) { - case []bool, []float64, []int64, []string, []models.AggregateType, *[]bool, *[]float64, *[]int64, *[]string, *[]models.AggregateType: + case []bool, []float64, []int64, []string, []models.AggregateType, []models.EventType, *[]bool, *[]float64, *[]int64, *[]string, *[]models.AggregateType, *[]models.EventType: value = pq.Array(value) } @@ -165,6 +165,8 @@ func getField(field es_models.Field) string { return "editor_service" case es_models.Field_EditorUser: return "editor_user" + case es_models.Field_EventType: + return "event_type" } return "" } diff --git a/internal/eventstore/models/field.go b/internal/eventstore/models/field.go index 7878e9e966..72ce5831ff 100644 --- a/internal/eventstore/models/field.go +++ b/internal/eventstore/models/field.go @@ -9,4 +9,5 @@ const ( Field_ResourceOwner Field_EditorService Field_EditorUser + Field_EventType ) diff --git a/internal/eventstore/models/search_query.go b/internal/eventstore/models/search_query.go index 8a81d4bf15..2563f045f1 100644 --- a/internal/eventstore/models/search_query.go +++ b/internal/eventstore/models/search_query.go @@ -43,6 +43,10 @@ func (q *SearchQuery) AggregateTypeFilter(types ...AggregateType) *SearchQuery { return q.setFilter(NewFilter(Field_AggregateType, types, Operation_In)) } +func (q *SearchQuery) EventTypesFilter(types ...EventType) *SearchQuery { + return q.setFilter(NewFilter(Field_EventType, types, Operation_In)) +} + func (q *SearchQuery) LatestSequenceFilter(sequence uint64) *SearchQuery { sortOrder := Operation_Greater if q.Desc { diff --git a/internal/project/repository/eventsourcing/eventstore.go b/internal/project/repository/eventsourcing/eventstore.go index 91fdf1e764..9aef8e1acc 100644 --- a/internal/project/repository/eventsourcing/eventstore.go +++ b/internal/project/repository/eventsourcing/eventstore.go @@ -113,8 +113,10 @@ func (es *ProjectEventstore) DeactivateProject(ctx context.Context, id string) ( repoExisting := model.ProjectFromModel(existing) aggregate := ProjectDeactivateAggregate(es.AggregateCreator(), repoExisting) - es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate) - + err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate) + if err != nil { + return nil, err + } es.projectCache.cacheProject(repoExisting) return model.ProjectToModel(repoExisting), nil } @@ -130,8 +132,10 @@ func (es *ProjectEventstore) ReactivateProject(ctx context.Context, id string) ( repoExisting := model.ProjectFromModel(existing) aggregate := ProjectReactivateAggregate(es.AggregateCreator(), repoExisting) - es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate) - + err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate) + if err != nil { + return nil, err + } es.projectCache.cacheProject(repoExisting) return model.ProjectToModel(repoExisting), nil } @@ -194,6 +198,9 @@ func (es *ProjectEventstore) ChangeProjectMember(ctx context.Context, member *pr projectAggregate := ProjectMemberChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoMember) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate) + if err != nil { + return nil, err + } es.projectCache.cacheProject(repoProject) if _, m := model.GetProjectMember(repoProject.Members, member.UserID); m != nil { @@ -218,6 +225,9 @@ func (es *ProjectEventstore) RemoveProjectMember(ctx context.Context, member *pr projectAggregate := ProjectMemberRemovedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoMember) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate) + if err != nil { + return err + } es.projectCache.cacheProject(repoProject) return err } @@ -347,6 +357,9 @@ func (es *ProjectEventstore) AddApplication(ctx context.Context, app *proj_model addAggregate := ApplicationAddedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoApp) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, addAggregate) + if err != nil { + return nil, err + } es.projectCache.cacheProject(repoProject) if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil { converted := model.AppToModel(a) @@ -372,6 +385,9 @@ func (es *ProjectEventstore) ChangeApplication(ctx context.Context, app *proj_mo projectAggregate := ApplicationChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoApp) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate) + if err != nil { + return nil, err + } es.projectCache.cacheProject(repoProject) if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil { return model.AppToModel(a), nil @@ -418,6 +434,9 @@ func (es *ProjectEventstore) DeactivateApplication(ctx context.Context, projectI projectAggregate := ApplicationDeactivatedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoApp) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate) + if err != nil { + return nil, err + } es.projectCache.cacheProject(repoProject) if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil { return model.AppToModel(a), nil @@ -442,6 +461,9 @@ func (es *ProjectEventstore) ReactivateApplication(ctx context.Context, projectI projectAggregate := ApplicationReactivatedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoApp) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate) + if err != nil { + return nil, err + } es.projectCache.cacheProject(repoProject) if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil { return model.AppToModel(a), nil @@ -469,6 +491,9 @@ func (es *ProjectEventstore) ChangeOIDCConfig(ctx context.Context, config *proj_ projectAggregate := OIDCConfigChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoConfig) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate) + if err != nil { + return nil, err + } es.projectCache.cacheProject(repoProject) if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil { return model.OIDCConfigToModel(a.OIDCConfig), nil @@ -500,6 +525,9 @@ func (es *ProjectEventstore) ChangeOIDCConfigSecret(ctx context.Context, project projectAggregate := OIDCConfigSecretChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, appID, crypto) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate) + if err != nil { + return nil, err + } es.projectCache.cacheProject(repoProject) if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil { @@ -550,7 +578,9 @@ func (es *ProjectEventstore) AddProjectGrant(ctx context.Context, grant *proj_mo addAggregate := ProjectGrantAddedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoGrant) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, addAggregate) - + if err != nil { + return nil, err + } if _, g := model.GetProjectGrant(repoProject.Grants, grant.GrantID); g != nil { return model.GrantToModel(g), nil } @@ -576,6 +606,9 @@ func (es *ProjectEventstore) ChangeProjectGrant(ctx context.Context, grant *proj projectAggregate := ProjectGrantChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoGrant) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate) + if err != nil { + return nil, err + } es.projectCache.cacheProject(repoProject) if _, g := model.GetProjectGrant(repoProject.Grants, grant.GrantID); g != nil { @@ -623,6 +656,9 @@ func (es *ProjectEventstore) DeactivateProjectGrant(ctx context.Context, project projectAggregate := ProjectGrantDeactivatedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoGrant) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate) + if err != nil { + return nil, err + } es.projectCache.cacheProject(repoProject) if _, g := model.GetProjectGrant(repoProject.Grants, grant.GrantID); g != nil { return model.GrantToModel(g), nil @@ -647,6 +683,9 @@ func (es *ProjectEventstore) ReactivateProjectGrant(ctx context.Context, project projectAggregate := ProjectGrantReactivatedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoGrant) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate) + if err != nil { + return nil, err + } es.projectCache.cacheProject(repoProject) if _, g := model.GetProjectGrant(repoProject.Grants, grant.GrantID); g != nil { @@ -687,6 +726,9 @@ func (es *ProjectEventstore) AddProjectGrantMember(ctx context.Context, member * addAggregate := ProjectGrantMemberAddedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoMember) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, addAggregate) + if err != nil { + return nil, err + } es.projectCache.cacheProject(repoProject) if _, g := model.GetProjectGrant(repoProject.Grants, member.GrantID); g != nil { if _, m := model.GetProjectGrantMember(g.Members, member.UserID); m != nil { @@ -712,6 +754,9 @@ func (es *ProjectEventstore) ChangeProjectGrantMember(ctx context.Context, membe projectAggregate := ProjectGrantMemberChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoMember) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate) + if err != nil { + return nil, err + } es.projectCache.cacheProject(repoProject) if _, g := model.GetProjectGrant(repoProject.Grants, member.GrantID); g != nil { if _, m := model.GetProjectGrantMember(g.Members, member.UserID); m != nil { @@ -737,6 +782,9 @@ func (es *ProjectEventstore) RemoveProjectGrantMember(ctx context.Context, membe projectAggregate := ProjectGrantMemberRemovedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoMember) err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate) + if err != nil { + return err + } es.projectCache.cacheProject(repoProject) return err } diff --git a/internal/project/repository/eventsourcing/model/project.go b/internal/project/repository/eventsourcing/model/project.go index 56b60198fe..e503fc49fb 100644 --- a/internal/project/repository/eventsourcing/model/project.go +++ b/internal/project/repository/eventsourcing/model/project.go @@ -21,6 +21,15 @@ type Project struct { Grants []*ProjectGrant `json:"-"` } +func GetProject(projects []*Project, id string) (int, *Project) { + for i, p := range projects { + if p.AggregateID == id { + return i, p + } + } + return -1, nil +} + func (p *Project) Changes(changed *Project) map[string]interface{} { changes := make(map[string]interface{}, 1) if changed.Name != "" && p.Name != changed.Name { diff --git a/internal/project/repository/eventsourcing/project.go b/internal/project/repository/eventsourcing/project.go index aecacc264b..65a31bfd2a 100644 --- a/internal/project/repository/eventsourcing/project.go +++ b/internal/project/repository/eventsourcing/project.go @@ -6,7 +6,9 @@ import ( "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models" + org_model "github.com/caos/zitadel/internal/org/model" "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" + usr_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" ) func ProjectByIDQuery(id string, latestSequence uint64) (*es_models.SearchQuery, error) { @@ -40,8 +42,12 @@ func ProjectCreateAggregate(aggCreator *es_models.AggregateCreator, project *mod if err != nil { return nil, err } + validationQuery := es_models.NewSearchQuery(). + AggregateTypeFilter(model.ProjectAggregate). + EventTypesFilter(model.ProjectAdded, model.ProjectChanged, model.ProjectRemoved) - return agg.AppendEvent(model.ProjectAdded, project) + validation := addProjectValidation(project.Name) + return agg.SetPrecondition(validationQuery, validation).AppendEvent(model.ProjectAdded, project) } } @@ -55,6 +61,17 @@ func ProjectUpdateAggregate(aggCreator *es_models.AggregateCreator, existing *mo return nil, err } changes := existing.Changes(new) + if len(changes) == 0 { + return nil, errors.ThrowPreconditionFailed(nil, "EVENT-9soPE", "no changes found") + } + if existing.Name != new.Name { + validationQuery := es_models.NewSearchQuery(). + AggregateTypeFilter(model.ProjectAggregate). + EventTypesFilter(model.ProjectAdded, model.ProjectChanged, model.ProjectRemoved) + + validation := addProjectValidation(new.Name) + agg.SetPrecondition(validationQuery, validation) + } return agg.AppendEvent(model.ProjectChanged, changes) } } @@ -86,7 +103,12 @@ func ProjectMemberAddedAggregate(aggCreator *es_models.AggregateCreator, existin if err != nil { return nil, err } - return agg.AppendEvent(model.ProjectMemberAdded, member) + validationQuery := es_models.NewSearchQuery(). + AggregateTypeFilter(usr_model.UserAggregate). + AggregateIDFilter(member.UserID) + + validation := addProjectMemberValidation() + return agg.SetPrecondition(validationQuery, validation).AppendEvent(model.ProjectMemberAdded, member) } } @@ -287,7 +309,12 @@ func ProjectGrantAddedAggregate(aggCreator *es_models.AggregateCreator, existing if err != nil { return nil, err } - agg.AppendEvent(model.ProjectGrantAdded, grant) + validationQuery := es_models.NewSearchQuery(). + AggregateTypeFilter(org_model.OrgAggregate). + AggregateIDFilter(grant.GrantedOrgID) + + validation := addProjectGrantValidation() + agg.SetPrecondition(validationQuery, validation).AppendEvent(model.ProjectGrantAdded, grant) return agg, nil } } @@ -352,9 +379,7 @@ func ProjectGrantReactivatedAggregate(aggCreator *es_models.AggregateCreator, ex if err != nil { return nil, err } - agg.AppendEvent(model.ProjectGrantReactivated, &model.ProjectGrantID{GrantID: grant.GrantID}) - - return agg, nil + return agg.AppendEvent(model.ProjectGrantReactivated, &model.ProjectGrantID{GrantID: grant.GrantID}) } } @@ -367,8 +392,12 @@ func ProjectGrantMemberAddedAggregate(aggCreator *es_models.AggregateCreator, ex if err != nil { return nil, err } - agg.AppendEvent(model.ProjectGrantMemberAdded, member) - return agg, nil + validationQuery := es_models.NewSearchQuery(). + AggregateTypeFilter(usr_model.UserAggregate). + AggregateIDFilter(member.UserID) + + validation := addProjectGrantMemberValidation() + return agg.SetPrecondition(validationQuery, validation).AppendEvent(model.ProjectGrantMemberAdded, member) } } @@ -403,3 +432,86 @@ func ProjectGrantMemberRemovedAggregate(aggCreator *es_models.AggregateCreator, return agg.AppendEvent(model.ProjectGrantMemberRemoved, member) } } + +func addProjectValidation(projectName string) func(...*es_models.Event) error { + return func(events ...*es_models.Event) error { + projects := make([]*model.Project, 0) + for _, event := range events { + switch event.Type { + case model.ProjectAdded: + project := &model.Project{ObjectRoot: es_models.ObjectRoot{AggregateID: event.AggregateID}} + project.AppendAddProjectEvent(event) + projects = append(projects, project) + case model.ProjectChanged: + _, project := model.GetProject(projects, event.AggregateID) + project.AppendAddProjectEvent(event) + case model.ProjectRoleRemoved: + for i, project := range projects { + if project.AggregateID == event.AggregateID { + projects[i] = projects[len(projects)-1] + projects[len(projects)-1] = nil + projects = projects[:len(projects)-1] + } + } + } + } + for _, p := range projects { + if p.Name == projectName { + return errors.ThrowPreconditionFailed(nil, "EVENT-s9oPw", "conditions not met") + } + } + return nil + } +} + +func addProjectMemberValidation() func(...*es_models.Event) error { + return func(events ...*es_models.Event) error { + return checkExistsUser(events...) + } +} + +func addProjectGrantValidation() func(...*es_models.Event) error { + return func(events ...*es_models.Event) error { + existsOrg := false + for _, event := range events { + switch event.AggregateType { + case org_model.OrgAggregate: + switch event.Type { + case org_model.OrgAdded: + existsOrg = true + case org_model.OrgRemoved: + existsOrg = false + } + } + } + if existsOrg { + return nil + } + return errors.ThrowPreconditionFailed(nil, "EVENT-3OfIm", "conditions not met") + } +} + +func addProjectGrantMemberValidation() func(...*es_models.Event) error { + return func(events ...*es_models.Event) error { + return checkExistsUser(events...) + } +} + +func checkExistsUser(events ...*es_models.Event) error { + existsUser := false + for _, event := range events { + switch event.AggregateType { + case usr_model.UserAggregate: + switch event.Type { + case usr_model.UserAdded, usr_model.UserRegistered: + existsUser = true + case usr_model.UserRemoved: + existsUser = false + } + } + } + if existsUser { + return nil + } + return errors.ThrowPreconditionFailed(nil, "EVENT-3OfIm", "conditions not met") +} diff --git a/internal/user/repository/eventsourcing/model/types.go b/internal/user/repository/eventsourcing/model/types.go index f2cbe799b0..9838011625 100644 --- a/internal/user/repository/eventsourcing/model/types.go +++ b/internal/user/repository/eventsourcing/model/types.go @@ -11,6 +11,7 @@ const ( UserRegistered models.EventType = "user.selfregistered" InitializedUserCodeAdded models.EventType = "user.initialization.code.added" InitializedUserCodeSent models.EventType = "user.initialization.code.sent" + UserRemoved models.EventType = "user.removed" UserUserNameReserved models.EventType = "user.username.reserved" UserUserNameReleased models.EventType = "user.username.released"