feat: actions (#2377)

* feat(actions): begin api

* feat(actions): begin api

* api and projections

* fix: handle multiple statements for a single event in projections

* export func type

* fix test

* update to new reduce interface

* flows in login

* feat: jwt idp

* feat: command side

* feat: add tests

* actions and flows

* fill idp views with jwt idps and return apis

* add jwtEndpoint to jwt idp

* begin jwt request handling

* add feature

* merge

* merge

* handle jwt idp

* cleanup

* bug fixes

* autoregister

* get token from specific header name

* fix: proto

* fixes

* i18n

* begin tests

* fix and log http proxy

* remove docker cache

* fixes

* usergrants in actions api

* tests adn cleanup

* cleanup

* fix add user grant

* set login context

* i18n

Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
This commit is contained in:
Livio Amstutz
2021-09-27 13:43:49 +02:00
committed by GitHub
parent 5c32fc9c12
commit ed80a8bb1e
73 changed files with 5197 additions and 64 deletions

View File

@@ -9,6 +9,7 @@ import (
"github.com/caos/zitadel/internal/config/types"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/action"
"github.com/caos/zitadel/internal/api/http"
sd "github.com/caos/zitadel/internal/config/systemdefaults"
@@ -78,6 +79,7 @@ func StartCommands(eventstore *eventstore.Eventstore, defaults sd.SystemDefaults
usr_grant_repo.RegisterEventMappers(repo.eventstore)
proj_repo.RegisterEventMappers(repo.eventstore)
keypair.RegisterEventMappers(repo.eventstore)
action.RegisterEventMappers(repo.eventstore)
repo.idpConfigSecretCrypto, err = crypto.NewAESCrypto(defaults.IDPConfigVerificationKey)
if err != nil {

View File

@@ -31,6 +31,7 @@ type FeaturesWriteModel struct {
CustomTextMessage bool
CustomTextLogin bool
LockoutPolicy bool
Actions bool
}
func (wm *FeaturesWriteModel) Reduce() error {
@@ -98,6 +99,9 @@ func (wm *FeaturesWriteModel) Reduce() error {
if e.LockoutPolicy != nil {
wm.LockoutPolicy = *e.LockoutPolicy
}
if e.Actions != nil {
wm.Actions = *e.Actions
}
case *features.FeaturesRemovedEvent:
wm.State = domain.FeaturesStateRemoved
}

View File

@@ -0,0 +1,52 @@
package command
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/flow"
)
type FlowWriteModel struct {
eventstore.WriteModel
FlowType domain.FlowType
State domain.FlowState
Triggers map[domain.TriggerType][]string
}
func NewFlowWriteModel(flowType domain.FlowType, resourceOwner string) *FlowWriteModel {
return &FlowWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: resourceOwner,
ResourceOwner: resourceOwner,
},
FlowType: flowType,
Triggers: make(map[domain.TriggerType][]string),
}
}
func (wm *FlowWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *flow.TriggerActionsSetEvent:
if wm.Triggers == nil {
wm.Triggers = make(map[domain.TriggerType][]string)
}
wm.Triggers[e.TriggerType] = e.ActionIDs
case *flow.TriggerActionsCascadeRemovedEvent:
remove(wm.Triggers[e.TriggerType], e.ActionID)
case *flow.FlowClearedEvent:
wm.Triggers = nil
}
}
return wm.WriteModel.Reduce()
}
func remove(ids []string, id string) {
for i := 0; i < len(ids); i++ {
if ids[i] == id {
ids = append(ids[:i], ids[i+1:]...)
break
}
}
}

View File

@@ -54,6 +54,7 @@ func (c *Commands) setDefaultFeatures(ctx context.Context, existingFeatures *IAM
features.CustomTextMessage,
features.CustomTextLogin,
features.LockoutPolicy,
features.Actions,
)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")

View File

