Make automation S&T namespace bound

This commit is contained in:
Denis Arh
2019-08-26 08:39:33 +02:00
parent bf166323e7
commit 91633e9695
9 changed files with 169 additions and 111 deletions
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
ALTER TABLE `compose_automation_script`
ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,
ADD INDEX (`rel_namespace`);
UPDATE `compose_automation_script` SET `rel_namespace` = (SELECT MIN(id) FROM compose_namespace);
ALTER TABLE `compose_automation_script`
ADD CONSTRAINT `compose_automation_script_namespace`
FOREIGN KEY (`rel_namespace`)
REFERENCES `compose_namespace` (`id`);
+5 -41
View File
@@ -18,6 +18,7 @@ type (
ns NamespaceService
mod ModuleService
ac automationScriptAccessController
trg automationTrigger
}
automationScriptManager interface {
@@ -38,12 +39,6 @@ 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 {
FindByID(uint64) (*types.Namespace, error)
}
)
@@ -54,6 +49,7 @@ func AutomationScript(sm automationScriptManager) automationScript {
ac: DefaultAccessControl,
mod: DefaultModule,
ns: DefaultNamespace,
trg: DefaultAutomationTriggerManager,
}
return svc
@@ -72,6 +68,7 @@ func (svc automationScript) Find(ctx context.Context, namespaceID uint64, f auto
return nil, f, err
}
f.NamespaceID = namespaceID
f.AccessCheck = permissions.InitAccessCheckFilter(
"read",
auth.GetIdentityFromContext(ctx).Roles(),
@@ -99,7 +96,7 @@ 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)
return svc.trg.isValid(ctx, mod, t)
})
if err != nil {
@@ -139,7 +136,7 @@ func (svc automationScript) Update(ctx context.Context, namespaceID uint64, mod
s.Enabled = mod.Enabled
err = mod.Triggers().Walk(func(t *automation.Trigger) error {
return svc.isValidTrigger(ctx, t, namespaceID)
return svc.trg.isValid(ctx, mod, t)
})
if err != nil {
@@ -185,36 +182,3 @@ 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
}
+77 -4
View File
@@ -5,6 +5,7 @@ import (
"go.uber.org/zap"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/automation"
)
@@ -13,6 +14,9 @@ type (
automationTrigger struct {
logger *zap.Logger
triggerManager automationTriggerManager
mod ModuleService
ac automationTriggerAccessController
}
automationTriggerManager interface {
@@ -22,12 +26,20 @@ type (
UpdateTrigger(context.Context, *automation.Script, *automation.Trigger) error
DeleteTrigger(context.Context, *automation.Trigger) error
}
automationTriggerAccessController interface {
CanUpdateAutomationScript(context.Context, *automation.Script) bool
CanManageAutomationTriggersOnModule(context.Context, *types.Module) bool
}
)
func AutomationTrigger(tm automationTriggerManager) automationTrigger {
var svc = automationTrigger{
triggerManager: tm,
logger: DefaultLogger.Named("automation-trigger"),
ac: DefaultAccessControl,
mod: DefaultModule,
}
return svc
@@ -44,16 +56,77 @@ func (svc automationTrigger) Find(ctx context.Context, f automation.TriggerFilte
}
func (svc automationTrigger) Create(ctx context.Context, s *automation.Script, t *automation.Trigger) (err error) {
// @todo security check - can user create trigger on this specific resource
if err = svc.isValid(ctx, s, t); err != nil {
return
}
if !svc.ac.CanUpdateAutomationScript(ctx, s) {
return ErrNoTriggerManagementPermissions
}
return svc.triggerManager.CreateTrigger(ctx, s, t)
}
func (svc automationTrigger) Update(ctx context.Context, s *automation.Script, t *automation.Trigger) (err error) {
// @todo security check - can user create update triggers on this specific resource
if err = svc.isValid(ctx, s, t); err != nil {
return
}
if !svc.ac.CanUpdateAutomationScript(ctx, s) {
return ErrNoTriggerManagementPermissions
}
return svc.triggerManager.UpdateTrigger(ctx, s, t)
}
func (svc automationTrigger) Delete(ctx context.Context, t *automation.Trigger) (err error) {
// @todo security check - can user create delete triggers on this specific resource
func (svc automationTrigger) Delete(ctx context.Context, s *automation.Script, t *automation.Trigger) (err error) {
if err = svc.isValid(ctx, s, t); err != nil {
return
}
if !svc.ac.CanUpdateAutomationScript(ctx, s) {
return ErrNoTriggerManagementPermissions
}
return svc.triggerManager.DeleteTrigger(ctx, t)
}
// Validates trigger (in compose context)
func (svc automationTrigger) isValid(ctx context.Context, s *automation.Script, t *automation.Trigger) error {
if !t.Enabled {
return nil
}
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 moduleID > 0 {
if m, err := svc.mod.With(ctx).FindByID(s.NamespaceID, moduleID); err != nil {
return err
} else if !svc.ac.CanManageAutomationTriggersOnModule(ctx, m) {
return ErrNoTriggerManagementPermissions
}
}
default:
return automation.ErrAutomationTriggerInvalidEvent
}
return nil
}
+1 -1
View File
@@ -102,8 +102,8 @@ func Init(ctx context.Context, log *zap.Logger, c Config) (err error) {
})
// Pass automation manager to
DefaultAutomationScriptManager = AutomationScript(ias)
DefaultAutomationTriggerManager = AutomationTrigger(ias)
DefaultAutomationScriptManager = AutomationScript(ias)
{
var scriptRunnerClient corredor.ScriptRunnerClient
+26 -22
View File
@@ -96,9 +96,7 @@ func (AutomationScript) New() *AutomationScript {
func (ctrl AutomationScript) List(ctx context.Context, r *request.AutomationScriptList) (interface{}, error) {
set, filter, err := ctrl.scripts.Find(ctx, r.NamespaceID, automation.ScriptFilter{
// @todo namespace filtering
// Might be a bit tricky as scripts themselves not know about namespaces
// Namespace: r.NamespaceID
NamespaceID: r.NamespaceID,
Query: r.Query,
Resource: r.Resource,
@@ -113,15 +111,16 @@ func (ctrl AutomationScript) List(ctx context.Context, r *request.AutomationScri
func (ctrl AutomationScript) Create(ctx context.Context, r *request.AutomationScriptCreate) (interface{}, error) {
var (
script = &automation.Script{
Name: r.Name,
SourceRef: r.SourceRef,
Source: r.Source,
Async: r.Async,
RunAs: r.RunAs,
RunInUA: r.RunInUA,
Timeout: r.Timeout,
Critical: r.Critical,
Enabled: r.Enabled,
NamespaceID: r.NamespaceID,
Name: r.Name,
SourceRef: r.SourceRef,
Source: r.Source,
Async: r.Async,
RunAs: r.RunAs,
RunInUA: r.RunInUA,
Timeout: r.Timeout,
Critical: r.Critical,
Enabled: r.Enabled,
}
)
@@ -137,16 +136,17 @@ func (ctrl AutomationScript) Read(ctx context.Context, r *request.AutomationScri
func (ctrl AutomationScript) Update(ctx context.Context, r *request.AutomationScriptUpdate) (interface{}, error) {
mod := &automation.Script{
ID: r.ScriptID,
Name: r.Name,
SourceRef: r.SourceRef,
Source: r.Source,
Async: r.Async,
RunAs: r.RunAs,
RunInUA: r.RunInUA,
Timeout: r.Timeout,
Critical: r.Critical,
Enabled: r.Enabled,
ID: r.ScriptID,
NamespaceID: r.NamespaceID,
Name: r.Name,
SourceRef: r.SourceRef,
Source: r.Source,
Async: r.Async,
RunAs: r.RunAs,
RunInUA: r.RunInUA,
Timeout: r.Timeout,
Critical: r.Critical,
Enabled: r.Enabled,
}
mod.AddTrigger(automation.STMS_UPDATE, r.Triggers...)
@@ -166,6 +166,10 @@ func (ctrl AutomationScript) Runnable(ctx context.Context, r *request.Automation
)
return rval, ctrl.runner.UserScripts(ctx).Walk(func(script *automation.Script) error {
if script.NamespaceID != r.NamespaceID {
return nil
}
// @todo filter out all modules (by t.Condition) we do not have access to
out := &automationScriptRunnable{
ScriptID: script.ID,
+38 -41
View File
@@ -4,7 +4,9 @@ import (
"context"
"github.com/pkg/errors"
"github.com/titpetric/factory/resputil"
"github.com/cortezaproject/corteza-server/compose/internal/repository"
"github.com/cortezaproject/corteza-server/compose/internal/service"
"github.com/cortezaproject/corteza-server/compose/rest/request"
"github.com/cortezaproject/corteza-server/pkg/automation"
@@ -37,7 +39,7 @@ type (
Find(context.Context, automation.TriggerFilter) (automation.TriggerSet, automation.TriggerFilter, error)
Create(context.Context, *automation.Script, *automation.Trigger) error
Update(context.Context, *automation.Script, *automation.Trigger) error
Delete(context.Context, *automation.Trigger) error
Delete(context.Context, *automation.Script, *automation.Trigger) error
}
automationScriptFinderService interface {
@@ -77,25 +79,22 @@ func (ctrl AutomationTrigger) List(ctx context.Context, r *request.AutomationTri
}
func (ctrl AutomationTrigger) Create(ctx context.Context, r *request.AutomationTriggerCreate) (interface{}, error) {
// @todo trigger management is currently done through automation-script endpoints
return nil, errors.New("direct trigger management disabled")
s, _, err := ctrl.loadCombo(ctx, r.NamespaceID, r.ScriptID, 0)
if err != nil {
return nil, errors.Wrap(err, "can not create trigger")
}
// 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))
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) {
@@ -108,33 +107,27 @@ func (ctrl AutomationTrigger) Read(ctx context.Context, r *request.AutomationTri
}
func (ctrl AutomationTrigger) Update(ctx context.Context, r *request.AutomationTriggerUpdate) (interface{}, error) {
// @todo trigger management is currently done through automation-script endpoints
return nil, errors.New("direct trigger management disabled")
s, t, err := ctrl.loadCombo(ctx, r.NamespaceID, r.ScriptID, r.TriggerID)
if err != nil {
return nil, errors.Wrap(err, "can not update trigger")
}
// 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))
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) {
// @todo trigger management is currently done through automation-script endpoints
return nil, errors.New("direct trigger management disabled")
s, t, err := ctrl.loadCombo(ctx, r.NamespaceID, r.ScriptID, r.TriggerID)
if err != nil {
return nil, errors.Wrap(err, "can not update 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)
return resputil.OK(), ctrl.triggers.Delete(ctx, s, t)
}
func (ctrl AutomationTrigger) loadCombo(ctx context.Context, namespaceID, scriptID, triggerID uint64) (s *automation.Script, t *automation.Trigger, err error) {
@@ -145,6 +138,10 @@ func (ctrl AutomationTrigger) loadCombo(ctx context.Context, namespaceID, script
if scriptID > 0 {
s, err = ctrl.scripts.FindByID(ctx, namespaceID, scriptID)
if err != nil && s.NamespaceID != namespaceID {
err = repository.ErrNamespaceNotFound
}
return
}
+4
View File
@@ -14,6 +14,8 @@ type (
Script struct {
ID uint64 `json:"scriptID,string" db:"id"`
NamespaceID uint64 `json:"namespaceID,string" db:"rel_namespace"`
Name string `json:"name" db:"name"`
// (URL) Where did we get the source from?
@@ -67,6 +69,8 @@ type (
}
ScriptFilter struct {
NamespaceID uint64 `json:"namespaceID,string"`
Query string
Resource string
IncDeleted bool `json:"incDeleted"`
+6 -1
View File
@@ -32,6 +32,7 @@ func (r scriptRepository) table() string {
func (r scriptRepository) columns() []string {
return []string{
"id",
"rel_namespace",
"name",
"source_ref",
"source",
@@ -74,10 +75,14 @@ func (r *scriptRepository) find(db *factory.DB, filter ScriptFilter) (set Script
query := r.query()
if !filter.IncDeleted {
if !f.IncDeleted {
query = query.Where("deleted_at IS NULL")
}
if f.NamespaceID > 0 {
query = query.Where("rel_namespace = ?", f.NamespaceID)
}
if f.Query != "" {
q := "%" + f.Query + "%"
query = query.Where("name like ?", q)