Add trigger management access-control, disable direct trigger management
This commit is contained in:
@@ -114,7 +114,7 @@ func (svc accessControl) CanDeleteRecord(ctx context.Context, r *types.Module) b
|
||||
}
|
||||
|
||||
func (svc accessControl) CanManageAutomationTriggersOnModule(ctx context.Context, r *types.Module) bool {
|
||||
return svc.can(ctx, r, "automation-trigger.manage", permissions.Allowed)
|
||||
return svc.can(ctx, r, "automation-trigger.manage")
|
||||
}
|
||||
|
||||
func (svc accessControl) CanCreateChart(ctx context.Context, r *types.Namespace) bool {
|
||||
|
||||
@@ -16,6 +16,7 @@ type (
|
||||
logger *zap.Logger
|
||||
scriptManager automationScriptManager
|
||||
ns NamespaceService
|
||||
mod ModuleService
|
||||
ac automationScriptAccessController
|
||||
}
|
||||
|
||||
@@ -37,6 +38,8 @@ type (
|
||||
CanReadAutomationScript(context.Context, *automation.Script) bool
|
||||
CanUpdateAutomationScript(context.Context, *automation.Script) bool
|
||||
CanDeleteAutomationScript(context.Context, *automation.Script) bool
|
||||
|
||||
CanManageAutomationTriggersOnModule(context.Context, *types.Module) bool
|
||||
}
|
||||
|
||||
automationScriptNamespaceFinder interface {
|
||||
@@ -49,7 +52,8 @@ func AutomationScript(sm automationScriptManager) automationScript {
|
||||
scriptManager: sm,
|
||||
logger: DefaultLogger.Named("automation-script"),
|
||||
ac: DefaultAccessControl,
|
||||
ns: Namespace(),
|
||||
mod: DefaultModule,
|
||||
ns: DefaultNamespace,
|
||||
}
|
||||
|
||||
return svc
|
||||
@@ -78,9 +82,13 @@ func (svc automationScript) Find(ctx context.Context, namespaceID uint64, f auto
|
||||
}
|
||||
|
||||
func (svc automationScript) Create(ctx context.Context, namespaceID uint64, mod *automation.Script) (err error) {
|
||||
if ns, _, err := svc.loadCombo(ctx, namespaceID, 0); err != nil {
|
||||
var ns *types.Namespace
|
||||
|
||||
if ns, _, err = svc.loadCombo(ctx, namespaceID, 0); err != nil {
|
||||
return err
|
||||
} else if !svc.ac.CanCreateAutomationScript(ctx, ns) {
|
||||
}
|
||||
|
||||
if !svc.ac.CanCreateAutomationScript(ctx, ns) {
|
||||
return ErrNoCreatePermissions.withStack()
|
||||
}
|
||||
|
||||
@@ -90,35 +98,57 @@ func (svc automationScript) Create(ctx context.Context, namespaceID uint64, mod
|
||||
}
|
||||
}
|
||||
|
||||
err = mod.Triggers().Walk(func(t *automation.Trigger) error {
|
||||
return svc.isValidTrigger(ctx, t, namespaceID)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return svc.scriptManager.CreateScript(ctx, mod)
|
||||
}
|
||||
|
||||
func (svc automationScript) Update(ctx context.Context, namespaceID uint64, mod *automation.Script) (err error) {
|
||||
if _, s, err := svc.loadCombo(ctx, namespaceID, mod.ID); err != nil {
|
||||
var s *automation.Script
|
||||
|
||||
if _, s, err = svc.loadCombo(ctx, namespaceID, mod.ID); err != nil {
|
||||
return err
|
||||
} else if !svc.ac.CanUpdateAutomationScript(ctx, s) {
|
||||
return ErrNoCreatePermissions.withStack()
|
||||
} else {
|
||||
// Users need to have grant privileges to
|
||||
// set script runner
|
||||
if mod.RunAs != s.RunAs {
|
||||
if !svc.ac.CanGrant(ctx) {
|
||||
return ErrNoGrantPermissions
|
||||
}
|
||||
}
|
||||
|
||||
s.Name = mod.Name
|
||||
s.SourceRef = mod.SourceRef
|
||||
s.Source = mod.Source
|
||||
s.Async = mod.Async
|
||||
s.RunAs = mod.RunAs
|
||||
s.RunInUA = mod.RunInUA
|
||||
s.Timeout = mod.Timeout
|
||||
s.Critical = mod.Critical
|
||||
s.Enabled = mod.Enabled
|
||||
|
||||
return svc.scriptManager.UpdateScript(ctx, s)
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateAutomationScript(ctx, s) {
|
||||
return ErrNoCreatePermissions.withStack()
|
||||
}
|
||||
|
||||
// Users need to have grant privileges to
|
||||
// set script runner
|
||||
if mod.RunAs != s.RunAs {
|
||||
if !svc.ac.CanGrant(ctx) {
|
||||
return ErrNoGrantPermissions
|
||||
}
|
||||
}
|
||||
|
||||
s.Name = mod.Name
|
||||
s.SourceRef = mod.SourceRef
|
||||
s.Source = mod.Source
|
||||
s.Async = mod.Async
|
||||
s.RunAs = mod.RunAs
|
||||
s.RunInUA = mod.RunInUA
|
||||
s.Timeout = mod.Timeout
|
||||
s.Critical = mod.Critical
|
||||
s.Enabled = mod.Enabled
|
||||
|
||||
err = mod.Triggers().Walk(func(t *automation.Trigger) error {
|
||||
return svc.isValidTrigger(ctx, t, namespaceID)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.AddTrigger(automation.STMS_UPDATE, mod.Triggers()...)
|
||||
|
||||
return svc.scriptManager.UpdateScript(ctx, s)
|
||||
}
|
||||
|
||||
func (svc automationScript) Delete(ctx context.Context, namespaceID, scriptID uint64) (err error) {
|
||||
@@ -155,3 +185,36 @@ func (svc automationScript) loadCombo(ctx context.Context, namespaceID, scriptID
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc automationScript) isValidTrigger(ctx context.Context, t *automation.Trigger, namespaceID uint64) error {
|
||||
if t.Resource != "compose:record" {
|
||||
// Accepting only compose:record resources
|
||||
return automation.ErrAutomationTriggerInvalidResource
|
||||
}
|
||||
|
||||
if t.IsDeferred() {
|
||||
// @todo validate condition for deferred triggers
|
||||
return nil
|
||||
}
|
||||
|
||||
switch t.Event {
|
||||
case "manual",
|
||||
"beforeCreate", "beforeUpdate", "beforeDelete",
|
||||
"afterCreate", "afterUpdate", "afterDelete":
|
||||
var moduleID = t.Uint64Condition()
|
||||
|
||||
if t.Event != "manual" && moduleID == 0 {
|
||||
return automation.ErrAutomationTriggerInvalidCondition
|
||||
}
|
||||
|
||||
if m, err := svc.mod.With(ctx).FindByID(namespaceID, moduleID); err != nil {
|
||||
return err
|
||||
} else if !svc.ac.CanManageAutomationTriggersOnModule(ctx, m) {
|
||||
return ErrNoTriggerManagementPermissions
|
||||
}
|
||||
default:
|
||||
return automation.ErrAutomationTriggerInvalidEvent
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,16 +9,17 @@ type (
|
||||
)
|
||||
|
||||
const (
|
||||
ErrInvalidID serviceError = "InvalidID"
|
||||
ErrStaleData serviceError = "StaleData"
|
||||
ErrNoGrantPermissions serviceError = "NoGrantPermissions"
|
||||
ErrNoCreatePermissions serviceError = "NoCreatePermissions"
|
||||
ErrNoReadPermissions serviceError = "NoReadPermissions"
|
||||
ErrNoUpdatePermissions serviceError = "NoUpdatePermissions"
|
||||
ErrNoDeletePermissions serviceError = "NoDeletePermissions"
|
||||
ErrNamespaceRequired serviceError = "NamespaceRequired"
|
||||
ErrModulePageExists serviceError = "ModulePageExists"
|
||||
ErrNotImplemented serviceError = "NotImplemented"
|
||||
ErrInvalidID serviceError = "InvalidID"
|
||||
ErrStaleData serviceError = "StaleData"
|
||||
ErrNoGrantPermissions serviceError = "NoGrantPermissions"
|
||||
ErrNoCreatePermissions serviceError = "NoCreatePermissions"
|
||||
ErrNoReadPermissions serviceError = "NoReadPermissions"
|
||||
ErrNoUpdatePermissions serviceError = "NoUpdatePermissions"
|
||||
ErrNoDeletePermissions serviceError = "NoDeletePermissions"
|
||||
ErrNoTriggerManagementPermissions serviceError = "NoTriggerManagementPermissions"
|
||||
ErrNamespaceRequired serviceError = "NamespaceRequired"
|
||||
ErrModulePageExists serviceError = "ModulePageExists"
|
||||
ErrNotImplemented serviceError = "NotImplemented"
|
||||
)
|
||||
|
||||
func (e serviceError) Error() string {
|
||||
|
||||
@@ -77,6 +77,8 @@ func Init(ctx context.Context, log *zap.Logger, c Config) (err error) {
|
||||
permissions.Repository(db, "compose_permission_rules"))
|
||||
|
||||
DefaultAccessControl = AccessControl(DefaultPermissions)
|
||||
DefaultNamespace = Namespace()
|
||||
DefaultModule = Module()
|
||||
|
||||
{
|
||||
systemClientConn, err := NewSystemGRPCClient(ctx, c.GRPCClientSystem, DefaultLogger)
|
||||
@@ -128,10 +130,7 @@ func Init(ctx context.Context, log *zap.Logger, c Config) (err error) {
|
||||
)
|
||||
}
|
||||
|
||||
// Compose internals:
|
||||
DefaultNamespace = Namespace()
|
||||
DefaultRecord = Record()
|
||||
DefaultModule = Module()
|
||||
DefaultPage = Page()
|
||||
DefaultChart = Chart()
|
||||
DefaultNotification = Notification()
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/titpetric/factory/resputil"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/internal/service"
|
||||
"github.com/cortezaproject/corteza-server/compose/rest/request"
|
||||
@@ -78,22 +77,25 @@ func (ctrl AutomationTrigger) List(ctx context.Context, r *request.AutomationTri
|
||||
}
|
||||
|
||||
func (ctrl AutomationTrigger) Create(ctx context.Context, r *request.AutomationTriggerCreate) (interface{}, error) {
|
||||
s, _, err := ctrl.loadCombo(ctx, r.NamespaceID, r.ScriptID, 0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can not create trigger")
|
||||
}
|
||||
// @todo trigger management is currently done through automation-script endpoints
|
||||
return nil, errors.New("direct trigger management disabled")
|
||||
|
||||
var (
|
||||
t = &automation.Trigger{
|
||||
Event: r.Event,
|
||||
Resource: r.Resource,
|
||||
Condition: r.Condition,
|
||||
ScriptID: s.ID,
|
||||
Enabled: r.Enabled,
|
||||
}
|
||||
)
|
||||
|
||||
return ctrl.makePayload(ctx, t, ctrl.triggers.Create(ctx, s, t))
|
||||
// s, _, err := ctrl.loadCombo(ctx, r.NamespaceID, r.ScriptID, 0)
|
||||
// if err != nil {
|
||||
// return nil, errors.Wrap(err, "can not create trigger")
|
||||
// }
|
||||
//
|
||||
// var (
|
||||
// t = &automation.Trigger{
|
||||
// Event: r.Event,
|
||||
// Resource: r.Resource,
|
||||
// Condition: r.Condition,
|
||||
// ScriptID: s.ID,
|
||||
// Enabled: r.Enabled,
|
||||
// }
|
||||
// )
|
||||
//
|
||||
// return ctrl.makePayload(ctx, t, ctrl.triggers.Create(ctx, s, t))
|
||||
}
|
||||
|
||||
func (ctrl AutomationTrigger) Read(ctx context.Context, r *request.AutomationTriggerRead) (interface{}, error) {
|
||||
@@ -106,27 +108,33 @@ func (ctrl AutomationTrigger) Read(ctx context.Context, r *request.AutomationTri
|
||||
}
|
||||
|
||||
func (ctrl AutomationTrigger) Update(ctx context.Context, r *request.AutomationTriggerUpdate) (interface{}, error) {
|
||||
s, t, err := ctrl.loadCombo(ctx, r.NamespaceID, r.ScriptID, r.TriggerID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can not update trigger")
|
||||
}
|
||||
// @todo trigger management is currently done through automation-script endpoints
|
||||
return nil, errors.New("direct trigger management disabled")
|
||||
|
||||
t.Event = r.Event
|
||||
t.Resource = r.Resource
|
||||
t.Condition = r.Condition
|
||||
t.ScriptID = r.ScriptID
|
||||
t.Enabled = r.Enabled
|
||||
|
||||
return ctrl.makePayload(ctx, t, ctrl.triggers.Update(ctx, s, t))
|
||||
// s, t, err := ctrl.loadCombo(ctx, r.NamespaceID, r.ScriptID, r.TriggerID)
|
||||
// if err != nil {
|
||||
// return nil, errors.Wrap(err, "can not update trigger")
|
||||
// }
|
||||
//
|
||||
// t.Event = r.Event
|
||||
// t.Resource = r.Resource
|
||||
// t.Condition = r.Condition
|
||||
// t.ScriptID = r.ScriptID
|
||||
// t.Enabled = r.Enabled
|
||||
//
|
||||
// return ctrl.makePayload(ctx, t, ctrl.triggers.Update(ctx, s, t))
|
||||
}
|
||||
|
||||
func (ctrl AutomationTrigger) Delete(ctx context.Context, r *request.AutomationTriggerDelete) (interface{}, error) {
|
||||
trigger, err := ctrl.triggers.FindByID(ctx, r.TriggerID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can not delete trigger")
|
||||
}
|
||||
// @todo trigger management is currently done through automation-script endpoints
|
||||
return nil, errors.New("direct trigger management disabled")
|
||||
|
||||
return resputil.OK(), ctrl.triggers.Delete(ctx, trigger)
|
||||
// trigger, err := ctrl.triggers.FindByID(ctx, r.TriggerID)
|
||||
// if err != nil {
|
||||
// return nil, errors.Wrap(err, "can not delete trigger")
|
||||
// }
|
||||
//
|
||||
// return resputil.OK(), ctrl.triggers.Delete(ctx, trigger)
|
||||
}
|
||||
|
||||
func (ctrl AutomationTrigger) loadCombo(ctx context.Context, namespaceID, scriptID, triggerID uint64) (s *automation.Script, t *automation.Trigger, err error) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package automation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/rh"
|
||||
@@ -66,6 +68,12 @@ const (
|
||||
EVENT_TYPE_DEFERRED = "deferred"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAutomationTriggerInvalidResource = errors.New("AutomationTriggerInvalidResource")
|
||||
ErrAutomationTriggerInvalidCondition = errors.New("AutomationTriggerInvalidCondition")
|
||||
ErrAutomationTriggerInvalidEvent = errors.New("AutomationTriggerInvalidEvent")
|
||||
)
|
||||
|
||||
// IsValid checks if trigger is enabled and not deleted
|
||||
func (t *Trigger) IsValid() bool {
|
||||
return t != nil && t.Enabled && t.DeletedAt == nil
|
||||
@@ -81,6 +89,14 @@ func (t Trigger) IsDeferred() bool {
|
||||
return t.Event == EVENT_TYPE_DEFERRED
|
||||
}
|
||||
|
||||
// Uint64Condition converts condition to uint64
|
||||
//
|
||||
// Errors are ignored
|
||||
func (t Trigger) Uint64Condition() (o uint64) {
|
||||
o, _ = strconv.ParseUint(t.Condition, 10, 64)
|
||||
return
|
||||
}
|
||||
|
||||
// HasMatch checks if any og the triggers in a set matches the given parameters
|
||||
func (set TriggerSet) HasMatch(m Trigger, ff ...TriggerConditionChecker) bool {
|
||||
withTriggers:
|
||||
|
||||
Reference in New Issue
Block a user