@@ -71,7 +71,8 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
metadataUser,
customTextMessage,
customTextLogin,
lockoutPolicy bool,
lockoutPolicy,
actions bool,
) (*iam.FeaturesSetEvent, bool) {
changes := make([]features.FeaturesChanges, 0)
@@ -133,6 +134,9 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
if wm.LockoutPolicy != lockoutPolicy {
changes = append(changes, features.ChangeLockoutPolicy(lockoutPolicy))
}
if wm.Actions != actions {
changes = append(changes, features.ChangeActions(actions))
}
if len(changes) == 0 {
return nil, false
}

View File

@@ -13,6 +13,7 @@ import (
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/repository/mock"
action_repo "github.com/caos/zitadel/internal/repository/action"
iam_repo "github.com/caos/zitadel/internal/repository/iam"
key_repo "github.com/caos/zitadel/internal/repository/keypair"
"github.com/caos/zitadel/internal/repository/org"
@@ -35,6 +36,7 @@ func eventstoreExpect(t *testing.T, expects ...expect) *eventstore.Eventstore {
proj_repo.RegisterEventMappers(es)
usergrant.RegisterEventMappers(es)
key_repo.RegisterEventMappers(es)
action_repo.RegisterEventMappers(es)
return es
}

View File

@@ -0,0 +1,200 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/action"
"github.com/caos/zitadel/internal/repository/org"
)
func (c *Commands) AddAction(ctx context.Context, addAction *domain.Action, resourceOwner string) (_ string, _ *domain.ObjectDetails, err error) {
if !addAction.IsValid() {
return "", nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-eg2gf", "Errors.Action.Invalid")
}
addAction.AggregateID, err = c.idGenerator.Next()
if err != nil {
return "", nil, err
}
actionModel := NewActionWriteModel(addAction.AggregateID, resourceOwner)
actionAgg := ActionAggregateFromWriteModel(&actionModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, action.NewAddedEvent(
ctx,
actionAgg,
addAction.Name,
addAction.Script,
addAction.Timeout,
addAction.AllowedToFail,
))
if err != nil {
return "", nil, err
}
err = AppendAndReduce(actionModel, pushedEvents...)
if err != nil {
return "", nil, err
}
return actionModel.AggregateID, writeModelToObjectDetails(&actionModel.WriteModel), nil
}
func (c *Commands) ChangeAction(ctx context.Context, actionChange *domain.Action, resourceOwner string) (*domain.ObjectDetails, error) {
if !actionChange.IsValid() || actionChange.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Df2f3", "Errors.Action.Invalid")
}
existingAction, err := c.getActionWriteModelByID(ctx, actionChange.AggregateID, resourceOwner)
if err != nil {
return nil, err
}
if !existingAction.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Sfg2t", "Errors.Action.NotFound")
}
actionAgg := ActionAggregateFromWriteModel(&existingAction.WriteModel)
changedEvent, err := existingAction.NewChangedEvent(
ctx,
actionAgg,
actionChange.Name,
actionChange.Script,
actionChange.Timeout,
actionChange.AllowedToFail)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingAction, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingAction.WriteModel), nil
}
func (c *Commands) DeactivateAction(ctx context.Context, actionID string, resourceOwner string) (*domain.ObjectDetails, error) {
if actionID == "" || resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-DAhk5", "Errors.IDMissing")
}
existingAction, err := c.getActionWriteModelByID(ctx, actionID, resourceOwner)
if err != nil {
return nil, err
}
if !existingAction.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-NRmhu", "Errors.Action.NotFound")
}
if existingAction.State != domain.ActionStateActive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Dgj92", "Errors.Action.NotActive")
}
actionAgg := ActionAggregateFromWriteModel(&existingAction.WriteModel)
events := []eventstore.EventPusher{
action.NewDeactivatedEvent(ctx, actionAgg),
}
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingAction, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingAction.WriteModel), nil
}
func (c *Commands) ReactivateAction(ctx context.Context, actionID string, resourceOwner string) (*domain.ObjectDetails, error) {
if actionID == "" || resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-BNm56", "Errors.IDMissing")
}
existingAction, err := c.getActionWriteModelByID(ctx, actionID, resourceOwner)
if err != nil {
return nil, err
}
if !existingAction.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Aa22g", "Errors.Action.NotFound")
}
if existingAction.State != domain.ActionStateInactive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-J53zh", "Errors.Action.NotInactive")
}
actionAgg := ActionAggregateFromWriteModel(&existingAction.WriteModel)
events := []eventstore.EventPusher{
action.NewReactivatedEvent(ctx, actionAgg),
}
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingAction, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingAction.WriteModel), nil
}
func (c *Commands) DeleteAction(ctx context.Context, actionID, resourceOwner string, flowTypes ...domain.FlowType) (*domain.ObjectDetails, error) {
if actionID == "" || resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Gfg3g", "Errors.IDMissing")
}
existingAction, err := c.getActionWriteModelByID(ctx, actionID, resourceOwner)
if err != nil {
return nil, err
}
if !existingAction.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Dgh4h", "Errors.Action.NotFound")
}
actionAgg := ActionAggregateFromWriteModel(&existingAction.WriteModel)
events := []eventstore.EventPusher{
action.NewRemovedEvent(ctx, actionAgg, existingAction.Name),
}
orgAgg := org.NewAggregate(resourceOwner, resourceOwner).Aggregate
for _, flowType := range flowTypes {
events = append(events, org.NewTriggerActionsCascadeRemovedEvent(ctx, &orgAgg, flowType, actionID))
}
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingAction, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingAction.WriteModel), nil
}
func (c *Commands) removeActionsFromOrg(ctx context.Context, resourceOwner string) ([]eventstore.EventPusher, error) {
existingActions, err := c.getActionsByOrgWriteModelByID(ctx, resourceOwner)
if err != nil {
return nil, err
}
if len(existingActions.Actions) == 0 {
return nil, nil
}
events := make([]eventstore.EventPusher, 0, len(existingActions.Actions))
for id, name := range existingActions.Actions {
actionAgg := NewActionAggregate(id, resourceOwner)
events = append(events, action.NewRemovedEvent(ctx, actionAgg, name))
}
return events, nil
}
func (c *Commands) getActionWriteModelByID(ctx context.Context, actionID string, resourceOwner string) (*ActionWriteModel, error) {
actionWriteModel := NewActionWriteModel(actionID, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, actionWriteModel)
if err != nil {
return nil, err
}
return actionWriteModel, nil
}
func (c *Commands) getActionsByOrgWriteModelByID(ctx context.Context, resourceOwner string) (*ActionsListByOrgModel, error) {
actionWriteModel := NewActionsListByOrgModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, actionWriteModel)
if err != nil {
return nil, err
}
return actionWriteModel, nil
}

