feat: delete (#243)

* feat: project role remove

* feat: search queries

* feat: search queries

* feat: cascade remove/change project role

* fix: comment in project grant

* fix: remove projecr grant

* fix: only search usergrants of my org

* fix: delete usergrants

* fix: delete usergrants

* fix: check if role exists on project grant

* feat: bulk add project role

* fix: tests

* fix: update user grants on project update

* fix: return roles

* feat: add resourceowner name on project grants

* fix: migration number

* fix: tests

* fix: generate protos

* fix: some unnecessary code
This commit is contained in:
Fabi
2020-06-19 15:32:03 +02:00
committed by GitHub
parent 8f49f2c2d8
commit 710652ef24
55 changed files with 4404 additions and 2668 deletions

View File

@@ -30,3 +30,26 @@ func (u *UserGrant) IsActive() bool {
func (u *UserGrant) IsInactive() bool {
return u.State == USERGRANTSTATE_INACTIVE
}
func (u *UserGrant) RemoveRoleKeyIfExisting(key string) bool {
for i, role := range u.RoleKeys {
if role == key {
u.RoleKeys[i] = u.RoleKeys[len(u.RoleKeys)-1]
u.RoleKeys[len(u.RoleKeys)-1] = ""
u.RoleKeys = u.RoleKeys[:len(u.RoleKeys)-1]
return true
}
}
return false
}
func (u *UserGrant) RemoveRoleKeysIfExisting(keys []string) bool {
exists := false
for _, key := range keys {
keyExists := u.RemoveRoleKeyIfExisting(key)
if keyExists {
exists = true
}
}
return exists
}

View File

@@ -44,12 +44,13 @@ const (
USERGRANTSEARCHKEY_STATE
USERGRANTSEARCHKEY_GRANT_ID
USERGRANTSEARCHKEY_ORG_NAME
USERGRANTSEARCHKEY_ROLE_KEY
)
type UserGrantSearchQuery struct {
Key UserGrantSearchKey
Method model.SearchMethod
Value string
Value interface{}
}
type UserGrantSearchResponse struct {

View File

@@ -6,6 +6,7 @@ import (
caos_errs "github.com/caos/zitadel/internal/errors"
es_int "github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
"github.com/caos/zitadel/internal/id"
grant_model "github.com/caos/zitadel/internal/usergrant/model"
@@ -54,18 +55,7 @@ func (es *UserGrantEventStore) UserGrantByID(ctx context.Context, id string) (*g
}
func (es *UserGrantEventStore) AddUserGrant(ctx context.Context, grant *grant_model.UserGrant) (*grant_model.UserGrant, error) {
if grant == nil || !grant.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-sdiw3", "User grant invalid")
}
id, err := es.idGenerator.Next()
if err != nil {
return nil, err
}
grant.AggregateID = id
repoGrant := model.UserGrantFromModel(grant)
addAggregates, err := UserGrantAddedAggregate(ctx, es.Eventstore.AggregateCreator(), repoGrant)
repoGrant, addAggregates, err := es.PrepareAddUserGrant(ctx, grant)
if err != nil {
return nil, err
}
@@ -76,19 +66,65 @@ func (es *UserGrantEventStore) AddUserGrant(ctx context.Context, grant *grant_mo
return model.UserGrantToModel(repoGrant), nil
}
func (es *UserGrantEventStore) ChangeUserGrant(ctx context.Context, grant *grant_model.UserGrant) (*grant_model.UserGrant, error) {
func (es *UserGrantEventStore) AddUserGrants(ctx context.Context, grants ...*grant_model.UserGrant) error {
aggregates := make([]*es_models.Aggregate, 0)
for _, grant := range grants {
_, addAggregates, err := es.PrepareAddUserGrant(ctx, grant)
if err != nil {
return err
}
for _, agg := range addAggregates {
aggregates = append(aggregates, agg)
}
}
return es_sdk.PushAggregates(ctx, es.PushAggregates, nil, aggregates...)
}
func (es *UserGrantEventStore) PrepareAddUserGrant(ctx context.Context, grant *grant_model.UserGrant) (*model.UserGrant, []*es_models.Aggregate, error) {
if grant == nil || !grant.IsValid() {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-sdiw3", "User grant invalid")
}
id, err := es.idGenerator.Next()
if err != nil {
return nil, nil, err
}
grant.AggregateID = id
repoGrant := model.UserGrantFromModel(grant)
addAggregates, err := UserGrantAddedAggregate(ctx, es.Eventstore.AggregateCreator(), repoGrant)
if err != nil {
return nil, nil, err
}
return repoGrant, addAggregates, nil
}
func (es *UserGrantEventStore) PrepareChangeUserGrant(ctx context.Context, grant *grant_model.UserGrant, cascade bool) (*model.UserGrant, *es_models.Aggregate, error) {
if grant == nil {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo0s9", "invalid grant")
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo0s9", "invalid grant")
}
existing, err := es.UserGrantByID(ctx, grant.AggregateID)
if err != nil {
return nil, err
return nil, nil, err
}
repoExisting := model.UserGrantFromModel(existing)
repoGrant := model.UserGrantFromModel(grant)
projectAggregate := UserGrantChangedAggregate(es.Eventstore.AggregateCreator(), repoExisting, repoGrant)
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, projectAggregate)
aggFunc := UserGrantChangedAggregate(es.Eventstore.AggregateCreator(), repoExisting, repoGrant, cascade)
projectAggregate, err := aggFunc(ctx)
if err != nil {
return nil, nil, err
}
return repoExisting, projectAggregate, err
}
func (es *UserGrantEventStore) ChangeUserGrant(ctx context.Context, grant *grant_model.UserGrant) (*grant_model.UserGrant, error) {
repoExisting, agg, err := es.PrepareChangeUserGrant(ctx, grant, false)
if err != nil {
return nil, err
}
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoExisting.AppendEvents, agg)
if err != nil {
return nil, err
}
@@ -96,23 +132,57 @@ func (es *UserGrantEventStore) ChangeUserGrant(ctx context.Context, grant *grant
return model.UserGrantToModel(repoExisting), nil
}
func (es *UserGrantEventStore) ChangeUserGrants(ctx context.Context, grants ...*grant_model.UserGrant) error {
aggregates := make([]*es_models.Aggregate, len(grants))
for i, grant := range grants {
_, agg, err := es.PrepareChangeUserGrant(ctx, grant, false)
if err != nil {
return err
}
aggregates[i] = agg
}
return es_sdk.PushAggregates(ctx, es.PushAggregates, nil, aggregates...)
}
func (es *UserGrantEventStore) RemoveUserGrant(ctx context.Context, grantID string) error {
existing, err := es.UserGrantByID(ctx, grantID)
existing, projectAggregates, err := es.PrepareRemoveUserGrant(ctx, grantID, false)
if err != nil {
return err
}
err = es_sdk.PushAggregates(ctx, es.PushAggregates, existing.AppendEvents, projectAggregates...)
if err != nil {
return err
}
es.userGrantCache.cacheUserGrant(existing)
return nil
}
func (es *UserGrantEventStore) RemoveUserGrants(ctx context.Context, grantIDs ...string) error {
aggregates := make([]*es_models.Aggregate, 0)
for _, grantID := range grantIDs {
_, aggs, err := es.PrepareRemoveUserGrant(ctx, grantID, false)
if err != nil {
return err
}
for _, agg := range aggs {
aggregates = append(aggregates, agg)
}
}
return es_sdk.PushAggregates(ctx, es.PushAggregates, nil, aggregates...)
}
func (es *UserGrantEventStore) PrepareRemoveUserGrant(ctx context.Context, grantID string, cascade bool) (*model.UserGrant, []*es_models.Aggregate, error) {
existing, err := es.UserGrantByID(ctx, grantID)
if err != nil {
return nil, nil, err
}
repoExisting := model.UserGrantFromModel(existing)
repoGrant := &model.UserGrant{ObjectRoot: models.ObjectRoot{AggregateID: grantID}}
projectAggregates, err := UserGrantRemovedAggregate(ctx, es.Eventstore.AggregateCreator(), repoExisting, repoGrant)
projectAggregates, err := UserGrantRemovedAggregate(ctx, es.Eventstore.AggregateCreator(), repoExisting, repoGrant, cascade)
if err != nil {
return err
return nil, nil, err
}
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoExisting.AppendEvents, projectAggregates...)
if err != nil {
return err
}
es.userGrantCache.cacheUserGrant(repoExisting)
return nil
return repoExisting, projectAggregates, nil
}
func (es *UserGrantEventStore) DeactivateUserGrant(ctx context.Context, grantID string) (*grant_model.UserGrant, error) {

View File

@@ -13,4 +13,7 @@ const (
UserGrantReactivated models.EventType = "user.grant.reactivated"
UserGrantReserved models.EventType = "user.grant.reserved"
UserGrantReleased models.EventType = "user.grant.released"
UserGrantCascadeRemoved models.EventType = "user.grant.cascade.removed"
UserGrantCascadeChanged models.EventType = "user.grant.cascade.changed"
)

View File

@@ -68,13 +68,15 @@ func (g *UserGrant) AppendEvent(event *es_models.Event) error {
g.ObjectRoot.AppendEvent(event)
switch event.Type {
case UserGrantAdded,
UserGrantChanged:
UserGrantChanged,
UserGrantCascadeChanged:
return g.setData(event)
case UserGrantDeactivated:
g.appendGrantStateEvent(model.USERGRANTSTATE_INACTIVE)
case UserGrantReactivated:
g.appendGrantStateEvent(model.USERGRANTSTATE_ACTIVE)
case UserGrantRemoved:
case UserGrantRemoved,
UserGrantCascadeRemoved:
g.appendGrantStateEvent(model.USERGRANTSTATE_REMOVED)
}
return nil

View File

@@ -95,7 +95,7 @@ func releasedUniqueUserGrantAggregate(ctx context.Context, aggCreator *es_models
return aggregate.SetPrecondition(UserGrantUniqueQuery(grant.ResourceOwner, grant.ProjectID, grant.UserID), isEventValidation(aggregate, model.UserGrantReleased)), nil
}
func UserGrantChangedAggregate(aggCreator *es_models.AggregateCreator, existing *model.UserGrant, grant *model.UserGrant) func(ctx context.Context) (*es_models.Aggregate, error) {
func UserGrantChangedAggregate(aggCreator *es_models.AggregateCreator, existing *model.UserGrant, grant *model.UserGrant, cascade bool) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if grant == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-osl8x", "grant should not be nil")
@@ -105,7 +105,10 @@ func UserGrantChangedAggregate(aggCreator *es_models.AggregateCreator, existing
return nil, err
}
changes := existing.Changes(grant)
return agg.AppendEvent(model.UserGrantChanged, changes)
if !cascade {
return agg.AppendEvent(model.UserGrantChanged, changes)
}
return agg.AppendEvent(model.UserGrantCascadeChanged, changes)
}
}
@@ -135,7 +138,7 @@ func UserGrantReactivatedAggregate(aggCreator *es_models.AggregateCreator, exist
}
}
func UserGrantRemovedAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *model.UserGrant, grant *model.UserGrant) ([]*es_models.Aggregate, error) {
func UserGrantRemovedAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *model.UserGrant, grant *model.UserGrant, cascade bool) ([]*es_models.Aggregate, error) {
if grant == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-lo21s", "grant should not be nil")
}
@@ -143,7 +146,11 @@ func UserGrantRemovedAggregate(ctx context.Context, aggCreator *es_models.Aggreg
if err != nil {
return nil, err
}
agg, err = agg.AppendEvent(model.UserGrantRemoved, nil)
eventType := model.UserGrantRemoved
if cascade {
eventType = model.UserGrantCascadeRemoved
}
agg, err = agg.AppendEvent(eventType, nil)
if err != nil {
return nil, err
}
@@ -196,37 +203,43 @@ func addUserGrantValidation(resourceOwner string, grant *model.UserGrant) func(.
project.AppendEvent(event)
}
}
if existsOrg && existsUser && checkProjectConditions(resourceOwner, grant, project) {
return nil
if !existsOrg {
return errors.ThrowPreconditionFailed(nil, "EVENT-3OfIm", "org doesn't exist")
}
return errors.ThrowPreconditionFailed(nil, "EVENT-3OfIm", "conditions not met")
if !existsUser {
return errors.ThrowPreconditionFailed(nil, "EVENT-Sl8uS", "user doesn't exist")
}
if err := checkProjectConditions(resourceOwner, grant, project); err != nil {
return err
}
return nil
}
}
func checkProjectConditions(resourceOwner string, grant *model.UserGrant, project *proj_es_model.Project) bool {
func checkProjectConditions(resourceOwner string, grant *model.UserGrant, project *proj_es_model.Project) error {
if project.State == int32(proj_model.PROJECTSTATE_REMOVED) {
return false
return errors.ThrowPreconditionFailed(nil, "EVENT-Lxp0s", "project doesn't exist")
}
if resourceOwner == project.ResourceOwner {
return checkIfProjectHasRoles(grant.RoleKeys, project.Roles)
}
if _, projectGrant := proj_es_model.GetProjectGrantByResourceOwner(project.Grants, resourceOwner); projectGrant != nil {
if _, projectGrant := proj_es_model.GetProjectGrantByOrgID(project.Grants, resourceOwner); projectGrant != nil {
return checkIfProjectGrantHasRoles(grant.RoleKeys, projectGrant.RoleKeys)
}
return false
return nil
}
func checkIfProjectHasRoles(roles []string, existing []*proj_es_model.ProjectRole) bool {
func checkIfProjectHasRoles(roles []string, existing []*proj_es_model.ProjectRole) error {
for _, roleKey := range roles {
if _, role := proj_es_model.GetProjectRole(existing, roleKey); role == nil {
return false
return errors.ThrowPreconditionFailedf(nil, "EVENT-Lxp0s", "project doesn't have role %v", roleKey)
}
}
return true
return nil
}
func checkIfProjectGrantHasRoles(roles []string, existing []string) bool {
func checkIfProjectGrantHasRoles(roles []string, existing []string) error {
roleExists := false
for _, roleKey := range roles {
for _, existingRoleKey := range existing {
@@ -236,8 +249,8 @@ func checkIfProjectGrantHasRoles(roles []string, existing []string) bool {
}
}
if !roleExists {
return false
return errors.ThrowPreconditionFailed(nil, "EVENT-LSpwi", "project grant doesn't have role")
}
}
return true
return nil
}

View File

@@ -74,6 +74,7 @@ func TestUserGrantChangedAggregate(t *testing.T) {
ctx context.Context
existing *model.UserGrant
new *model.UserGrant
cascade bool
aggCreator *models.AggregateCreator
}
type res struct {
@@ -108,6 +109,29 @@ func TestUserGrantChangedAggregate(t *testing.T) {
eventTypes: []models.EventType{model.UserGrantChanged},
},
},
{
name: "change project grant cascade",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.UserGrant{
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
UserID: "UserID",
ProjectID: "ProjectID",
RoleKeys: []string{"Key"}},
new: &model.UserGrant{
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
UserID: "UserID",
ProjectID: "ProjectID",
RoleKeys: []string{"KeyChanged"},
},
cascade: true,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventTypes: []models.EventType{model.UserGrantCascadeChanged},
},
},
{
name: "existing grant nil",
args: args{
@@ -143,7 +167,7 @@ func TestUserGrantChangedAggregate(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := UserGrantChangedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
agg, err := UserGrantChangedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new, tt.args.cascade)(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))
@@ -169,6 +193,7 @@ func TestUserGrantRemovedAggregate(t *testing.T) {
ctx context.Context
existing *model.UserGrant
new *model.UserGrant
cascade bool
aggCreator *models.AggregateCreator
}
type res struct {
@@ -200,6 +225,26 @@ func TestUserGrantRemovedAggregate(t *testing.T) {
eventTypes: []models.EventType{model.UserGrantRemoved},
},
},
{
name: "remove app cascade",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.UserGrant{
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
UserID: "UserID",
ProjectID: "ProjectID",
RoleKeys: []string{"Key"}},
new: &model.UserGrant{
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
},
cascade: true,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventTypes: []models.EventType{model.UserGrantCascadeRemoved},
},
},
{
name: "existing project nil",
args: args{
@@ -230,7 +275,7 @@ func TestUserGrantRemovedAggregate(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
aggregates, err := UserGrantRemovedAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing, tt.args.new)
aggregates, err := UserGrantRemovedAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing, tt.args.new, tt.args.cascade)
if tt.res.errFunc == nil && len(aggregates[0].Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(aggregates[0].Events))

View File

@@ -19,6 +19,7 @@ const (
UserGrantKeyResourceOwner = "resource_owner"
UserGrantKeyState = "state"
UserGrantKeyOrgName = "org_name"
UserGrantKeyRole = "role_keys"
)
type UserGrantView struct {
@@ -98,7 +99,7 @@ func (g *UserGrantView) AppendEvent(event *models.Event) (err error) {
g.CreationDate = event.CreationDate
g.setRootData(event)
err = g.setData(event)
case es_model.UserGrantChanged:
case es_model.UserGrantChanged, es_model.UserGrantCascadeChanged:
err = g.setData(event)
case es_model.UserGrantDeactivated:
g.State = int32(model.USERGRANTSTATE_INACTIVE)

View File

@@ -63,6 +63,8 @@ func (key UserGrantSearchKey) ToColumnName() string {
return UserGrantKeyID
case grant_model.USERGRANTSEARCHKEY_ORG_NAME:
return UserGrantKeyOrgName
case grant_model.USERGRANTSEARCHKEY_ROLE_KEY:
return UserGrantKeyRole
default:
return ""
}

View File

@@ -62,6 +62,34 @@ func UserGrantsByProjectID(db *gorm.DB, table, projectID string) ([]*model.UserG
return users, nil
}
func UserGrantsByProjectIDAndRole(db *gorm.DB, table, projectID, roleKey string) ([]*model.UserGrantView, error) {
users := make([]*model.UserGrantView, 0)
queries := []*grant_model.UserGrantSearchQuery{
&grant_model.UserGrantSearchQuery{Key: grant_model.USERGRANTSEARCHKEY_PROJECT_ID, Value: projectID, Method: global_model.SEARCHMETHOD_EQUALS},
&grant_model.UserGrantSearchQuery{Key: grant_model.USERGRANTSEARCHKEY_ROLE_KEY, Value: roleKey, Method: global_model.SEARCHMETHOD_LIST_CONTAINS},
}
query := view.PrepareSearchQuery(table, model.UserGrantSearchRequest{Queries: queries})
_, err := query(db, &users)
if err != nil {
return nil, err
}
return users, nil
}
func UserGrantsByOrgIDAndProjectID(db *gorm.DB, table, orgID, projectID string) ([]*model.UserGrantView, error) {
users := make([]*model.UserGrantView, 0)
queries := []*grant_model.UserGrantSearchQuery{
&grant_model.UserGrantSearchQuery{Key: grant_model.USERGRANTSEARCHKEY_RESOURCEOWNER, Value: orgID, Method: global_model.SEARCHMETHOD_EQUALS},
&grant_model.UserGrantSearchQuery{Key: grant_model.USERGRANTSEARCHKEY_PROJECT_ID, Value: projectID, Method: global_model.SEARCHMETHOD_EQUALS},
}
query := view.PrepareSearchQuery(table, model.UserGrantSearchRequest{Queries: queries})
_, err := query(db, &users)
if err != nil {
return nil, err
}
return users, nil
}
func UserGrantsByOrgID(db *gorm.DB, table, orgID string) ([]*model.UserGrantView, error) {
users := make([]*model.UserGrantView, 0)
queries := []*grant_model.UserGrantSearchQuery{