diff --git a/automation/service/trigger.go b/automation/service/trigger.go index 6998da8e9..01ee599a8 100644 --- a/automation/service/trigger.go +++ b/automation/service/trigger.go @@ -47,6 +47,7 @@ type ( triggerAccessController interface { CanSearchTriggers(context.Context) bool CanManageWorkflowTriggers(context.Context, *types.Workflow) bool + CanExecuteWorkflow(context.Context, *types.Workflow) bool } triggerEventTriggerHandler interface { @@ -510,7 +511,7 @@ func (svc *trigger) registerTriggers(wf *types.Workflow, runAs auth.Identifiable ).Wrap(wf.Issues) } } else { - handlerFn = makeWorkflowHandler(svc.session, t, wf, g, runAs) + handlerFn = makeWorkflowHandler(svc.ac, svc.session, t, wf, g, runAs) } ops = append( @@ -573,71 +574,6 @@ func (svc *trigger) unregisterTriggers(tt ...*types.Trigger) { } } -func makeWorkflowHandler(s *session, t *types.Trigger, wf *types.Workflow, g *wfexec.Graph, runAs auth.Identifiable) eventbus.HandlerFn { - return func(ctx context.Context, ev eventbus.Event) (err error) { - - var ( - // create session scope from predefined workflow scope and trigger input - scope = wf.Scope.Merge(t.Input) - evScope *expr.Vars - wait WaitFn - ) - - if enc, is := ev.(varsEncoder); is { - if evScope, err = enc.EncodeVars(); err != nil { - return - } - - scope = scope.Merge(evScope) - } - - _ = scope.AssignFieldValue("eventType", expr.Must(expr.NewString(ev.EventType()))) - _ = scope.AssignFieldValue("resourceType", expr.Must(expr.NewString(ev.ResourceType()))) - - if runAs == nil { - // @todo can/should we get alternative identity from Event? - // for example: - // - use http auth header and get username - // - use from/to/replyTo and use that as an identifier - runAs = auth.GetIdentityFromContext(ctx) - } - - wait, err = s.Start(g, runAs, types.SessionStartParams{ - WorkflowID: wf.ID, - KeepFor: wf.KeepSessions, - Trace: wf.Trace, - Input: scope, - StepID: t.StepID, - EventType: t.EventType, - ResourceType: t.ResourceType, - }) - - if err != nil { - return err - } - - if wf.CheckDeferred() { - // deferred workflow, return right away and keep the workflow session - // running without waiting for the execution - return nil - } - - // wait for the workflow to complete - // reuse scope for results - // this will be decoded back to event properties - scope, _, err = wait(ctx) - if err != nil { - return - } - - if dec, is := ev.(varsDecoder); is { - return dec.DecodeVars(scope) - } - - return nil - } -} - func loadTrigger(ctx context.Context, s store.Storer, workflowID uint64) (res *types.Trigger, err error) { if workflowID == 0 { return nil, TriggerErrInvalidID() diff --git a/automation/service/workflow.go b/automation/service/workflow.go index 210cbe834..e74f47b13 100644 --- a/automation/service/workflow.go +++ b/automation/service/workflow.go @@ -51,6 +51,10 @@ type ( Grant(ctx context.Context, rr ...*rbac.Rule) error } + workflowExecController interface { + CanExecuteWorkflow(context.Context, *types.Workflow) bool + } + workflowEventTriggerHandler interface { Register(h eventbus.HandlerFn, ops ...eventbus.HandlerRegOp) uintptr Unregister(ptrs ...uintptr) @@ -449,7 +453,7 @@ func (svc workflow) handleDelete(ctx context.Context, res *types.Workflow) (work } func (svc workflow) handleUndelete(ctx context.Context, res *types.Workflow) (workflowChanges, error) { - if !svc.ac.CanDeleteWorkflow(ctx, res) { + if !svc.ac.CanUndeleteWorkflow(ctx, res) { return workflowUnchanged, WorkflowErrNotAllowedToUndelete() } @@ -475,6 +479,74 @@ func (svc *workflow) Load(ctx context.Context) error { return svc.triggers.registerWorkflows(ctx, wwf...) } +func makeWorkflowHandler(ac workflowExecController, s *session, t *types.Trigger, wf *types.Workflow, g *wfexec.Graph, runAs intAuth.Identifiable) eventbus.HandlerFn { + return func(ctx context.Context, ev eventbus.Event) (err error) { + if !ac.(*accessControl).CanExecuteWorkflow(ctx, wf) { + return WorkflowErrNotAllowedToExecute() + } + + var ( + // create session scope from predefined workflow scope and trigger input + scope = wf.Scope.Merge(t.Input) + evScope *expr.Vars + wait WaitFn + ) + + if enc, is := ev.(varsEncoder); is { + if evScope, err = enc.EncodeVars(); err != nil { + return + } + + scope = scope.Merge(evScope) + } + + _ = scope.AssignFieldValue("eventType", expr.Must(expr.NewString(ev.EventType()))) + _ = scope.AssignFieldValue("resourceType", expr.Must(expr.NewString(ev.ResourceType()))) + + if runAs == nil { + // @todo can/should we get alternative identity from Event? + // for example: + // - use http auth header and get username + // - use from/to/replyTo and use that as an identifier + runAs = intAuth.GetIdentityFromContext(ctx) + } + + wait, err = s.Start(g, runAs, types.SessionStartParams{ + WorkflowID: wf.ID, + KeepFor: wf.KeepSessions, + Trace: wf.Trace, + Input: scope, + StepID: t.StepID, + EventType: t.EventType, + ResourceType: t.ResourceType, + }) + + if err != nil { + return err + } + + if wf.CheckDeferred() { + // deferred workflow, return right away and keep the workflow session + // running without waiting for the execution + return nil + } + + // wait for the workflow to complete + // reuse scope for results + // this will be decoded back to event properties + scope, _, err = wait(ctx) + if err != nil { + return + } + + if dec, is := ev.(varsDecoder); is { + return dec.DecodeVars(scope) + } + + return nil + } +} + func loadWorkflow(ctx context.Context, s store.Storer, workflowID uint64) (res *types.Workflow, err error) { if workflowID == 0 { return nil, WorkflowErrInvalidID() diff --git a/automation/service/workflow_actions.gen.go b/automation/service/workflow_actions.gen.go index b7820d331..bc709eb89 100644 --- a/automation/service/workflow_actions.gen.go +++ b/automation/service/workflow_actions.gen.go @@ -700,6 +700,38 @@ func WorkflowErrNotAllowedToUndelete(mm ...*workflowActionProps) *errors.Error { return e } +// WorkflowErrNotAllowedToExecute returns "automation:workflow.notAllowedToExecute" as *errors.Error +// +// +// This function is auto-generated. +// +func WorkflowErrNotAllowedToExecute(mm ...*workflowActionProps) *errors.Error { + var p = &workflowActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to execute this workflow", nil), + + errors.Meta("type", "notAllowedToExecute"), + errors.Meta("resource", "automation:workflow"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(workflowLogMetaKey{}, "failed to execute {workflow}; insufficient permissions"), + errors.Meta(workflowPropsMetaKey{}, p), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + // WorkflowErrHandleNotUnique returns "automation:workflow.handleNotUnique" as *errors.Error // // diff --git a/automation/service/workflow_actions.yaml b/automation/service/workflow_actions.yaml index 1eae4f4dd..db8038924 100644 --- a/automation/service/workflow_actions.yaml +++ b/automation/service/workflow_actions.yaml @@ -86,6 +86,10 @@ errors: message: "not allowed to undelete this workflow" log: "failed to undelete {workflow}; insufficient permissions" + - error: notAllowedToExecute + message: "not allowed to execute this workflow" + log: "failed to execute {workflow}; insufficient permissions" + - error: handleNotUnique message: "workflow handle not unique" log: "duplicate handle used for workflow ({workflow})"