View File

@@ -0,0 +1,194 @@
package command
import (
"context"
"time"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/action"
)
type ActionWriteModel struct {
eventstore.WriteModel
Name string
Script string
Timeout time.Duration
AllowedToFail bool
State domain.ActionState
}
func NewActionWriteModel(actionID string, resourceOwner string) *ActionWriteModel {
return &ActionWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: actionID,
ResourceOwner: resourceOwner,
},
}
}
func (wm *ActionWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *action.AddedEvent:
wm.Name = e.Name
wm.Script = e.Script
wm.Timeout = e.Timeout
wm.AllowedToFail = e.AllowedToFail
wm.State = domain.ActionStateActive
case *action.ChangedEvent:
if e.Name != nil {
wm.Name = *e.Name
}
if e.Script != nil {
wm.Script = *e.Script
}
if e.Timeout != nil {
wm.Timeout = *e.Timeout
}
if e.AllowedToFail != nil {
wm.AllowedToFail = *e.AllowedToFail
}
case *action.DeactivatedEvent:
wm.State = domain.ActionStateInactive
case *action.ReactivatedEvent:
wm.State = domain.ActionStateActive
case *action.RemovedEvent:
wm.State = domain.ActionStateRemoved
}
}
return wm.WriteModel.Reduce()
}
func (wm *ActionWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(action.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(action.AddedEventType,
action.ChangedEventType,
action.DeactivatedEventType,
action.ReactivatedEventType,
action.RemovedEventType).
Builder()
}
func (wm *ActionWriteModel) NewChangedEvent(
ctx context.Context,
agg *eventstore.Aggregate,
name string,
script string,
timeout time.Duration,
allowedToFail bool,
) (*action.ChangedEvent, error) {
changes := make([]action.ActionChanges, 0)
if wm.Name != name {
changes = append(changes, action.ChangeName(name, wm.Name))
}
if wm.Script != script {
changes = append(changes, action.ChangeScript(script))
}
if wm.Timeout != timeout {
changes = append(changes, action.ChangeTimeout(timeout))
}
if wm.AllowedToFail != allowedToFail {
changes = append(changes, action.ChangeAllowedToFail(allowedToFail))
}
return action.NewChangedEvent(ctx, agg, changes)
}
func ActionAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
return eventstore.AggregateFromWriteModel(wm, action.AggregateType, action.AggregateVersion)
}
func NewActionAggregate(id, resourceOwner string) *eventstore.Aggregate {
return ActionAggregateFromWriteModel(&eventstore.WriteModel{
AggregateID: id,
ResourceOwner: resourceOwner,
})
}
type ActionExistsModel struct {
eventstore.WriteModel
actionIDs []string
checkedIDs []string
}
func NewActionsExistModel(actionIDs []string, resourceOwner string) *ActionExistsModel {
return &ActionExistsModel{
WriteModel: eventstore.WriteModel{
ResourceOwner: resourceOwner,
},
actionIDs: actionIDs,
}
}
func (wm *ActionExistsModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *action.AddedEvent:
wm.checkedIDs = append(wm.checkedIDs, e.Aggregate().ID)
case *action.RemovedEvent:
for i := len(wm.checkedIDs) - 1; i >= 0; i-- {
if wm.checkedIDs[i] == e.Aggregate().ID {
wm.checkedIDs[i] = wm.checkedIDs[len(wm.checkedIDs)-1]
wm.checkedIDs[len(wm.checkedIDs)-1] = ""
wm.checkedIDs = wm.checkedIDs[:len(wm.checkedIDs)-1]
break
}
}
}
}
return wm.WriteModel.Reduce()
}
func (wm *ActionExistsModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(action.AggregateType).
AggregateIDs(wm.actionIDs...).
EventTypes(action.AddedEventType,
action.RemovedEventType).
Builder()
}
type ActionsListByOrgModel struct {
eventstore.WriteModel
Actions map[string]string
}
func NewActionsListByOrgModel(resourceOwner string) *ActionsListByOrgModel {
return &ActionsListByOrgModel{
WriteModel: eventstore.WriteModel{
ResourceOwner: resourceOwner,
},
Actions: make(map[string]string),
}
}
func (wm *ActionsListByOrgModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *action.AddedEvent:
wm.Actions[e.Aggregate().ID] = e.Name
case *action.RemovedEvent:
delete(wm.Actions, e.Aggregate().ID)
}
}
return wm.WriteModel.Reduce()
}
func (wm *ActionsListByOrgModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(action.AggregateType).
EventTypes(action.AddedEventType,
action.RemovedEventType).
Builder()
}

View File

@@ -0,0 +1,791 @@
package command
import (
"context"
"testing"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/id"
"github.com/caos/zitadel/internal/id/mock"
"github.com/caos/zitadel/internal/repository/action"
"github.com/caos/zitadel/internal/repository/org"
"github.com/stretchr/testify/assert"
)
func TestCommands_AddAction(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
}
type args struct {
ctx context.Context
addAction *domain.Action
resourceOwner string
}
type res struct {
id string
details *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"no name, error",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
addAction: &domain.Action{
Script: "test()",
},
resourceOwner: "org1",
},
res{
err: errors.IsErrorInvalidArgument,
},
},
{
"unique constraint failed, error",
fields{
eventstore: eventstoreExpect(t,
expectPushFailed(
errors.ThrowPreconditionFailed(nil, "id", "name already exists"),
[]*repository.Event{
eventFromEventPusher(
action.NewAddedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
"name",
"name() {};",
0,
false,
),
),
},
uniqueConstraintsFromEventConstraint(action.NewAddActionNameUniqueConstraint("name", "org1")),
),
),
idGenerator: mock.ExpectID(t, "id1"),
},
args{
ctx: context.Background(),
addAction: &domain.Action{
Name: "name",
Script: "name() {};",
},
resourceOwner: "org1",
},
res{
err: errors.IsPreconditionFailed,
},
},
{
"push ok",
fields{
eventstore: eventstoreExpect(t,
expectPush(
[]*repository.Event{
eventFromEventPusher(
action.NewAddedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
"name",
"name() {};",
0,
false,
),
),
},
uniqueConstraintsFromEventConstraint(action.NewAddActionNameUniqueConstraint("name", "org1")),
),
),
idGenerator: mock.ExpectID(t, "id1"),
},
args{
ctx: context.Background(),
addAction: &domain.Action{
Name: "name",
Script: "name() {};",
},
resourceOwner: "org1",
},
res{
id: "id1",
details: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
}
id, details, err := c.AddAction(tt.args.ctx, tt.args.addAction, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.id, id)
assert.Equal(t, tt.res.details, details)
}
})
}
}
func TestCommands_ChangeAction(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
changeAction *domain.Action
resourceOwner string
}
type res struct {
details *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"id missing, error",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
changeAction: &domain.Action{
Name: "name",
Script: "name() {};",
},
resourceOwner: "org1",
},
res{
err: errors.IsErrorInvalidArgument,
},
},
{
"not found, error",
fields{
eventstore: eventstoreExpect(t,
expectFilter(),
),
},
args{
ctx: context.Background(),
changeAction: &domain.Action{
ObjectRoot: models.ObjectRoot{
AggregateID: "id1",
},
Name: "name",
Script: "name() {};",
},
resourceOwner: "org1",
},
res{
err: errors.IsNotFound,
},
},
{
"no changes, error",
fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
action.NewAddedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
"name",
"name() {};",
0,
false,
),
),
),
),
},
args{
ctx: context.Background(),
changeAction: &domain.Action{
ObjectRoot: models.ObjectRoot{
AggregateID: "id1",
},
Name: "name",
Script: "name() {};",
},
resourceOwner: "org1",
},
res{
err: errors.IsPreconditionFailed,
},
},
{
"unique constraint failed, error",
fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
action.NewAddedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
"name",
"name() {};",
0,
false,
),
),
),
expectPushFailed(
errors.ThrowPreconditionFailed(nil, "id", "name already exists"),
[]*repository.Event{
eventFromEventPusher(
func() *action.ChangedEvent {
event, _ := action.NewChangedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
[]action.ActionChanges{
action.ChangeName("name2", "name"),
action.ChangeScript("name2() {};"),
},
)
return event
}(),
),
},
uniqueConstraintsFromEventConstraint(action.NewRemoveActionNameUniqueConstraint("name", "org1")),
uniqueConstraintsFromEventConstraint(action.NewAddActionNameUniqueConstraint("name2", "org1")),
),
),
},
args{
ctx: context.Background(),
changeAction: &domain.Action{
ObjectRoot: models.ObjectRoot{
AggregateID: "id1",
},
Name: "name2",
Script: "name2() {};",
},
resourceOwner: "org1",
},
res{
err: errors.IsPreconditionFailed,
},
},
{
"push ok",
fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
action.NewAddedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
"name",
"name() {};",
0,
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
func() *action.ChangedEvent {
event, _ := action.NewChangedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
[]action.ActionChanges{
action.ChangeName("name2", "name"),
action.ChangeScript("name2() {};"),
},
)
return event
}(),
),
},
uniqueConstraintsFromEventConstraint(action.NewRemoveActionNameUniqueConstraint("name", "org1")),
uniqueConstraintsFromEventConstraint(action.NewAddActionNameUniqueConstraint("name2", "org1")),
),
),
},
args{
ctx: context.Background(),
changeAction: &domain.Action{
ObjectRoot: models.ObjectRoot{
AggregateID: "id1",
},
Name: "name2",
Script: "name2() {};",
},
resourceOwner: "org1",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
}
details, err := c.ChangeAction(tt.args.ctx, tt.args.changeAction, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.details, details)
}
})
}
}
func TestCommands_DeactivateAction(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
actionID string
resourceOwner string
}
type res struct {
details *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"id missing, error",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
actionID: "",
resourceOwner: "org1",
},
res{
err: errors.IsErrorInvalidArgument,
},
},
{
"not found, error",
fields{
eventstore: eventstoreExpect(t,
expectFilter(),
),
},
args{
ctx: context.Background(),
actionID: "id1",
resourceOwner: "org1",
},
res{
err: errors.IsNotFound,
},
},
{
"not active, error",
fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
action.NewAddedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
"name",
"name() {};",
0,
false,
),
),
eventFromEventPusher(
action.NewDeactivatedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
),
),
),
),
},
args{
ctx: context.Background(),
actionID: "id1",
resourceOwner: "org1",
},
res{
err: errors.IsPreconditionFailed,
},
},
{
"deactivate ok",
fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
action.NewAddedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
"name",
"name() {};",
0,
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
action.NewDeactivatedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
),
),
},
),
),
},
args{
ctx: context.Background(),
actionID: "id1",
resourceOwner: "org1",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
}
details, err := c.DeactivateAction(tt.args.ctx, tt.args.actionID, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.details, details)
}
})
}
}
func TestCommands_ReactivateAction(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
actionID string
resourceOwner string
}
type res struct {
details *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"id missing, error",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
actionID: "",
resourceOwner: "org1",
},
res{
err: errors.IsErrorInvalidArgument,
},
},
{
"not found, error",
fields{
eventstore: eventstoreExpect(t,
expectFilter(),
),
},
args{
ctx: context.Background(),
actionID: "id1",
resourceOwner: "org1",
},
res{
err: errors.IsNotFound,
},
},
{
"not inactive, error",
fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
action.NewAddedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
"name",
"name() {};",
0,
false,
),
),
),
),
},
args{
ctx: context.Background(),
actionID: "id1",
resourceOwner: "org1",
},
res{
err: errors.IsPreconditionFailed,
},
},
{
"reactivate ok",
fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
action.NewAddedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
"name",
"name() {};",
0,
false,
),
),
eventFromEventPusher(
action.NewDeactivatedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
action.NewReactivatedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
),
),
},
),
),
},
args{
ctx: context.Background(),
actionID: "id1",
resourceOwner: "org1",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
}
details, err := c.ReactivateAction(tt.args.ctx, tt.args.actionID, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.details, details)
}
})
}
}
func TestCommands_DeleteAction(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
id string
resourceOwner string
flowTypes []domain.FlowType
}
type res struct {
details *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"id or resourceOwner emtpy, error",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
id: "",
resourceOwner: "",
},
res{
err: errors.IsErrorInvalidArgument,
},
},
{
"action not found, error",
fields{
eventstore: eventstoreExpect(t,
expectFilter(),
),
},
args{
ctx: context.Background(),
id: "id1",
resourceOwner: "org1",
},
res{
err: errors.IsNotFound,
},
},
{
"remove ok",
fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
action.NewAddedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
"name",
"name() {};",
0,
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
action.NewRemovedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
"name",
),
),
},
uniqueConstraintsFromEventConstraint(action.NewRemoveActionNameUniqueConstraint("name", "org1")),
),
),
},
args{
ctx: context.Background(),
id: "id1",
resourceOwner: "org1",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
"remove with used action ok",
fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
action.NewAddedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
"name",
"name() {};",
0,
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
action.NewRemovedEvent(context.Background(),
&action.NewAggregate("id1", "org1").Aggregate,
"name",
),
),
eventFromEventPusher(
org.NewTriggerActionsCascadeRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
domain.FlowTypeExternalAuthentication,
"id1",
),
),
},
uniqueConstraintsFromEventConstraint(action.NewRemoveActionNameUniqueConstraint("name", "org1")),
),
),
},
args{
ctx: context.Background(),
id: "id1",
resourceOwner: "org1",
flowTypes: []domain.FlowType{
domain.FlowTypeExternalAuthentication,
},
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
}
details, err := c.DeleteAction(tt.args.ctx, tt.args.id, tt.args.resourceOwner, tt.args.flowTypes...)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.details, details)
}
})
}
}

View File

@@ -45,6 +45,7 @@ func (c *Commands) SetOrgFeatures(ctx context.Context, resourceOwner string, fea
features.CustomTextMessage,
features.CustomTextLogin,
features.LockoutPolicy,
features.Actions,
)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
@@ -176,6 +177,15 @@ func (c *Commands) ensureOrgSettingsToFeatures(ctx context.Context, orgID string
events = append(events, removeOrgUserMetadatas...)
}
}
if !features.Actions {
removeOrgActions, err := c.removeActionsFromOrg(ctx, orgID)
if err != nil {
return nil, err
}
if len(removeOrgActions) > 0 {
events = append(events, removeOrgActions...)
}
}
return events, nil
}

View File

@@ -78,7 +78,8 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
metadataUser,
customTextMessage,
customTextLogin,
lockoutPolicy bool,
lockoutPolicy,
actions bool,
) (*org.FeaturesSetEvent, bool) {
changes := make([]features.FeaturesChanges, 0)
@@ -143,6 +144,9 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
if wm.LockoutPolicy != lockoutPolicy {
changes = append(changes, features.ChangeLockoutPolicy(lockoutPolicy))
}
if wm.Actions != actions {
changes = append(changes, features.ChangeActions(actions))
}
if len(changes) == 0 {
return nil, false

View File

@@ -275,6 +275,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
expectFilter(),
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -307,6 +308,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
PrivacyPolicy: false,
MetadataUser: false,
LockoutPolicy: false,
Actions: false,
},
},
res: res{
@@ -472,6 +474,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
expectFilter(),
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -509,6 +512,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
MetadataUser: false,
PrivacyPolicy: false,
LockoutPolicy: false,
Actions: false,
},
},
res: res{
@@ -681,6 +685,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
expectFilter(),
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -721,6 +726,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
MetadataUser: false,
PrivacyPolicy: false,
LockoutPolicy: false,
Actions: false,
},
},
res: res{
@@ -900,6 +906,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
expectFilter(),
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -943,6 +950,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
MetadataUser: false,
PrivacyPolicy: false,
LockoutPolicy: false,
Actions: false,
},
},
res: res{
@@ -1174,6 +1182,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
expectFilter(),
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -1234,6 +1243,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
MetadataUser: false,
PrivacyPolicy: false,
LockoutPolicy: false,
Actions: false,
},
},
res: res{
@@ -1387,6 +1397,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -1422,6 +1433,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
PrivacyPolicy: false,
MetadataUser: false,
LockoutPolicy: false,
Actions: false,
},
},
res: res{
@@ -1635,6 +1647,7 @@ func TestCommandSide_RemoveOrgFeatures(t *testing.T) {
),
),
expectFilter(),
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(

View File

@@ -0,0 +1,83 @@
package command
import (
"context"
"reflect"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/repository/org"
)
func (c *Commands) ClearFlow(ctx context.Context, flowType domain.FlowType, resourceOwner string) (*domain.ObjectDetails, error) {
if !flowType.Valid() || resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Dfw2h", "Errors.Flow.FlowTypeMissing")
}
existingFlow, err := c.getOrgFlowWriteModelByType(ctx, flowType, resourceOwner)
if err != nil {
return nil, err
}
if len(existingFlow.Triggers) == 0 {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-DgGh3", "Errors.Flow.Empty")
}
orgAgg := OrgAggregateFromWriteModel(&existingFlow.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewFlowClearedEvent(ctx, orgAgg, flowType))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingFlow, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingFlow.WriteModel), nil
}
func (c *Commands) SetTriggerActions(ctx context.Context, flowType domain.FlowType, triggerType domain.TriggerType, actionIDs []string, resourceOwner string) (*domain.ObjectDetails, error) {
if !flowType.Valid() || !triggerType.Valid() || resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Dfhj5", "Errors.Flow.FlowTypeMissing")
}
if !flowType.HasTrigger(triggerType) {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Dfgh6", "Errors.Flow.WrongTriggerType")
}
existingFlow, err := c.getOrgFlowWriteModelByType(ctx, flowType, resourceOwner)
if err != nil {
return nil, err
}
if reflect.DeepEqual(existingFlow.Triggers[triggerType], actionIDs) {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Nfh52", "Errors.Flow.NoChanges")
}
if len(actionIDs) > 0 {
exists, err := c.actionsIDsExist(ctx, actionIDs, resourceOwner)
if err != nil {
return nil, err
}
if !exists {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-dg422", "Errors.Flow.ActionIDsNotExist")
}
}
orgAgg := OrgAggregateFromWriteModel(&existingFlow.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewTriggerActionsSetEvent(ctx, orgAgg, flowType, triggerType, actionIDs))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingFlow, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingFlow.WriteModel), nil
}
func (c *Commands) getOrgFlowWriteModelByType(ctx context.Context, flowType domain.FlowType, resourceOwner string) (*OrgFlowWriteModel, error) {
flowWriteModel := NewOrgFlowWriteModel(flowType, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, flowWriteModel)
if err != nil {
return nil, err
}
return flowWriteModel, nil
}
func (c *Commands) actionsIDsExist(ctx context.Context, ids []string, resourceOwner string) (bool, error) {
actionIDsModel := NewActionsExistModel(ids, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, actionIDsModel)
return len(actionIDsModel.actionIDs) == len(actionIDsModel.checkedIDs), err
}

View File

@@ -0,0 +1,54 @@
package command
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
)
type OrgFlowWriteModel struct {
FlowWriteModel
}
func NewOrgFlowWriteModel(flowType domain.FlowType, resourceOwner string) *OrgFlowWriteModel {
return &OrgFlowWriteModel{
FlowWriteModel: *NewFlowWriteModel(flowType, resourceOwner),
}
}
func (wm *OrgFlowWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *org.TriggerActionsSetEvent:
if e.FlowType != wm.FlowType {
continue
}
wm.FlowWriteModel.AppendEvents(&e.TriggerActionsSetEvent)
case *org.TriggerActionsCascadeRemovedEvent:
if e.FlowType != wm.FlowType {
continue
}
wm.FlowWriteModel.AppendEvents(&e.TriggerActionsCascadeRemovedEvent)
case *org.FlowClearedEvent:
if e.FlowType != wm.FlowType {
continue
}
wm.FlowWriteModel.AppendEvents(&e.FlowClearedEvent)
}
}
}
func (wm *OrgFlowWriteModel) Reduce() error {
return wm.FlowWriteModel.Reduce()
}
func (wm *OrgFlowWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(org.AggregateType).
EventTypes(org.TriggerActionsSetEventType,
org.TriggerActionsCascadeRemovedEventType,
org.FlowClearedEventType).
Builder()
}

View File

@@ -0,0 +1,286 @@
package command
import (
"context"
"testing"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/action"
"github.com/caos/zitadel/internal/repository/org"
"github.com/stretchr/testify/assert"
)
func TestCommands_ClearFlow(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
flowType domain.FlowType
resourceOwner string
}
type res struct {
details *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"invalid flow type, error",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
flowType: domain.FlowTypeUnspecified,
resourceOwner: "org1",
},
res{
details: nil,
err: errors.IsErrorInvalidArgument,
},
},
{
"already empty, error",
fields{
eventstore: eventstoreExpect(t,
expectFilter(),
),
},
args{
ctx: context.Background(),
flowType: domain.FlowTypeExternalAuthentication,
resourceOwner: "org1",
},
res{
details: nil,
err: errors.IsPreconditionFailed,
},
},
{
"clear ok",
fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
org.NewTriggerActionsSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
domain.FlowTypeExternalAuthentication,
domain.TriggerTypePostAuthentication,
[]string{"actionID1"},
),
),
),
expectPush(
eventPusherToEvents(
org.NewFlowClearedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
domain.FlowTypeExternalAuthentication,
),
),
),
),
},
args{
ctx: context.Background(),
flowType: domain.FlowTypeExternalAuthentication,
resourceOwner: "org1",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
},
err: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
}
details, err := c.ClearFlow(tt.args.ctx, tt.args.flowType, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.details, details)
}
})
}
}
func TestCommands_SetTriggerActions(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
flowType domain.FlowType
resourceOwner string
triggerType domain.TriggerType
actionIDs []string
}
type res struct {
details *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"invalid flow type, error",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
flowType: domain.FlowTypeUnspecified,
triggerType: domain.TriggerTypePostAuthentication,
actionIDs: []string{"actionID1"},
resourceOwner: "org1",
},
res{
details: nil,
err: errors.IsErrorInvalidArgument,
},
},
//TODO: combination not possible at the moment, add when more flow types available
//{
// "impossible flow / trigger type, error",
// fields{
// eventstore: eventstoreExpect(t,),
// },
// args{
// ctx: context.Background(),
// flowType: domain.FlowTypeUnspecified,
// triggerType: domain.TriggerTypePostAuthentication,
// actionIDs: []string{"actionID1"},
// resourceOwner: "org1",
// },
// res{
// details: nil,
// err: errors.IsErrorInvalidArgument,
// },
//},
{
"no changes, error",
fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
org.NewTriggerActionsSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
domain.FlowTypeExternalAuthentication,
domain.TriggerTypePostAuthentication,
[]string{"actionID1"},
),
),
),
),
},
args{
ctx: context.Background(),
flowType: domain.FlowTypeExternalAuthentication,
triggerType: domain.TriggerTypePostAuthentication,
actionIDs: []string{"actionID1"},
resourceOwner: "org1",
},
res{
details: nil,
err: errors.IsPreconditionFailed,
},
},
{
"actionID not exists, error",
fields{
eventstore: eventstoreExpect(t,
expectFilter(),
expectFilter(),
),
},
args{
ctx: context.Background(),
flowType: domain.FlowTypeExternalAuthentication,
triggerType: domain.TriggerTypePostAuthentication,
actionIDs: []string{"actionID1"},
resourceOwner: "org1",
},
res{
details: nil,
err: errors.IsPreconditionFailed,
},
},
{
"set ok",
fields{
eventstore: eventstoreExpect(t,
expectFilter(),
expectFilter(
eventFromEventPusher(
action.NewAddedEvent(context.Background(),
&action.NewAggregate("action1", "org1").Aggregate,
"actionID1",
"function(ctx, api) action {};",
0,
false,
),
),
),
expectPush(
eventPusherToEvents(
org.NewTriggerActionsSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
domain.FlowTypeExternalAuthentication,
domain.TriggerTypePostAuthentication,
[]string{"actionID1"},
),
),
),
),
},
args{
ctx: context.Background(),
flowType: domain.FlowTypeExternalAuthentication,
triggerType: domain.TriggerTypePostAuthentication,
actionIDs: []string{"actionID1"},
resourceOwner: "org1",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
},
err: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
}
details, err := c.SetTriggerActions(tt.args.ctx, tt.args.flowType, tt.args.triggerType, tt.args.actionIDs, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.details, details)
}
})
}
}

View File

@@ -2,9 +2,10 @@ package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"reflect"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/repository/usergrant"
@@ -29,10 +30,6 @@ func (c *Commands) AddUserGrant(ctx context.Context, usergrant *domain.UserGrant
}
func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string) (pusher eventstore.EventPusher, _ *UserGrantWriteModel, err error) {
err = checkExplicitProjectPermission(ctx, userGrant.ProjectGrantID, userGrant.ProjectID)
if err != nil {
return nil, nil, err
}
if !userGrant.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0fs", "Errors.UserGrant.Invalid")
}

View File

@@ -2,6 +2,8 @@ package command
import (
"context"
"testing"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
@@ -15,7 +17,6 @@ import (
"github.com/caos/zitadel/internal/repository/usergrant"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"testing"
)
func TestCommandSide_AddUserGrant(t *testing.T) {
@@ -38,24 +39,6 @@ func TestCommandSide_AddUserGrant(t *testing.T) {
args args
res res
}{
{
name: "invalid permissions, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
userGrant: &domain.UserGrant{
UserID: "user1",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPermissionDenied,
},
},
{
name: "invalid usergrant, error",
fields: fields{