snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s

This commit is contained in:
upstream-mirror
2026-09-18 01:22:23 +05:00
commit 7e385fc126
8391 changed files with 1766236 additions and 0 deletions

View File

@@ -0,0 +1,444 @@
package automation
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// system/automation/actionlog_handler.yaml
import (
"context"
atypes "github.com/cortezaproject/corteza/server/automation/types"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/wfexec"
"time"
)
var _ wfexec.ExecResponse
type (
actionlogHandlerRegistry interface {
AddFunctions(ff ...*atypes.Function)
Type(ref string) expr.Type
}
)
func (h actionlogHandler) register() {
h.reg.AddFunctions(
h.Search(),
h.Each(),
h.Record(),
)
}
type (
actionlogSearchArgs struct {
hasFromTimestamp bool
FromTimestamp *time.Time
hasToTimestamp bool
ToTimestamp *time.Time
hasBeforeActionID bool
BeforeActionID uint64
hasActorID bool
ActorID uint64
hasOrigin bool
Origin string
hasResource bool
Resource string
hasAction bool
Action string
hasLimit bool
Limit uint64
}
actionlogSearchResults struct {
Actions []*actionlog.Action
}
)
// Search function Action log search
//
// expects implementation of search function:
//
// func (h actionlogHandler) search(ctx context.Context, args *actionlogSearchArgs) (results *actionlogSearchResults, err error) {
// return
// }
func (h actionlogHandler) Search() *atypes.Function {
return &atypes.Function{
Ref: "actionlogSearch",
Kind: "function",
Labels: map[string]string{"actionlog": "step,workflow"},
Meta: &atypes.FunctionMeta{
Short: "Action log search",
},
Parameters: []*atypes.Param{
{
Name: "fromTimestamp",
Types: []string{"DateTime"},
Meta: &atypes.ParamMeta{
Label: "From timestamp",
},
},
{
Name: "toTimestamp",
Types: []string{"DateTime"},
Meta: &atypes.ParamMeta{
Label: "To timestamp",
},
},
{
Name: "beforeActionID",
Types: []string{"ID"},
Meta: &atypes.ParamMeta{
Label: "Before action ID",
},
},
{
Name: "actorID",
Types: []string{"ID"},
Meta: &atypes.ParamMeta{
Label: "Actor ID",
},
},
{
Name: "origin",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Origin",
},
},
{
Name: "resource",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Resource",
},
},
{
Name: "action",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Action",
},
},
{
Name: "limit",
Types: []string{"UnsignedInteger"},
Meta: &atypes.ParamMeta{
Label: "Limit",
},
},
},
Results: []*atypes.Param{
{
Name: "actions",
Types: []string{"Action"},
IsArray: true,
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &actionlogSearchArgs{
hasFromTimestamp: in.Has("fromTimestamp"),
hasToTimestamp: in.Has("toTimestamp"),
hasBeforeActionID: in.Has("beforeActionID"),
hasActorID: in.Has("actorID"),
hasOrigin: in.Has("origin"),
hasResource: in.Has("resource"),
hasAction: in.Has("action"),
hasLimit: in.Has("limit"),
}
)
if err = in.Decode(args); err != nil {
return
}
var results *actionlogSearchResults
if results, err = h.search(ctx, args); err != nil {
return
}
out = &expr.Vars{}
{
// converting results.Actions (*actionlog.Action) to Array (of Action)
var (
tval expr.TypedValue
tarr = make([]expr.TypedValue, len(results.Actions))
)
for i := range results.Actions {
if tarr[i], err = h.reg.Type("Action").Cast(results.Actions[i]); err != nil {
return
}
}
if tval, err = expr.NewArray(tarr); err != nil {
return
} else if err = expr.Assign(out, "actions", tval); err != nil {
return
}
}
return
},
}
}
type (
actionlogEachArgs struct {
hasFromTimestamp bool
FromTimestamp *time.Time
hasToTimestamp bool
ToTimestamp *time.Time
hasBeforeActionID bool
BeforeActionID uint64
hasActorID bool
ActorID uint64
hasOrigin bool
Origin string
hasResource bool
Resource string
hasAction bool
Action string
hasLimit bool
Limit uint64
}
actionlogEachResults struct {
Action *actionlog.Action
}
)
// Each function Action log
//
// expects implementation of each function:
//
// func (h actionlogHandler) each(ctx context.Context, args *actionlogEachArgs) (results *actionlogEachResults, err error) {
// return
// }
func (h actionlogHandler) Each() *atypes.Function {
return &atypes.Function{
Ref: "actionlogEach",
Kind: "iterator",
Labels: map[string]string{"actionlog": "step,workflow"},
Meta: &atypes.FunctionMeta{
Short: "Action log",
},
Parameters: []*atypes.Param{
{
Name: "fromTimestamp",
Types: []string{"DateTime"},
Meta: &atypes.ParamMeta{
Label: "From timestamp",
},
},
{
Name: "toTimestamp",
Types: []string{"DateTime"},
Meta: &atypes.ParamMeta{
Label: "To timestamp",
},
},
{
Name: "beforeActionID",
Types: []string{"ID"},
Meta: &atypes.ParamMeta{
Label: "Before action ID",
},
},
{
Name: "actorID",
Types: []string{"ID"},
Meta: &atypes.ParamMeta{
Label: "Actor ID",
},
},
{
Name: "origin",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Origin",
},
},
{
Name: "resource",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Resource",
},
},
{
Name: "action",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Action",
},
},
{
Name: "limit",
Types: []string{"UnsignedInteger"},
Meta: &atypes.ParamMeta{
Label: "Limit",
},
},
},
Results: []*atypes.Param{
{
Name: "action",
Types: []string{"Action"},
},
},
Iterator: func(ctx context.Context, in *expr.Vars) (out wfexec.IteratorHandler, err error) {
var (
args = &actionlogEachArgs{
hasFromTimestamp: in.Has("fromTimestamp"),
hasToTimestamp: in.Has("toTimestamp"),
hasBeforeActionID: in.Has("beforeActionID"),
hasActorID: in.Has("actorID"),
hasOrigin: in.Has("origin"),
hasResource: in.Has("resource"),
hasAction: in.Has("action"),
hasLimit: in.Has("limit"),
}
)
if err = in.Decode(args); err != nil {
return
}
return h.each(ctx, args)
},
}
}
type (
actionlogRecordArgs struct {
hasAction bool
Action string
hasResource bool
Resource string
hasError bool
Error string
hasSeverity bool
Severity string
hasDescription bool
Description string
hasMeta bool
Meta *expr.Vars
}
)
// Record function Record action into action log
//
// expects implementation of record function:
//
// func (h actionlogHandler) record(ctx context.Context, args *actionlogRecordArgs) (err error) {
// return
// }
func (h actionlogHandler) Record() *atypes.Function {
return &atypes.Function{
Ref: "actionlogRecord",
Kind: "function",
Labels: map[string]string{"actionlog": "step,workflow"},
Meta: &atypes.FunctionMeta{
Short: "Record action into action log",
},
Parameters: []*atypes.Param{
{
Name: "action",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Action",
},
},
{
Name: "resource",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Resource",
},
},
{
Name: "error",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Error",
},
},
{
Name: "severity",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Severity",
Visual: map[string]interface{}{"options": []interface{}{"emergency", "alert", "critical", "err", "warning", "notice", "info", "debug"}},
},
},
{
Name: "description",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Description",
},
},
{
Name: "meta",
Types: []string{"Vars"},
Meta: &atypes.ParamMeta{
Label: "Meta",
},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &actionlogRecordArgs{
hasAction: in.Has("action"),
hasResource: in.Has("resource"),
hasError: in.Has("error"),
hasSeverity: in.Has("severity"),
hasDescription: in.Has("description"),
hasMeta: in.Has("meta"),
}
)
if err = in.Decode(args); err != nil {
return
}
return out, h.record(ctx, args)
},
}
}

View File

@@ -0,0 +1,154 @@
package automation
import (
"context"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
. "github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/pkg/wfexec"
)
type (
actionlogHandler struct {
reg actionlogHandlerRegistry
svc actionlog.Recorder
}
actionSetIterator struct {
// Item buffer, current item pointer, and total items traversed
ptr uint
buffer actionlog.ActionSet
total uint
// When filter limit is set, this constraints it
iterLimit uint
useIterLimit bool
// Item loader for additional chunks
filter actionlog.Filter
loader func() error
}
)
func ActionlogHandler(reg actionlogHandlerRegistry, svc actionlog.Recorder) *actionlogHandler {
h := &actionlogHandler{
reg: reg,
svc: svc,
}
h.register()
return h
}
func (h actionlogHandler) search(ctx context.Context, args *actionlogSearchArgs) (results *actionlogSearchResults, err error) {
results = &actionlogSearchResults{}
var (
f = actionlog.Filter{
FromTimestamp: args.FromTimestamp,
ToTimestamp: args.ToTimestamp,
BeforeActionID: args.BeforeActionID,
Origin: args.Origin,
Resource: args.Resource,
Action: args.Action,
Limit: uint(args.Limit),
}
)
if args.ActorID > 0 {
f.ActorID = id.Strings(args.ActorID)
}
results.Actions, _, err = h.svc.Find(ctx, f)
return
}
func (h actionlogHandler) each(ctx context.Context, args *actionlogEachArgs) (out wfexec.IteratorHandler, err error) {
var (
i = &actionSetIterator{}
f = actionlog.Filter{
FromTimestamp: args.FromTimestamp,
ToTimestamp: args.ToTimestamp,
BeforeActionID: args.BeforeActionID,
Origin: args.Origin,
Resource: args.Resource,
Action: args.Action,
}
)
if args.ActorID > 0 {
f.ActorID = id.Strings(args.ActorID)
}
if args.hasLimit {
i.useIterLimit = true
i.iterLimit = uint(args.Limit)
f.Limit = uint(args.Limit)
if args.Limit > uint64(wfexec.MaxIteratorBufferSize) {
f.Limit = wfexec.MaxIteratorBufferSize
}
i.iterLimit = uint(args.Limit)
} else {
f.Limit = wfexec.MaxIteratorBufferSize
}
i.filter = f
i.loader = func() (err error) {
i.total += i.ptr
i.ptr = 0
if len(i.buffer) > 0 {
i.filter.BeforeActionID = i.buffer[len(i.buffer)-1].ID
}
i.buffer, i.filter, err = h.svc.Find(ctx, i.filter)
return
}
// Initial load
return i, i.loader()
}
func (h actionlogHandler) record(ctx context.Context, args *actionlogRecordArgs) (err error) {
a := &actionlog.Action{
Resource: args.Resource,
Action: args.Action,
Error: args.Error,
Severity: actionlog.NewSeverity(args.Severity),
Description: args.Description,
}
if args.Meta != nil {
a.Meta = args.Meta.Dict()
}
ctx = actionlog.RequestOriginToContext(ctx, actionlog.RequestOrigin_Automation)
h.svc.Record(ctx, a)
return nil
}
func (i *actionSetIterator) More(context.Context, *Vars) (bool, error) {
return wfexec.GenericResourceNextCheck(i.useIterLimit, i.ptr, uint(len(i.buffer)), i.total, i.iterLimit, i.iterLimit > uint(len(i.buffer))), nil
}
func (i *actionSetIterator) Start(context.Context, *Vars) error { i.ptr = 0; return nil }
func (i *actionSetIterator) Next(context.Context, *Vars) (out *Vars, err error) {
if len(i.buffer)-int(i.ptr) <= 0 {
if err = i.loader(); err != nil {
panic(err)
}
}
out = &Vars{}
out.Set("user", Must(NewUser(i.buffer[i.ptr])))
out.Set("index", Must(NewInteger(i.total+i.ptr)))
i.ptr++
return out, nil
}

View File

@@ -0,0 +1,78 @@
imports:
- time
- github.com/cortezaproject/corteza/server/pkg/actionlog
snippets:
filterParams: &filterParams
fromTimestamp: { meta: { label: From timestamp }, types: [ { wf: DateTime } ] }
toTimestamp: { meta: { label: To timestamp }, types: [ { wf: DateTime } ] }
beforeActionID: { meta: { label: Before action ID }, types: [ { wf: ID } ] }
actorID: { meta: { label: Actor ID }, types: [ { wf: ID } ] }
origin: { meta: { label: Origin }, types: [ { wf: String } ] }
resource: { meta: { label: Resource }, types: [ { wf: String } ] }
action: { meta: { label: Action }, types: [ { wf: String } ] }
limit: { meta: { label: Limit }, types: [ { wf: UnsignedInteger } ] }
rvAction: &rvAction
wf: Action
labels: &labels
actionlog: "step,workflow"
functions:
search:
meta:
short: Action log search
params: *filterParams
labels:
<<: *labels
results:
actions:
<<: *rvAction
isArray: true
each:
kind: iterator
meta:
short: Action log
params: *filterParams
labels:
<<: *labels
results:
action: *rvAction
record:
meta:
short: Record action into action log
labels:
<<: *labels
params:
action:
meta: { label: Action }
types: [ { wf: String } ]
resource:
meta: { label: Resource }
types: [ { wf: String } ]
error:
meta: { label: Error }
types: [ { wf: String } ]
severity:
types: [ { wf: String } ]
meta:
label: Severity
visual:
options:
- "emergency"
- "alert"
- "critical"
- "err"
- "warning"
- "notice"
- "info"
- "debug"
description:
meta: { label: Description }
types: [ { wf: String } ]
meta:
meta: { label: Meta }
types: [ { wf: Vars, go: '*expr.Vars' } ]

1610
server/system/automation/expr_types.gen.go generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,325 @@
package automation
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/rbac"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/spf13/cast"
)
type (
renderedDocument struct {
Document io.Reader
Name string
Type string
}
)
func CastToUser(val interface{}) (out *types.User, err error) {
switch val := val.(type) {
case expr.Iterator:
out = &types.User{}
return out, val.Each(func(k string, v expr.TypedValue) error {
return assignToUser(out, k, v)
})
}
switch val := expr.UntypedValue(val).(type) {
case *types.User:
return val, nil
case map[string]interface{}:
out = &types.User{}
m, _ := json.Marshal(val)
_ = json.Unmarshal(m, out)
return out, nil
case nil:
return &types.User{}, nil
default:
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func CastToRole(val interface{}) (out *types.Role, err error) {
switch val := val.(type) {
case expr.Iterator:
out = &types.Role{}
return out, val.Each(func(k string, v expr.TypedValue) error {
return assignToRole(out, k, v)
})
}
switch val := expr.UntypedValue(val).(type) {
case *types.Role:
return val, nil
case map[string]interface{}:
out = &types.Role{}
m, _ := json.Marshal(val)
_ = json.Unmarshal(m, out)
return out, nil
case nil:
return &types.Role{}, nil
default:
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func CastToReminder(val interface{}) (out *types.Reminder, err error) {
switch val := val.(type) {
case expr.Iterator:
out = &types.Reminder{}
return out, val.Each(func(k string, v expr.TypedValue) error {
return assignToReminder(out, k, v)
})
}
switch val := expr.UntypedValue(val).(type) {
case *types.Reminder:
return val, nil
case map[string]interface{}:
out = &types.Reminder{}
// m, _ := json.Marshal(val)
// _ = json.Unmarshal(m, out)
for k,v := range val {
if err = assignToReminder(out, k,v);err != nil{
return nil, err
}
}
return out, nil
case nil:
return &types.Reminder{}, nil
default:
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func CastToTemplate(val interface{}) (out *types.Template, err error) {
switch val := val.(type) {
case expr.Iterator:
out = &types.Template{}
return out, val.Each(func(k string, v expr.TypedValue) error {
return assignToTemplate(out, k, v)
})
}
switch val := expr.UntypedValue(val).(type) {
case *types.Template:
return val, nil
case map[string]interface{}:
out = &types.Template{}
m, _ := json.Marshal(val)
_ = json.Unmarshal(m, out)
return out, nil
case nil:
return &types.Template{}, nil
default:
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func CastToTemplateMeta(val interface{}) (out types.TemplateMeta, err error) {
switch val := val.(type) {
case expr.Iterator:
out = types.TemplateMeta{}
return out, val.Each(func(k string, v expr.TypedValue) error {
return assignToTemplateMeta(&out, k, v)
})
}
switch val := expr.UntypedValue(val).(type) {
case types.TemplateMeta:
return val, nil
case map[string]interface{}:
out = types.TemplateMeta{}
m, _ := json.Marshal(val)
_ = json.Unmarshal(m, &out)
return out, nil
case nil:
return types.TemplateMeta{}, nil
default:
return types.TemplateMeta{}, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
var _ expr.DeepFieldAssigner = &Template{}
// AssignFieldValue implements expr.DeepFieldAssigner
//
// Meta is a value struct; assigning to its fields through the generated
// selector would modify a copy, so we reroute it to the template's own meta
func (t *Template) AssignFieldValue(p expr.Pather, val expr.TypedValue) (err error) {
t.mux.Lock()
defer t.mux.Unlock()
if p.Get() != "meta" || p.IsLast() {
return assignToTemplate(t.value, p.Get(), val)
}
if err = p.Next(); err != nil {
return
}
return assignToTemplateMeta(&t.value.Meta, p.Get(), val)
}
func CastToRenderedDocument(val interface{}) (out *renderedDocument, err error) {
switch val := val.(type) {
case expr.Iterator:
out = &renderedDocument{}
return out, val.Each(func(k string, v expr.TypedValue) error {
return assignToRenderedDocument(out, k, v)
})
}
switch val := expr.UntypedValue(val).(type) {
case *renderedDocument:
return val, nil
default:
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func CastToDocumentType(val interface{}) (out types.DocumentType, err error) {
switch val := val.(type) {
case string:
return types.DocumentType(val), nil
case *expr.String:
return types.DocumentType(val.GetValue()), nil
default:
return "", fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func CastToRenderOptions(val interface{}) (out map[string]string, err error) {
switch val := expr.UntypedValue(val).(type) {
case map[string]string:
return val, nil
case nil:
return make(map[string]string), nil
default:
out, err = cast.ToStringMapStringE(val)
if err != nil {
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
}
return out, nil
}
}
func (t *RenderOptions) AssignFieldValue(key string, val expr.TypedValue) error {
if t.value == nil {
t.value = make(map[string]string)
}
str, err := cast.ToStringE(expr.UntypedValue(val))
t.value[key] = str
return err
}
func CastToQueueMessage(val interface{}) (out *types.QueueMessage, err error) {
switch val := val.(type) {
case expr.Iterator:
out = &types.QueueMessage{}
return out, val.Each(func(k string, v expr.TypedValue) error {
return assignToQueueMessage(out, k, v)
})
}
switch val := expr.UntypedValue(val).(type) {
case *types.QueueMessage:
return val, nil
case nil:
return &types.QueueMessage{}, nil
default:
return &types.QueueMessage{}, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func (doc renderedDocument) String() string {
aux, _ := ioutil.ReadAll(doc.Document)
return string(aux)
}
func CastToRbacResource(val interface{}) (out rbac.Resource, err error) {
switch val := expr.UntypedValue(val).(type) {
case rbac.Resource:
return val, nil
case RbacResource:
return val.value, nil
case string:
return rbac.NewResource(val), nil
default:
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func CastToAction(val interface{}) (out *actionlog.Action, err error) {
switch val := expr.UntypedValue(val).(type) {
case *actionlog.Action:
return val, nil
default:
return nil, fmt.Errorf("unable to cast type %T to %T", val, out)
}
}
func (v *Action) Clone() (expr.TypedValue, error) {
aux := *v.value
return NewAction(&aux)
}
func (v *DocumentType) Clone() (expr.TypedValue, error) {
return NewDocumentType(v.value)
}
func (v *QueueMessage) Clone() (expr.TypedValue, error) {
aux := *v.value
return NewQueueMessage(&aux)
}
func (v *RbacResource) Clone() (expr.TypedValue, error) {
return NewRbacResource(v.value)
}
func (v *RenderOptions) Clone() (expr.TypedValue, error) {
aux := make(map[string]string)
for k, v := range v.value {
aux[k] = v
}
return NewRenderOptions(aux)
}
func (v *RenderedDocument) Clone() (expr.TypedValue, error) {
aux := *v.value
return NewRenderedDocument(&aux)
}
func (v *Reminder) Clone() (expr.TypedValue, error) {
if v.value == nil {
return NewReminder(nil)
}
aux := *v.value
return NewReminder(&aux)
}
func (v *Role) Clone() (expr.TypedValue, error) {
aux := v.value.Clone()
return NewRole(aux)
}
func (v *Template) Clone() (expr.TypedValue, error) {
aux := v.value.Clone()
return NewTemplate(aux)
}
func (v *TemplateMeta) Clone() (expr.TypedValue, error) {
return NewTemplateMeta(v.value)
}
func (v *User) Clone() (expr.TypedValue, error) {
aux := v.value.Clone()
return NewUser(aux)
}

View File

@@ -0,0 +1,97 @@
package: automation
imports:
- github.com/cortezaproject/corteza/server/system/types
- github.com/cortezaproject/corteza/server/pkg/rbac
- github.com/cortezaproject/corteza/server/pkg/actionlog
types:
Template:
as: '*types.Template'
# meta is a value struct, assigning to it needs to be rerouted
customFieldAssigner: true
struct:
- { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro, alias: 'templateID'}
- { name: 'handle', exprType: 'Handle', goType: 'string' }
- { name: 'language', exprType: 'String', goType: 'string' }
- { name: 'type', exprType: 'DocumentType', goType: 'types.DocumentType' }
- { name: 'partial', exprType: 'Boolean', goType: 'bool' }
- { name: 'meta', exprType: 'TemplateMeta', goType: 'types.TemplateMeta' }
- { name: 'template', exprType: 'String', goType: 'string' }
- { name: 'labels', exprType: 'LabelValue', goType: 'map[string]labelTypes.LabelValue' }
- { name: 'ownerID', exprType: 'ID', goType: 'uint64', mode: ro}
- { name: 'createdAt', exprType: 'DateTime', goType: 'time.Time', mode: ro}
- { name: 'updatedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
- { name: 'deletedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
- { name: 'lastUsedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
User:
as: '*types.User'
struct:
- { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro, alias: 'userID' }
- { name: 'username', exprType: 'String', goType: 'string' }
- { name: 'email', exprType: 'String', goType: 'string' }
- { name: 'name', exprType: 'String', goType: 'string' }
- { name: 'handle', exprType: 'Handle', goType: 'string' }
- { name: 'emailConfirmed', exprType: 'Boolean', goType: 'bool' }
- { name: 'labels', exprType: 'LabelValue', goType: 'map[string]labelTypes.LabelValue' }
- { name: 'createdAt', exprType: 'DateTime', goType: 'time.Time', mode: ro}
- { name: 'updatedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
- { name: 'suspendedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
- { name: 'deletedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
Role:
as: '*types.Role'
struct:
- { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro, alias: 'roleID' }
- { name: 'name', exprType: 'String', goType: 'string' }
- { name: 'handle', exprType: 'Handle', goType: 'string' }
- { name: 'labels', exprType: 'LabelValue', goType: 'map[string]labelTypes.LabelValue' }
- { name: 'createdAt', exprType: 'DateTime', goType: 'time.Time', mode: ro}
- { name: 'updatedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
- { name: 'archivedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
- { name: 'deletedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
Reminder:
as: '*types.Reminder'
struct:
- { name: 'ID', exprType: 'ID', goType: 'uint64', mode: ro, alias: 'reminderID' }
- { name: 'resource', exprType: 'String', goType: 'string'}
- { name: 'snoozeCount', exprType: 'UnsignedInteger', goType: 'uint', mode: ro }
- { name: 'assignedTo', exprType: 'ID', goType: 'uint64' }
- { name: 'assignedBy', exprType: 'ID', goType: 'uint64', mode: ro }
- { name: 'assignedAt', exprType: 'DateTime', goType: 'time.Time', mode: ro }
- { name: 'dismissedBy', exprType: 'ID', goType: 'uint64', mode: ro }
- { name: 'dismissedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
- { name: 'remindAt', exprType: 'DateTime', goType: '*time.Time' }
- { name: 'createdAt', exprType: 'DateTime', goType: 'time.Time', mode: ro }
- { name: 'updatedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
- { name: 'deletedAt', exprType: 'DateTime', goType: '*time.Time', mode: ro }
- { name: 'payload', exprType: 'Bytes', goType: 'types.JSONText'}
DocumentType:
as: 'types.DocumentType'
TemplateMeta:
as: 'types.TemplateMeta'
struct:
- { name: 'short', exprType: 'String', goType: 'string'}
- { name: 'description', exprType: 'String', goType: 'string'}
- { name: 'headerTemplateID', exprType: 'ID', goType: 'uint64'}
- { name: 'footerTemplateID', exprType: 'ID', goType: 'uint64'}
RenderedDocument:
as: '*renderedDocument'
struct:
- { name: 'document', exprType: 'Reader', goType: 'io.Reader' }
- { name: 'name', exprType: 'string', goType: 'string' }
- { name: 'type', exprType: 'string', goType: 'string' }
RenderOptions:
as: 'map[string]string'
QueueMessage:
as: '*types.QueueMessage'
struct:
- { name: 'Queue', exprType: 'String', goType: 'string' }
- { name: 'Payload', exprType: 'Bytes', goType: '[]byte' }
RbacResource:
as: 'rbac.Resource'
Action:
as: '*actionlog.Action'

View File

@@ -0,0 +1,148 @@
package automation
import (
"context"
"testing"
"github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/rbac"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/stretchr/testify/require"
)
func TestUser(t *testing.T) {
var (
req = require.New(t)
u, err = NewUser(&types.User{Handle: "handle"})
)
req.NoError(err)
req.Equal("handle", u.value.Handle)
req.Error(u.AssignFieldValue("some-unexisting-field", nil))
req.NoError(u.AssignFieldValue("email", expr.Must(expr.NewString("dummy@domain.tpl"))))
req.Equal("dummy@domain.tpl", u.value.Email)
}
func TestUser_Expr(t *testing.T) {
var (
req = require.New(t)
u, _ = NewUser(&types.User{Handle: "hendl"})
scope = &expr.Vars{}
)
req.NoError(scope.Set("user", u))
eval, err := expr.NewParser().Parse("user.handle")
req.NoError(err)
res, err := eval.Eval(context.Background(), scope)
req.NoError(err)
req.Equal("hendl", res.(string))
}
func TestTemplate_metaAssign(t *testing.T) {
var (
req = require.New(t)
t1, err = NewTemplate(&types.Template{
Handle: "invoice",
Meta: types.TemplateMeta{Short: "before"},
})
)
req.NoError(err)
// fields of the meta struct
req.NoError(expr.Assign(t1, "meta.short", expr.Must(expr.NewString("after"))))
req.Equal("after", t1.value.Meta.Short)
req.NoError(expr.Assign(t1, "meta.headerTemplateID", expr.Must(expr.NewID(42))))
req.Equal(uint64(42), t1.value.Meta.HeaderTemplateID)
req.NoError(expr.Assign(t1, "meta.footerTemplateID", expr.Must(expr.NewID(43))))
req.Equal(uint64(43), t1.value.Meta.FooterTemplateID)
req.Error(expr.Assign(t1, "meta.unknown-field", expr.Must(expr.NewString("nope"))))
// whole meta struct
meta, err := NewTemplateMeta(types.TemplateMeta{Short: "whole", HeaderTemplateID: 99})
req.NoError(err)
req.NoError(expr.Assign(t1, "meta", meta))
req.Equal("whole", t1.value.Meta.Short)
req.Equal(uint64(99), t1.value.Meta.HeaderTemplateID)
// meta from a map, the way it comes in from a workflow
fromMap, err := NewTemplateMeta(map[string]interface{}{"short": "mapped", "footerTemplateID": "13"})
req.NoError(err)
req.NoError(expr.Assign(t1, "meta", fromMap))
req.Equal("mapped", t1.value.Meta.Short)
req.Equal(uint64(13), t1.value.Meta.FooterTemplateID)
// other fields still assign
req.NoError(expr.Assign(t1, "handle", expr.Must(expr.NewString("receipt"))))
req.Equal("receipt", t1.value.Handle)
req.Error(expr.Assign(t1, "some-unexisting-field", expr.Must(expr.NewString("nope"))))
}
// Assigning through a scope, the way workflow expression steps do it
func TestTemplate_metaAssignInScope(t *testing.T) {
var (
req = require.New(t)
t1, err = NewTemplate(&types.Template{Handle: "invoice"})
scope = &expr.Vars{}
)
req.NoError(err)
req.NoError(scope.Set("template", t1))
req.NoError(expr.Assign(scope, "template.meta.headerTemplateID", expr.Must(expr.NewID(42))))
req.NoError(expr.Assign(scope, "template.meta.short", expr.Must(expr.NewString("scoped"))))
selected, err := scope.Select("template")
req.NoError(err)
tpl, err := CastToTemplate(selected)
req.NoError(err)
req.Equal(uint64(42), tpl.Meta.HeaderTemplateID)
req.Equal("scoped", tpl.Meta.Short)
}
func TestCastToRbacResource(t *testing.T) {
var (
req = require.New(t)
)
t.Run("string to RbacResource", func(t *testing.T) {
resourceString := "corteza::system:user/461133310995726337"
resource, err := CastToRbacResource(resourceString)
req.NoError(err)
req.NotNil(resource)
req.Equal(resourceString, resource.RbacResource())
})
t.Run("existing RbacResource", func(t *testing.T) {
originalResource := rbac.NewResource("corteza::system:role/123")
resource, err := CastToRbacResource(originalResource)
req.NoError(err)
req.Equal(originalResource, resource)
})
t.Run("RbacResource expression type", func(t *testing.T) {
originalResource := rbac.NewResource("corteza::system:role/123")
rbacRes, err := NewRbacResource(originalResource)
req.NoError(err)
resource, err := CastToRbacResource(rbacRes)
req.NoError(err)
req.Equal(originalResource, resource)
})
t.Run("invalid type", func(t *testing.T) {
resource, err := CastToRbacResource(123)
req.Error(err)
req.Nil(resource)
req.Contains(err.Error(), "unable to cast type int to")
})
}

View File

@@ -0,0 +1,124 @@
package automation
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// system/automation/notification_handler.yaml
import (
"context"
atypes "github.com/cortezaproject/corteza/server/automation/types"
"github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/wfexec"
)
var _ wfexec.ExecResponse
type (
notificationHandlerRegistry interface {
AddFunctions(ff ...*atypes.Function)
Type(ref string) expr.Type
}
)
func (h notificationHandler) register() {
h.reg.AddFunctions(
h.Send(),
)
}
type (
notificationSendArgs struct {
hasRecipient bool
Recipient interface{}
recipientID uint64
recipientHandle string
recipientEmail string
hasTitle bool
Title string
hasDescription bool
Description string
}
)
func (a notificationSendArgs) GetRecipient() (bool, uint64, string, string) {
return a.hasRecipient, a.recipientID, a.recipientHandle, a.recipientEmail
}
// Send function Send simple notification
//
// expects implementation of send function:
//
// func (h notificationHandler) send(ctx context.Context, args *notificationSendArgs) (err error) {
// return
// }
func (h notificationHandler) Send() *atypes.Function {
return &atypes.Function{
Ref: "notificationSend",
Kind: "function",
Labels: map[string]string(nil),
Meta: &atypes.FunctionMeta{
Short: "Send simple notification",
Description: "Sends a simple notification with title and description to a user",
},
Parameters: []*atypes.Param{
{
Name: "recipient",
Types: []string{"ID", "Handle", "String"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Recipient",
},
},
{
Name: "title",
Types: []string{"String"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Title",
},
},
{
Name: "description",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Description",
Visual: map[string]interface{}{"input": map[string]interface{}{"type": "richtext"}},
},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &notificationSendArgs{
hasRecipient: in.Has("recipient"),
hasTitle: in.Has("title"),
hasDescription: in.Has("description"),
}
)
if err = in.Decode(args); err != nil {
return
}
// Converting Recipient argument
if args.hasRecipient {
aux := expr.Must(expr.Select(in, "recipient"))
switch aux.Type() {
case h.reg.Type("ID").Type():
args.recipientID = aux.Get().(uint64)
case h.reg.Type("Handle").Type():
args.recipientHandle = aux.Get().(string)
case h.reg.Type("String").Type():
args.recipientEmail = aux.Get().(string)
}
}
return out, h.send(ctx, args)
},
}
}

View File

@@ -0,0 +1,104 @@
package automation
import (
"context"
"fmt"
composeTypes "github.com/cortezaproject/corteza/server/compose/types"
systemTypes "github.com/cortezaproject/corteza/server/system/types"
"go.uber.org/zap"
)
type (
notificationHandler struct {
reg notificationHandlerRegistry
svc notificationService
uSvc notificationUserService
logger *zap.Logger
}
notificationService interface {
Create(context.Context, *systemTypes.Notification) (*systemTypes.Notification, error)
}
// Interface for user service that we need for recipient lookups
notificationUserService interface {
FindByID(ctx context.Context, userID uint64) (*systemTypes.User, error)
FindByHandle(ctx context.Context, handle string) (*systemTypes.User, error)
FindByEmail(ctx context.Context, email string) (*systemTypes.User, error)
}
notificationNamespaceService interface {
FindByHandle(ctx context.Context, handle string) (*composeTypes.Namespace, error)
}
notificationModuleService interface {
FindByHandle(ctx context.Context, handle string) (*composeTypes.Module, error)
}
)
// NotificationHandler initializes the notification handler
func NotificationHandler(reg notificationHandlerRegistry, ntfSvc notificationService, uSvc notificationUserService, log *zap.Logger) *notificationHandler {
h := &notificationHandler{
reg: reg,
svc: ntfSvc,
uSvc: uSvc,
logger: log.Named("notification"),
}
h.register()
return h
}
// send creates and sends a notification
func (h notificationHandler) send(ctx context.Context, args *notificationSendArgs) error {
// Get the recipient user ID from the input parameter
var recipientID uint64
// Check if we have a direct user object
if args.recipientID > 0 {
// Direct ID passed
recipientID = args.recipientID
} else {
// Need to look up the user
var user *systemTypes.User
var err error
if args.recipientHandle != "" {
user, err = h.uSvc.FindByHandle(ctx, args.recipientHandle)
} else if args.recipientEmail != "" {
user, err = h.uSvc.FindByEmail(ctx, args.recipientEmail)
} else {
return fmt.Errorf("invalid recipient: unable to determine user ID")
}
if err != nil {
return err
}
if user == nil {
return fmt.Errorf("recipient not found")
}
recipientID = user.ID
}
// Create notification config directly with the expected fields for a simple notification
config := systemTypes.NotificationConfig{
Simple: systemTypes.SimpleNotificationConfig{
Title: args.Title,
Description: args.Description,
},
}
// Create a simple notification
ntf := &systemTypes.Notification{
Kind: systemTypes.NotificationKindSimple,
Config: config,
Recipient: recipientID,
}
// Create the notification
_, err := h.svc.Create(ctx, ntf)
return err
}

View File

@@ -0,0 +1,30 @@
name: notification
snippets:
lookupUser: &lookupUser
meta: { label: Recipient }
required: true
types:
- { wf: ID }
- { wf: Handle }
- { wf: String, suffix: email }
functions:
send:
meta:
short: Send simple notification
description: Sends a simple notification with title and description to a user
params:
recipient: *lookupUser
title:
meta: { label: Title }
types:
- { wf: String }
required: true
description: The title of the simple notification
description:
types:
- { wf: String }
required: false
description: Optional description for the simple notification
meta: { label: Description, visual: { input: { type: richtext } } }

View File

@@ -0,0 +1,412 @@
package automation
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// system/automation/rbac_handler.yaml
import (
"context"
atypes "github.com/cortezaproject/corteza/server/automation/types"
"github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/rbac"
"github.com/cortezaproject/corteza/server/pkg/wfexec"
"github.com/cortezaproject/corteza/server/system/types"
)
var _ wfexec.ExecResponse
type (
rbacHandlerRegistry interface {
AddFunctions(ff ...*atypes.Function)
Type(ref string) expr.Type
}
)
func (h rbacHandler) register() {
h.reg.AddFunctions(
h.Allow(),
h.Deny(),
h.Inherit(),
h.Check(),
)
}
type (
rbacAllowArgs struct {
hasResource bool
Resource rbac.Resource
hasRole bool
Role interface{}
roleID uint64
roleHandle string
roleRes *types.Role
hasOperation bool
Operation string
}
)
func (a rbacAllowArgs) GetRole() (bool, uint64, string, *types.Role) {
return a.hasRole, a.roleID, a.roleHandle, a.roleRes
}
// Allow function RBAC: Allow operation on resource to a role
//
// expects implementation of allow function:
//
// func (h rbacHandler) allow(ctx context.Context, args *rbacAllowArgs) (err error) {
// return
// }
func (h rbacHandler) Allow() *atypes.Function {
return &atypes.Function{
Ref: "rbacAllow",
Kind: "function",
Labels: map[string]string{"users": "rbac"},
Meta: &atypes.FunctionMeta{
Short: "RBAC: Allow operation on resource to a role",
},
Parameters: []*atypes.Param{
{
Name: "resource",
Types: []string{"RbacResource"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Resource",
},
},
{
Name: "role",
Types: []string{"ID", "Handle", "Role"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Role",
},
},
{
Name: "operation",
Types: []string{"String"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Operation",
},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &rbacAllowArgs{
hasResource: in.Has("resource"),
hasRole: in.Has("role"),
hasOperation: in.Has("operation"),
}
)
if err = in.Decode(args); err != nil {
return
}
// Converting Role argument
if args.hasRole {
aux := expr.Must(expr.Select(in, "role"))
switch aux.Type() {
case h.reg.Type("ID").Type():
args.roleID = aux.Get().(uint64)
case h.reg.Type("Handle").Type():
args.roleHandle = aux.Get().(string)
case h.reg.Type("Role").Type():
args.roleRes = aux.Get().(*types.Role)
}
}
return out, h.allow(ctx, args)
},
}
}
type (
rbacDenyArgs struct {
hasResource bool
Resource rbac.Resource
hasRole bool
Role interface{}
roleID uint64
roleHandle string
roleRes *types.Role
hasOperation bool
Operation string
}
)
func (a rbacDenyArgs) GetRole() (bool, uint64, string, *types.Role) {
return a.hasRole, a.roleID, a.roleHandle, a.roleRes
}
// Deny function RBAC: Deny operation on resource to a role
//
// expects implementation of deny function:
//
// func (h rbacHandler) deny(ctx context.Context, args *rbacDenyArgs) (err error) {
// return
// }
func (h rbacHandler) Deny() *atypes.Function {
return &atypes.Function{
Ref: "rbacDeny",
Kind: "function",
Labels: map[string]string{"users": "rbac"},
Meta: &atypes.FunctionMeta{
Short: "RBAC: Deny operation on resource to a role",
},
Parameters: []*atypes.Param{
{
Name: "resource",
Types: []string{"RbacResource"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Resource",
},
},
{
Name: "role",
Types: []string{"ID", "Handle", "Role"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Role",
},
},
{
Name: "operation",
Types: []string{"String"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Operation",
},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &rbacDenyArgs{
hasResource: in.Has("resource"),
hasRole: in.Has("role"),
hasOperation: in.Has("operation"),
}
)
if err = in.Decode(args); err != nil {
return
}
// Converting Role argument
if args.hasRole {
aux := expr.Must(expr.Select(in, "role"))
switch aux.Type() {
case h.reg.Type("ID").Type():
args.roleID = aux.Get().(uint64)
case h.reg.Type("Handle").Type():
args.roleHandle = aux.Get().(string)
case h.reg.Type("Role").Type():
args.roleRes = aux.Get().(*types.Role)
}
}
return out, h.deny(ctx, args)
},
}
}
type (
rbacInheritArgs struct {
hasResource bool
Resource rbac.Resource
hasRole bool
Role interface{}
roleID uint64
roleHandle string
roleRes *types.Role
hasOperation bool
Operation string
}
)
func (a rbacInheritArgs) GetRole() (bool, uint64, string, *types.Role) {
return a.hasRole, a.roleID, a.roleHandle, a.roleRes
}
// Inherit function RBAC: Remove allow/deny operation of a role from resource
//
// expects implementation of inherit function:
//
// func (h rbacHandler) inherit(ctx context.Context, args *rbacInheritArgs) (err error) {
// return
// }
func (h rbacHandler) Inherit() *atypes.Function {
return &atypes.Function{
Ref: "rbacInherit",
Kind: "function",
Labels: map[string]string{"users": "rbac"},
Meta: &atypes.FunctionMeta{
Short: "RBAC: Remove allow/deny operation of a role from resource",
},
Parameters: []*atypes.Param{
{
Name: "resource",
Types: []string{"RbacResource"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Resource",
},
},
{
Name: "role",
Types: []string{"ID", "Handle", "Role"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Role",
},
},
{
Name: "operation",
Types: []string{"String"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Operation",
},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &rbacInheritArgs{
hasResource: in.Has("resource"),
hasRole: in.Has("role"),
hasOperation: in.Has("operation"),
}
)
if err = in.Decode(args); err != nil {
return
}
// Converting Role argument
if args.hasRole {
aux := expr.Must(expr.Select(in, "role"))
switch aux.Type() {
case h.reg.Type("ID").Type():
args.roleID = aux.Get().(uint64)
case h.reg.Type("Handle").Type():
args.roleHandle = aux.Get().(string)
case h.reg.Type("Role").Type():
args.roleRes = aux.Get().(*types.Role)
}
}
return out, h.inherit(ctx, args)
},
}
}
type (
rbacCheckArgs struct {
hasResource bool
Resource rbac.Resource
hasOperation bool
Operation string
hasUser bool
User *types.User
}
rbacCheckResults struct {
Can bool
}
)
// Check function RBAC: Can user perform an operation on a resource
//
// expects implementation of check function:
//
// func (h rbacHandler) check(ctx context.Context, args *rbacCheckArgs) (results *rbacCheckResults, err error) {
// return
// }
func (h rbacHandler) Check() *atypes.Function {
return &atypes.Function{
Ref: "rbacCheck",
Kind: "function",
Labels: map[string]string{"users": "rbac"},
Meta: &atypes.FunctionMeta{
Short: "RBAC: Can user perform an operation on a resource",
},
Parameters: []*atypes.Param{
{
Name: "resource",
Types: []string{"RbacResource"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Resource",
},
},
{
Name: "operation",
Types: []string{"String"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Operation",
},
},
{
Name: "user",
Types: []string{"User"},
Meta: &atypes.ParamMeta{
Label: "User",
},
},
},
Results: []*atypes.Param{
{
Name: "can",
Types: []string{"Boolean"},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &rbacCheckArgs{
hasResource: in.Has("resource"),
hasOperation: in.Has("operation"),
hasUser: in.Has("user"),
}
)
if err = in.Decode(args); err != nil {
return
}
var results *rbacCheckResults
if results, err = h.check(ctx, args); err != nil {
return
}
out = &expr.Vars{}
{
// converting results.Can (bool) to Boolean
var (
tval expr.TypedValue
)
if tval, err = h.reg.Type("Boolean").Cast(results.Can); err != nil {
return
} else if err = expr.Assign(out, "can", tval); err != nil {
return
}
}
return
},
}
}

View File

@@ -0,0 +1,138 @@
package automation
import (
"context"
"fmt"
"github.com/cortezaproject/corteza/server/pkg/rbac"
"github.com/cortezaproject/corteza/server/system/types"
)
type (
rbacService interface {
Can(ses rbac.Session, op string, res rbac.Resource) bool
Grant(ctx context.Context, rules ...*rbac.Rule) (err error)
}
rbacUserService interface {
FindByAny(ctx context.Context, identifier interface{}) (*types.User, error)
}
rbacHandler struct {
reg rbacHandlerRegistry
rbac rbacService
user rbacUserService
role roleService
}
auxResource struct{ res string }
)
var _ rbac.Resource = &auxResource{}
func (r auxResource) RbacResource() string {
return r.res
}
func RbacHandler(reg rbacHandlerRegistry, rbac rbacService, user rbacUserService, role roleService) *rbacHandler {
h := &rbacHandler{
reg: reg,
rbac: rbac,
user: user,
role: role,
}
h.register()
return h
}
func (h rbacHandler) allow(ctx context.Context, args *rbacAllowArgs) (err error) {
r, err := lookupRole(ctx, h.role, rolesLookupArgs{
hasLookup: args.hasRole,
Lookup: args.Role,
lookupID: args.roleID,
lookupHandle: args.roleHandle,
lookupRes: args.roleRes,
})
if err != nil {
return
}
return h.grant(ctx, rbac.AllowRule(r.ID, args.Resource.RbacResource(), args.Operation))
}
func (h rbacHandler) deny(ctx context.Context, args *rbacDenyArgs) (err error) {
r, err := lookupRole(ctx, h.role, rolesLookupArgs{
hasLookup: args.hasRole,
Lookup: args.Role,
lookupID: args.roleID,
lookupHandle: args.roleHandle,
lookupRes: args.roleRes,
})
if err != nil {
return
}
return h.grant(ctx, rbac.DenyRule(r.ID, args.Resource.RbacResource(), args.Operation))
}
func (h rbacHandler) inherit(ctx context.Context, args *rbacInheritArgs) (err error) {
r, err := lookupRole(ctx, h.role, rolesLookupArgs{
hasLookup: args.hasRole,
Lookup: args.Role,
lookupID: args.roleID,
lookupHandle: args.roleHandle,
lookupRes: args.roleRes,
})
if err != nil {
return
}
return h.grant(ctx, rbac.InheritRule(r.ID, args.Resource.RbacResource(), args.Operation))
}
// verifies grant op (granter needs to be allowed to grant on the component
func (h rbacHandler) grant(ctx context.Context, r *rbac.Rule) (err error) {
// we can safely create a fake component resource since there are
// no extra metadata hidden under it,
cmpRes := &auxResource{rbac.ResourceComponent(r.Resource)}
if !h.rbac.Can(rbac.ContextToSession(ctx), "grant", cmpRes) {
return fmt.Errorf("not allowed to grant %s", r.String())
}
return h.rbac.Grant(ctx, r)
}
func (h rbacHandler) check(ctx context.Context, args *rbacCheckArgs) (b *rbacCheckResults, err error) {
var secses rbac.Session
b = &rbacCheckResults{}
if args.hasUser {
// user was explicitly set to rbac checking function
//
// load all roles that this user is member of
// and create new security session out of it
var (
mm types.RoleMemberSet
ids []uint64
)
mm, err = h.role.Membership(ctx, args.User.ID)
_ = mm.Walk(func(m *types.RoleMember) error {
ids = append(ids, m.RoleID)
return nil
})
args.User.SetRoles(ids...)
secses = rbac.NewSession(ctx, args.User)
} else {
secses = rbac.ContextToSession(ctx)
}
b.Can = h.rbac.Can(secses, args.Operation, args.Resource)
return
}

View File

@@ -0,0 +1,72 @@
imports:
- github.com/cortezaproject/corteza/server/system/types
- github.com/cortezaproject/corteza/server/pkg/rbac
params:
roleLookup: &roleLookup
meta: { label: Role }
required: true
types:
- { wf: ID, }
- { wf: Handle, }
- { wf: Role, suffix: res }
manageParams: &manageParams
resource:
meta: { label: Resource }
required: true
types:
# Note that we can not accept string here because
# we need whole resource with all the info loaded
- { wf: RbacResource }
role: *roleLookup
operation:
meta: { label: Operation }
required: true
types:
- { wf: String }
labels: &labels
users: "rbac"
functions:
allow:
meta:
short: 'RBAC: Allow operation on resource to a role'
labels:
<<: *labels
params: *manageParams
deny:
meta:
short: 'RBAC: Deny operation on resource to a role'
labels:
<<: *labels
params: *manageParams
inherit:
meta:
short: 'RBAC: Remove allow/deny operation of a role from resource'
labels:
<<: *labels
params: *manageParams
check:
meta:
short: 'RBAC: Can user perform an operation on a resource'
labels:
<<: *labels
params:
resource:
meta: { label: Resource }
required: true
types: [ { wf: RbacResource } ]
operation:
meta: { label: Operation }
required: true
types: [ { wf: String } ]
user:
meta: { label: User }
types: [ { wf: User } ]
results:
can: { wf: Boolean }

View File

@@ -0,0 +1,956 @@
package automation
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// system/automation/reminders_handler.yaml
import (
"context"
atypes "github.com/cortezaproject/corteza/server/automation/types"
"github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/wfexec"
"github.com/cortezaproject/corteza/server/system/types"
"time"
)
var _ wfexec.ExecResponse
type (
remindersHandlerRegistry interface {
AddFunctions(ff ...*atypes.Function)
Type(ref string) expr.Type
}
)
func (h remindersHandler) register() {
h.reg.AddFunctions(
h.Lookup(),
h.Search(),
h.Each(),
h.Create(),
h.Update(),
h.Dismiss(),
h.Snooze(),
h.Delete(),
)
}
type (
remindersLookupArgs struct {
hasLookup bool
Lookup interface{}
lookupID uint64
lookupRes *types.Reminder
}
remindersLookupResults struct {
Reminder *types.Reminder
}
)
func (a remindersLookupArgs) GetLookup() (bool, uint64, *types.Reminder) {
return a.hasLookup, a.lookupID, a.lookupRes
}
// Lookup function Reminder lookup
//
// expects implementation of lookup function:
//
// func (h remindersHandler) lookup(ctx context.Context, args *remindersLookupArgs) (results *remindersLookupResults, err error) {
// return
// }
func (h remindersHandler) Lookup() *atypes.Function {
return &atypes.Function{
Ref: "remindersLookup",
Kind: "function",
Labels: map[string]string{"reminders": "step,workflow"},
Meta: &atypes.FunctionMeta{
Short: "Reminder lookup",
Description: "Find specific reminder by ID",
},
Parameters: []*atypes.Param{
{
Name: "lookup",
Types: []string{"ID", "Reminder"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Lookup",
},
},
},
Results: []*atypes.Param{
{
Name: "reminder",
Types: []string{"Reminder"},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &remindersLookupArgs{
hasLookup: in.Has("lookup"),
}
)
if err = in.Decode(args); err != nil {
return
}
// Converting Lookup argument
if args.hasLookup {
aux := expr.Must(expr.Select(in, "lookup"))
switch aux.Type() {
case h.reg.Type("ID").Type():
args.lookupID = aux.Get().(uint64)
case h.reg.Type("Reminder").Type():
args.lookupRes = aux.Get().(*types.Reminder)
}
}
var results *remindersLookupResults
if results, err = h.lookup(ctx, args); err != nil {
return
}
out = &expr.Vars{}
{
// converting results.Reminder (*types.Reminder) to Reminder
var (
tval expr.TypedValue
)
if tval, err = h.reg.Type("Reminder").Cast(results.Reminder); err != nil {
return
} else if err = expr.Assign(out, "reminder", tval); err != nil {
return
}
}
return
},
}
}
type (
remindersSearchArgs struct {
hasResource bool
Resource string
hasAssignedTo bool
AssignedTo uint64
hasExcludeDismissed bool
ExcludeDismissed bool
hasScheduledOnly bool
ScheduledOnly bool
hasSort bool
Sort string
hasLimit bool
Limit uint64
hasIncTotal bool
IncTotal bool
hasPageCursor bool
PageCursor string
}
remindersSearchResults struct {
Reminders []*types.Reminder
Total uint64
}
)
// Search function Reminders search
//
// expects implementation of search function:
//
// func (h remindersHandler) search(ctx context.Context, args *remindersSearchArgs) (results *remindersSearchResults, err error) {
// return
// }
func (h remindersHandler) Search() *atypes.Function {
return &atypes.Function{
Ref: "remindersSearch",
Kind: "function",
Labels: map[string]string{"reminders": "step,workflow"},
Meta: &atypes.FunctionMeta{
Short: "Reminders search",
Description: "Search reminders",
},
Parameters: []*atypes.Param{
{
Name: "resource",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Resource",
},
},
{
Name: "assignedTo",
Types: []string{"ID"},
Meta: &atypes.ParamMeta{
Label: "Assigned to",
},
},
{
Name: "excludeDismissed",
Types: []string{"Boolean"},
Meta: &atypes.ParamMeta{
Label: "Exclude dismissed",
},
},
{
Name: "scheduledOnly",
Types: []string{"Boolean"},
Meta: &atypes.ParamMeta{
Label: "Scheduled only",
},
},
{
Name: "sort",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Sort",
},
},
{
Name: "limit",
Types: []string{"UnsignedInteger"},
Meta: &atypes.ParamMeta{
Label: "Limit",
},
},
{
Name: "incTotal",
Types: []string{"Boolean"},
Meta: &atypes.ParamMeta{
Label: "Include total",
},
},
{
Name: "pageCursor",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Page cursor",
},
},
},
Results: []*atypes.Param{
{
Name: "reminders",
Types: []string{"Reminder"},
IsArray: true,
},
{
Name: "total",
Types: []string{"UnsignedInteger"},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &remindersSearchArgs{
hasResource: in.Has("resource"),
hasAssignedTo: in.Has("assignedTo"),
hasExcludeDismissed: in.Has("excludeDismissed"),
hasScheduledOnly: in.Has("scheduledOnly"),
hasSort: in.Has("sort"),
hasLimit: in.Has("limit"),
hasIncTotal: in.Has("incTotal"),
hasPageCursor: in.Has("pageCursor"),
}
)
if err = in.Decode(args); err != nil {
return
}
var results *remindersSearchResults
if results, err = h.search(ctx, args); err != nil {
return
}
out = &expr.Vars{}
{
// converting results.Reminders (*types.Reminder) to Array (of Reminder)
var (
tval expr.TypedValue
tarr = make([]expr.TypedValue, len(results.Reminders))
)
for i := range results.Reminders {
if tarr[i], err = h.reg.Type("Reminder").Cast(results.Reminders[i]); err != nil {
return
}
}
if tval, err = expr.NewArray(tarr); err != nil {
return
} else if err = expr.Assign(out, "reminders", tval); err != nil {
return
}
}
{
// converting results.Total (uint64) to UnsignedInteger
var (
tval expr.TypedValue
)
if tval, err = h.reg.Type("UnsignedInteger").Cast(results.Total); err != nil {
return
} else if err = expr.Assign(out, "total", tval); err != nil {
return
}
}
return
},
}
}
type (
remindersEachArgs struct {
hasResource bool
Resource string
hasAssignedTo bool
AssignedTo uint64
hasExcludeDismissed bool
ExcludeDismissed bool
hasScheduledOnly bool
ScheduledOnly bool
hasSort bool
Sort string
hasLimit bool
Limit uint64
hasIncTotal bool
IncTotal bool
hasPageCursor bool
PageCursor string
}
remindersEachResults struct {
Reminder *types.Reminder
Total uint64
}
)
// Each function Iterate reminders
//
// expects implementation of each function:
//
// func (h remindersHandler) each(ctx context.Context, args *remindersEachArgs) (results *remindersEachResults, err error) {
// return
// }
func (h remindersHandler) Each() *atypes.Function {
return &atypes.Function{
Ref: "remindersEach",
Kind: "iterator",
Labels: map[string]string(nil),
Meta: &atypes.FunctionMeta{
Short: "Iterate reminders",
},
Parameters: []*atypes.Param{
{
Name: "resource",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Resource",
},
},
{
Name: "assignedTo",
Types: []string{"ID"},
Meta: &atypes.ParamMeta{
Label: "Assigned to",
},
},
{
Name: "excludeDismissed",
Types: []string{"Boolean"},
Meta: &atypes.ParamMeta{
Label: "Exclude dismissed",
},
},
{
Name: "scheduledOnly",
Types: []string{"Boolean"},
Meta: &atypes.ParamMeta{
Label: "Scheduled only",
},
},
{
Name: "sort",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Sort",
},
},
{
Name: "limit",
Types: []string{"UnsignedInteger"},
Meta: &atypes.ParamMeta{
Label: "Limit",
},
},
{
Name: "incTotal",
Types: []string{"Boolean"},
Meta: &atypes.ParamMeta{
Label: "Include total",
},
},
{
Name: "pageCursor",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Page cursor",
},
},
},
Results: []*atypes.Param{
{
Name: "reminder",
Types: []string{"Reminder"},
},
{
Name: "total",
Types: []string{"UnsignedInteger"},
},
},
Iterator: func(ctx context.Context, in *expr.Vars) (out wfexec.IteratorHandler, err error) {
var (
args = &remindersEachArgs{
hasResource: in.Has("resource"),
hasAssignedTo: in.Has("assignedTo"),
hasExcludeDismissed: in.Has("excludeDismissed"),
hasScheduledOnly: in.Has("scheduledOnly"),
hasSort: in.Has("sort"),
hasLimit: in.Has("limit"),
hasIncTotal: in.Has("incTotal"),
hasPageCursor: in.Has("pageCursor"),
}
)
if err = in.Decode(args); err != nil {
return
}
return h.each(ctx, args)
},
}
}
type (
remindersCreateArgs struct {
hasResource bool
Resource string
hasAssignedTo bool
AssignedTo uint64
hasAssignedBy bool
AssignedBy uint64
hasRemindAt bool
RemindAt *time.Time
hasPayload bool
Payload []byte
}
remindersCreateResults struct {
Reminder *types.Reminder
}
)
// Create function Reminder create
//
// expects implementation of create function:
//
// func (h remindersHandler) create(ctx context.Context, args *remindersCreateArgs) (results *remindersCreateResults, err error) {
// return
// }
func (h remindersHandler) Create() *atypes.Function {
return &atypes.Function{
Ref: "remindersCreate",
Kind: "function",
Labels: map[string]string{"create": "step", "reminders": "step,workflow"},
Meta: &atypes.FunctionMeta{
Short: "Reminder create",
},
Parameters: []*atypes.Param{
{
Name: "resource",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Resource",
},
},
{
Name: "assignedTo",
Types: []string{"ID"},
Meta: &atypes.ParamMeta{
Label: "Assigned to",
},
},
{
Name: "assignedBy",
Types: []string{"ID"},
Meta: &atypes.ParamMeta{
Label: "Assigned by",
},
},
{
Name: "remindAt",
Types: []string{"DateTime"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Remind at",
},
},
{
Name: "payload",
Types: []string{"Bytes"},
Meta: &atypes.ParamMeta{
Label: "Payload",
},
},
},
Results: []*atypes.Param{
{
Name: "reminder",
Types: []string{"Reminder"},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &remindersCreateArgs{
hasResource: in.Has("resource"),
hasAssignedTo: in.Has("assignedTo"),
hasAssignedBy: in.Has("assignedBy"),
hasRemindAt: in.Has("remindAt"),
hasPayload: in.Has("payload"),
}
)
if err = in.Decode(args); err != nil {
return
}
var results *remindersCreateResults
if results, err = h.create(ctx, args); err != nil {
return
}
out = &expr.Vars{}
{
// converting results.Reminder (*types.Reminder) to Reminder
var (
tval expr.TypedValue
)
if tval, err = h.reg.Type("Reminder").Cast(results.Reminder); err != nil {
return
} else if err = expr.Assign(out, "reminder", tval); err != nil {
return
}
}
return
},
}
}
type (
remindersUpdateArgs struct {
hasLookup bool
Lookup interface{}
lookupID uint64
lookupRes *types.Reminder
hasResource bool
Resource string
hasAssignedTo bool
AssignedTo uint64
hasAssignedBy bool
AssignedBy uint64
hasRemindAt bool
RemindAt *time.Time
hasPayload bool
Payload []byte
}
remindersUpdateResults struct {
Reminder *types.Reminder
}
)
func (a remindersUpdateArgs) GetLookup() (bool, uint64, *types.Reminder) {
return a.hasLookup, a.lookupID, a.lookupRes
}
// Update function Reminder update
//
// expects implementation of update function:
//
// func (h remindersHandler) update(ctx context.Context, args *remindersUpdateArgs) (results *remindersUpdateResults, err error) {
// return
// }
func (h remindersHandler) Update() *atypes.Function {
return &atypes.Function{
Ref: "remindersUpdate",
Kind: "function",
Labels: map[string]string{"reminders": "step,workflow", "update": "step"},
Meta: &atypes.FunctionMeta{
Short: "Reminder update",
},
Parameters: []*atypes.Param{
{
Name: "lookup",
Types: []string{"ID", "Reminder"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Lookup",
},
},
{
Name: "resource",
Types: []string{"String"},
Meta: &atypes.ParamMeta{
Label: "Resource",
},
},
{
Name: "assignedTo",
Types: []string{"ID"},
Meta: &atypes.ParamMeta{
Label: "Assigned to",
},
},
{
Name: "assignedBy",
Types: []string{"ID"},
Meta: &atypes.ParamMeta{
Label: "Assigned by",
},
},
{
Name: "remindAt",
Types: []string{"DateTime"},
Meta: &atypes.ParamMeta{
Label: "Remind at",
},
},
{
Name: "payload",
Types: []string{"Bytes"},
Meta: &atypes.ParamMeta{
Label: "Payload",
},
},
},
Results: []*atypes.Param{
{
Name: "reminder",
Types: []string{"Reminder"},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &remindersUpdateArgs{
hasLookup: in.Has("lookup"),
hasResource: in.Has("resource"),
hasAssignedTo: in.Has("assignedTo"),
hasAssignedBy: in.Has("assignedBy"),
hasRemindAt: in.Has("remindAt"),
hasPayload: in.Has("payload"),
}
)
if err = in.Decode(args); err != nil {
return
}
// Converting Lookup argument
if args.hasLookup {
aux := expr.Must(expr.Select(in, "lookup"))
switch aux.Type() {
case h.reg.Type("ID").Type():
args.lookupID = aux.Get().(uint64)
case h.reg.Type("Reminder").Type():
args.lookupRes = aux.Get().(*types.Reminder)
}
}
var results *remindersUpdateResults
if results, err = h.update(ctx, args); err != nil {
return
}
out = &expr.Vars{}
{
// converting results.Reminder (*types.Reminder) to Reminder
var (
tval expr.TypedValue
)
if tval, err = h.reg.Type("Reminder").Cast(results.Reminder); err != nil {
return
} else if err = expr.Assign(out, "reminder", tval); err != nil {
return
}
}
return
},
}
}
type (
remindersDismissArgs struct {
hasLookup bool
Lookup interface{}
lookupID uint64
lookupRes *types.Reminder
}
)
func (a remindersDismissArgs) GetLookup() (bool, uint64, *types.Reminder) {
return a.hasLookup, a.lookupID, a.lookupRes
}
// Dismiss function Dismiss reminder
//
// expects implementation of dismiss function:
//
// func (h remindersHandler) dismiss(ctx context.Context, args *remindersDismissArgs) (err error) {
// return
// }
func (h remindersHandler) Dismiss() *atypes.Function {
return &atypes.Function{
Ref: "remindersDismiss",
Kind: "function",
Labels: map[string]string{"dismiss": "step", "reminders": "step,workflow"},
Meta: &atypes.FunctionMeta{
Short: "Dismiss reminder",
},
Parameters: []*atypes.Param{
{
Name: "lookup",
Types: []string{"ID", "Reminder"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Lookup",
},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &remindersDismissArgs{
hasLookup: in.Has("lookup"),
}
)
if err = in.Decode(args); err != nil {
return
}
// Converting Lookup argument
if args.hasLookup {
aux := expr.Must(expr.Select(in, "lookup"))
switch aux.Type() {
case h.reg.Type("ID").Type():
args.lookupID = aux.Get().(uint64)
case h.reg.Type("Reminder").Type():
args.lookupRes = aux.Get().(*types.Reminder)
}
}
return out, h.dismiss(ctx, args)
},
}
}
type (
remindersSnoozeArgs struct {
hasLookup bool
Lookup interface{}
lookupID uint64
lookupRes *types.Reminder
hasRemindAt bool
RemindAt *time.Time
}
)
func (a remindersSnoozeArgs) GetLookup() (bool, uint64, *types.Reminder) {
return a.hasLookup, a.lookupID, a.lookupRes
}
// Snooze function Snooze reminder
//
// expects implementation of snooze function:
//
// func (h remindersHandler) snooze(ctx context.Context, args *remindersSnoozeArgs) (err error) {
// return
// }
func (h remindersHandler) Snooze() *atypes.Function {
return &atypes.Function{
Ref: "remindersSnooze",
Kind: "function",
Labels: map[string]string{"reminders": "step,workflow", "snooze": "step"},
Meta: &atypes.FunctionMeta{
Short: "Snooze reminder",
},
Parameters: []*atypes.Param{
{
Name: "lookup",
Types: []string{"ID", "Reminder"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Lookup",
},
},
{
Name: "remindAt",
Types: []string{"DateTime"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Remind at",
},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &remindersSnoozeArgs{
hasLookup: in.Has("lookup"),
hasRemindAt: in.Has("remindAt"),
}
)
if err = in.Decode(args); err != nil {
return
}
// Converting Lookup argument
if args.hasLookup {
aux := expr.Must(expr.Select(in, "lookup"))
switch aux.Type() {
case h.reg.Type("ID").Type():
args.lookupID = aux.Get().(uint64)
case h.reg.Type("Reminder").Type():
args.lookupRes = aux.Get().(*types.Reminder)
}
}
return out, h.snooze(ctx, args)
},
}
}
type (
remindersDeleteArgs struct {
hasLookup bool
Lookup interface{}
lookupID uint64
lookupRes *types.Reminder
}
)
func (a remindersDeleteArgs) GetLookup() (bool, uint64, *types.Reminder) {
return a.hasLookup, a.lookupID, a.lookupRes
}
// Delete function Reminder delete
//
// expects implementation of delete function:
//
// func (h remindersHandler) delete(ctx context.Context, args *remindersDeleteArgs) (err error) {
// return
// }
func (h remindersHandler) Delete() *atypes.Function {
return &atypes.Function{
Ref: "remindersDelete",
Kind: "function",
Labels: map[string]string{"delete": "step", "reminders": "step,workflow"},
Meta: &atypes.FunctionMeta{
Short: "Reminder delete",
},
Parameters: []*atypes.Param{
{
Name: "lookup",
Types: []string{"ID", "Reminder"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Lookup",
},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &remindersDeleteArgs{
hasLookup: in.Has("lookup"),
}
)
if err = in.Decode(args); err != nil {
return
}
// Converting Lookup argument
if args.hasLookup {
aux := expr.Must(expr.Select(in, "lookup"))
switch aux.Type() {
case h.reg.Type("ID").Type():
args.lookupID = aux.Get().(uint64)
case h.reg.Type("Reminder").Type():
args.lookupRes = aux.Get().(*types.Reminder)
}
}
return out, h.delete(ctx, args)
},
}
}

View File

@@ -0,0 +1,288 @@
package automation
import (
"context"
"fmt"
"time"
intAuth "github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/filter"
. "github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/wfexec"
"github.com/cortezaproject/corteza/server/system/types"
sqlxtypes "github.com/jmoiron/sqlx/types"
)
type(
reminderService interface {
FindByID(ctx context.Context,ID uint64) (*types.Reminder, error)
Find(ctx context.Context, filter types.ReminderFilter) (types.ReminderSet,types.ReminderFilter,error)
Create(ctx context.Context, reminder *types.Reminder) (*types.Reminder,error)
Update(ctx context.Context, reminder *types.Reminder) (*types.Reminder,error)
Delete(ctx context.Context, ID uint64) error
Dismiss(ctx context.Context, ID uint64) error
Snooze(ctx context.Context, ID uint64,remindAt *time.Time) error
}
remindersHandler struct {
reg remindersHandlerRegistry
rSvc reminderService
}
reminderSetIterator struct {
// Item buffer, current item pointer, and total items traversed
ptr uint
buffer types.ReminderSet
total uint
// When filter limit is set, this constraints it
iterLimit uint
useIterLimit bool
// Item loader for additional chunks
filter types.ReminderFilter
loader func() error
// Total reported by the first page
resTotal uint
}
reminderLookup interface {
GetLookup() (bool, uint64, *types.Reminder)
}
)
func RemindersHandler(reg remindersHandlerRegistry,rSvc reminderService) *remindersHandler {
h := &remindersHandler{
reg : reg,
rSvc: rSvc,
}
h.register()
return h
}
func (h remindersHandler) lookup(ctx context.Context, args *remindersLookupArgs) (results *remindersLookupResults, err error){
results = &remindersLookupResults{}
results.Reminder, err = lookupReminder(ctx, h.rSvc, args)
return
}
func (h remindersHandler) search(ctx context.Context,args *remindersSearchArgs) (results *remindersSearchResults,err error){
results = &remindersSearchResults{}
var (
f=types.ReminderFilter{
Resource: args.Resource,
AssignedTo: args.AssignedTo,
ExcludeDismissed: args.ExcludeDismissed,
ScheduledOnly: args.ScheduledOnly,
}
)
if args.hasSort {
if err = f.Sort.Set(args.Sort); err != nil {
return
}
}
if args.hasPageCursor && args.PageCursor != "" {
f.PageCursor = &filter.PagingCursor{}
if err = f.PageCursor.UnmarshalJSON([]byte(args.PageCursor)); err != nil {
return
}
}
if args.hasLimit {
f.Limit = uint(args.Limit)
}
// Total cannot be fetched together with a page cursor
f.IncTotal = args.IncTotal && f.PageCursor == nil
var auxf types.ReminderFilter
results.Reminders, auxf, err = h.rSvc.Find(ctx, f)
results.Total=uint64(auxf.Total)
return
}
func (h remindersHandler) each(ctx context.Context, args *remindersEachArgs) (out wfexec.IteratorHandler, err error){
var(
i = &reminderSetIterator{}
f=types.ReminderFilter{
Resource: args.Resource,
AssignedTo: args.AssignedTo,
ExcludeDismissed: args.ExcludeDismissed,
ScheduledOnly: args.ScheduledOnly,
}
)
if args.hasSort {
if err = f.Sort.Set(args.Sort); err != nil {
return
}
}
if args.hasPageCursor && args.PageCursor != "" {
f.NextPage = &filter.PagingCursor{}
if err = f.NextPage.UnmarshalJSON([]byte(args.PageCursor)); err != nil {
return
}
}
f.IncTotal = args.IncTotal
if args.hasLimit {
i.useIterLimit = true
i.iterLimit = uint(args.Limit)
f.Limit = uint(args.Limit)
if args.Limit > uint64(wfexec.MaxIteratorBufferSize) {
f.Limit = wfexec.MaxIteratorBufferSize
}
i.iterLimit = uint(args.Limit)
} else {
f.Limit = wfexec.MaxIteratorBufferSize
}
i.filter = f
i.loader = func() (err error) {
if i.filter.PageCursor != nil && i.filter.NextPage == nil {
return
}
i.total += i.ptr
i.ptr = 0
i.filter.PageCursor = i.filter.NextPage
i.filter.NextPage = nil
// Total is fetched with the first page only; a paged query cannot carry it
i.filter.IncTotal = i.filter.IncTotal && i.filter.PageCursor == nil
i.buffer, i.filter, err = h.rSvc.Find(ctx, i.filter)
if i.filter.IncTotal {
i.resTotal = i.filter.Total
}
return
}
return i, i.loader()
}
func (h remindersHandler) create(ctx context.Context, args *remindersCreateArgs) (results *remindersCreateResults, err error) {
results = &remindersCreateResults{}
currentUser := intAuth.GetIdentityFromContext(ctx).Identity()
reminder := &types.Reminder{
Resource:args.Resource,
AssignedTo: args.AssignedTo,
AssignedBy: args.AssignedBy,
RemindAt: args.RemindAt,
Payload: args.Payload,
}
if reminder.AssignedTo == 0 {
reminder.AssignedTo = currentUser
}
if reminder.AssignedBy == 0 {
reminder.AssignedBy = currentUser
}
if len(reminder.Payload) == 0 {
reminder.Payload = sqlxtypes.JSONText(`{"title": "", "note": ""}`)
}
if reminder.RemindAt == nil {
return nil, fmt.Errorf("remindAt is required")
}
results.Reminder, err = h.rSvc.Create(ctx, reminder)
return
}
func (h remindersHandler) update(ctx context.Context, args *remindersUpdateArgs) (results *remindersUpdateResults, err error) {
results = &remindersUpdateResults{}
reminder, err := lookupReminder(ctx, h.rSvc, args)
if err != nil {
return nil, err
}
if args.hasResource {
reminder.Resource = args.Resource
}
if args.hasAssignedTo {
reminder.AssignedTo = args.AssignedTo
}
if args.hasAssignedBy {
reminder.AssignedBy = args.AssignedBy
}
if args.hasRemindAt {
reminder.RemindAt = args.RemindAt
}
if args.hasPayload {
reminder.Payload = args.Payload
}
results.Reminder, err = h.rSvc.Update(ctx, reminder)
return
}
func (h remindersHandler) dismiss(ctx context.Context, args *remindersDismissArgs) error {
if id, err := getReminderID(ctx, h.rSvc, args); err != nil {
return err
} else {
return h.rSvc.Dismiss(ctx, id)
}
}
func (h remindersHandler) snooze(ctx context.Context, args *remindersSnoozeArgs) error {
if id, err := getReminderID(ctx, h.rSvc, args); err != nil {
return err
} else {
return h.rSvc.Snooze(ctx, id, args.RemindAt)
}
}
func (h remindersHandler) delete(ctx context.Context, args *remindersDeleteArgs) error {
if id, err := getReminderID(ctx, h.rSvc, args); err != nil {
return err
} else {
return h.rSvc.Delete(ctx, id)
}
}
func getReminderID(ctx context.Context, svc reminderService, args reminderLookup) (uint64, error) {
_, ID, reminder := args.GetLookup()
switch {
case reminder != nil:
return reminder.ID, nil
case ID > 0:
return ID, nil
}
lookupResult, err := lookupReminder(ctx, svc, args)
if err != nil {
return 0, err
}
return lookupResult.ID, nil
}
func lookupReminder(ctx context.Context, svc reminderService, args reminderLookup) (*types.Reminder, error) {
_, ID, reminder := args.GetLookup()
switch {
case reminder != nil:
return reminder, nil
case ID > 0:
return svc.FindByID(ctx, ID)
}
return nil, fmt.Errorf("empty lookup params")
}
func (i *reminderSetIterator) More(context.Context, *expr.Vars) (bool, error) {
a := wfexec.GenericResourceNextCheck(i.useIterLimit, i.ptr, uint(len(i.buffer)), i.total, i.iterLimit, i.filter.NextPage != nil)
return a, nil
}
func (i *reminderSetIterator) Start(context.Context, *expr.Vars) error {
i.ptr = 0
return nil
}
func (i *reminderSetIterator) Next(context.Context, *expr.Vars) (out *expr.Vars, err error) {
if len(i.buffer)-int(i.ptr) <= 0 {
if err = i.loader(); err != nil {
panic(err)
}
}
out = &expr.Vars{}
reminder := *i.buffer[i.ptr] // Make a copy
reminder.Payload = sqlxtypes.JSONText(string(i.buffer[i.ptr].Payload))
out.Set("reminder", Must(NewReminder(&reminder)))
out.Set("total", Must(NewInteger(i.resTotal)))
i.ptr++
return out, nil
}

View File

@@ -0,0 +1,173 @@
imports:
- github.com/cortezaproject/corteza/server/system/types
- time
params:
lookup: &lookup
meta: { label: Lookup }
required: true
types:
- { wf: ID, }
- { wf: Reminder, suffix: res }
reminder: &reminder
types:
- { wf: Reminder }
rvReminder: &rvReminder
wf: Reminder
rvTotal: &rvTotal
wf: UnsignedInteger
filterParams: &filterParams
resource:
meta: { label: Resource }
types:
- { wf: String }
assignedTo:
meta: { label: Assigned to }
types:
- { wf: ID }
excludeDismissed:
meta: { label: Exclude dismissed }
types:
- { wf: Boolean }
scheduledOnly:
meta: { label: Scheduled only }
types:
- { wf: Boolean }
sort:
meta: { label: Sort }
types:
- { wf: String }
limit:
meta: { label: Limit }
types:
- { wf: UnsignedInteger }
incTotal:
meta: { label: Include total }
types:
- { wf: Boolean }
pageCursor:
meta: { label: Page cursor }
types:
- { wf: String }
labels: &labels
reminders: "step,workflow"
functions:
lookup:
meta:
short: Reminder lookup
description: Find specific reminder by ID
labels:
<<: *labels
params:
lookup: *lookup
results:
reminder: *rvReminder
search:
meta:
short: Reminders search
description: Search reminders
labels:
<<: *labels
params: *filterParams
results:
reminders:
<<: *rvReminder
isArray: true
total: *rvTotal
each:
kind: iterator
meta:
short: Iterate reminders
params: *filterParams
results:
reminder: *rvReminder
total: *rvTotal
create:
meta:
short: Reminder create
labels:
<<: *labels
create: "step"
params:
resource:
meta: { label: Resource }
types:
- { wf: String}
assignedTo:
meta: { label: Assigned to }
types:
- { wf: ID }
assignedBy:
meta: { label: Assigned by }
types:
- { wf: ID }
remindAt:
meta: { label: Remind at }
types:
- { wf: DateTime}
required: true
payload:
meta: { label: Payload }
types:
- { wf: Bytes }
results:
reminder: *rvReminder
update:
meta:
short: Reminder update
labels:
<<: *labels
update: "step"
params:
lookup: *lookup
resource:
meta: { label: Resource }
types:
- { wf: String}
assignedTo:
meta: { label: Assigned to }
types:
- { wf: ID }
assignedBy:
meta: { label: Assigned by }
types:
- { wf: ID }
remindAt:
meta: { label: Remind at }
types:
- { wf: DateTime}
payload:
meta: { label: Payload }
types:
- { wf: Bytes }
results:
reminder: *rvReminder
dismiss:
meta:
short: Dismiss reminder
labels:
<<: *labels
dismiss: "step"
params:
lookup: *lookup
snooze:
meta:
short: Snooze reminder
labels:
<<: *labels
snooze: "step"
params:
lookup: *lookup
remindAt:
meta: { label: Remind at }
types:
- { wf: DateTime }
required: true
delete:
meta:
short: Reminder delete
labels:
<<: *labels
delete: "step"
params:
lookup: *lookup

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,434 @@
package automation
import (
"context"
"fmt"
. "github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/filter"
"github.com/cortezaproject/corteza/server/pkg/rbac"
"github.com/cortezaproject/corteza/server/pkg/wfexec"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/spf13/cast"
)
type (
roleService interface {
FindByID(ctx context.Context, roleID uint64) (*types.Role, error)
FindByHandle(ctx context.Context, handle string) (*types.Role, error)
Find(ctx context.Context, filter types.RoleFilter) (set types.RoleSet, f types.RoleFilter, err error)
Create(ctx context.Context, role *types.Role) (*types.Role, error)
Update(ctx context.Context, role *types.Role) (*types.Role, error)
Delete(ctx context.Context, id uint64) error
Archive(ctx context.Context, id uint64) error
Unarchive(ctx context.Context, id uint64) error
Undelete(ctx context.Context, id uint64) error
Membership(ctx context.Context, userID uint64) (types.RoleMemberSet, error)
MemberList(ctx context.Context, roleID uint64) (types.RoleMemberSet, error)
MemberAdd(ctx context.Context, roleID, userID uint64) error
MemberRemove(ctx context.Context, roleID, userID uint64) error
}
rolesHandler struct {
reg rolesHandlerRegistry
rSvc roleService
uSvc userService
}
roleSetIterator struct {
// Item buffer, current item pointer, and total items traversed
ptr uint
buffer types.RoleSet
total uint
// When filter limit is set, this constraints it
iterLimit uint
useIterLimit bool
// Item loader for additional chunks
filter types.RoleFilter
loader func() error
// Total reported by the first page
resTotal uint
}
roleLookup interface {
GetLookup() (bool, uint64, string, *types.Role)
}
)
func RolesHandler(reg rolesHandlerRegistry, rSvc roleService, uSvc userService) *rolesHandler {
h := &rolesHandler{
reg: reg,
rSvc: rSvc,
uSvc: uSvc,
}
h.register()
return h
}
func (h rolesHandler) lookup(ctx context.Context, args *rolesLookupArgs) (results *rolesLookupResults, err error) {
results = &rolesLookupResults{}
results.Role, err = lookupRole(ctx, h.rSvc, args)
return
}
func (h rolesHandler) searchMembers(ctx context.Context, args *rolesSearchMembersArgs) (results *rolesSearchMembersResults, err error) {
results = &rolesSearchMembersResults{}
rl, err := lookupRole(ctx, h.rSvc, args)
if err != nil {
return
}
if rl == nil {
return nil, fmt.Errorf("role not found")
}
// Get membership info
mm, err := h.rSvc.MemberList(ctx, rl.ID)
if err != nil {
return
}
if len(mm) == 0 {
results.Users = []*types.User{}
return
}
// Get actual users
uu := make([]string, len(mm))
for i, m := range mm {
uu[i] = cast.ToString(rbac.ResourceID(m.Resource))
}
results.Users, _, err = h.uSvc.Find(ctx, types.UserFilter{
UserID: uu,
})
results.Total = uint64(len(results.Users))
return
}
func (h rolesHandler) eachMember(ctx context.Context, args *rolesEachMemberArgs) (out wfexec.IteratorHandler, err error) {
var (
i = &userSetIterator{}
)
rl, err := lookupRole(ctx, h.rSvc, args)
if err != nil {
return
}
if rl == nil {
return nil, fmt.Errorf("role not found")
}
// Get membership info
mm, err := h.rSvc.MemberList(ctx, rl.ID)
if err != nil {
return
}
if len(mm) == 0 {
i.buffer = []*types.User{}
i.filter = types.UserFilter{}
return i, nil
}
// Get actual users
uu := make([]string, len(mm))
for i, m := range mm {
uu[i] = cast.ToString(rbac.ResourceID(m.Resource))
}
i.buffer, i.filter, err = h.uSvc.Find(ctx, types.UserFilter{
UserID: uu,
})
return i, err
}
func (h rolesHandler) addMember(ctx context.Context, args *rolesAddMemberArgs) (err error) {
role, err := lookupRole(ctx, h.rSvc, &rolesLookupArgs{
hasLookup: args.hasRole,
Lookup: args.Role,
lookupID: args.roleID,
lookupHandle: args.roleHandle,
lookupRes: args.roleRes,
})
if err != nil {
return
}
if role == nil {
return fmt.Errorf("role not found")
}
user, err := lookupUser(ctx, h.uSvc, &usersLookupArgs{
hasLookup: args.hasUser,
Lookup: args.User,
lookupID: args.userID,
lookupHandle: args.userHandle,
lookupEmail: args.userEmail,
lookupRes: args.userRes,
})
if err != nil {
return
}
if role == nil {
return fmt.Errorf("user not found")
}
return h.rSvc.MemberAdd(ctx, role.ID, user.ID)
}
func (h rolesHandler) removeMember(ctx context.Context, args *rolesRemoveMemberArgs) (err error) {
role, err := lookupRole(ctx, h.rSvc, &rolesLookupArgs{
hasLookup: args.hasRole,
Lookup: args.Role,
lookupID: args.roleID,
lookupHandle: args.roleHandle,
lookupRes: args.roleRes,
})
if err != nil {
return
}
if role == nil {
return fmt.Errorf("role not found")
}
user, err := lookupUser(ctx, h.uSvc, &usersLookupArgs{
hasLookup: args.hasUser,
Lookup: args.User,
lookupID: args.userID,
lookupHandle: args.userHandle,
lookupEmail: args.userEmail,
lookupRes: args.userRes,
})
if err != nil {
return
}
if role == nil {
return fmt.Errorf("user not found")
}
return h.rSvc.MemberRemove(ctx, role.ID, user.ID)
}
func (h rolesHandler) search(ctx context.Context, args *rolesSearchArgs) (results *rolesSearchResults, err error) {
results = &rolesSearchResults{}
var (
f = types.RoleFilter{
Query: args.Query,
MemberID: args.MemberID,
Name: args.Name,
Handle: args.Handle,
Labels: args.Labels,
Deleted: filter.State(args.Deleted),
Archived: filter.State(args.Archived),
}
)
if args.hasSort {
if err = f.Sort.Set(args.Sort); err != nil {
return
}
}
if args.hasPageCursor && args.PageCursor != "" {
f.PageCursor = &filter.PagingCursor{}
if err = f.PageCursor.UnmarshalJSON([]byte(args.PageCursor)); err != nil {
return
}
}
if args.hasLabels {
f.Labels = args.Labels
}
if args.hasLimit {
f.Limit = uint(args.Limit)
}
// Total cannot be fetched together with a page cursor
f.IncTotal = args.IncTotal && f.PageCursor == nil
var auxf types.RoleFilter
results.Roles, auxf, err = h.rSvc.Find(ctx, f)
results.Total = uint64(auxf.Total)
return
}
func (h rolesHandler) each(ctx context.Context, args *rolesEachArgs) (out wfexec.IteratorHandler, err error) {
var (
i = &roleSetIterator{}
f = types.RoleFilter{
Query: args.Query,
MemberID: args.MemberID,
Name: args.Name,
Handle: args.Handle,
Labels: args.Labels,
Deleted: filter.State(args.Deleted),
Archived: filter.State(args.Archived),
}
)
if args.hasSort {
if err = f.Sort.Set(args.Sort); err != nil {
return
}
}
if args.hasPageCursor && args.PageCursor != "" {
f.NextPage = &filter.PagingCursor{}
if err = f.NextPage.UnmarshalJSON([]byte(args.PageCursor)); err != nil {
return
}
}
if args.hasLabels {
f.Labels = args.Labels
}
if args.hasLimit {
i.useIterLimit = true
i.iterLimit = uint(args.Limit)
f.Limit = uint(args.Limit)
if args.Limit > uint64(wfexec.MaxIteratorBufferSize) {
f.Limit = wfexec.MaxIteratorBufferSize
}
i.iterLimit = uint(args.Limit)
} else {
f.Limit = wfexec.MaxIteratorBufferSize
}
f.IncTotal = args.IncTotal
i.filter = f
i.loader = func() (err error) {
// Edgecase
if i.filter.PageCursor != nil && i.filter.NextPage == nil {
return
}
i.total += i.ptr
i.ptr = 0
i.filter.PageCursor = i.filter.NextPage
i.filter.NextPage = nil
// Total is fetched with the first page only; a paged query cannot carry it
i.filter.IncTotal = i.filter.IncTotal && i.filter.PageCursor == nil
i.buffer, i.filter, err = h.rSvc.Find(ctx, i.filter)
if i.filter.IncTotal {
i.resTotal = i.filter.Total
}
return
}
// Initial load
return i, i.loader()
}
func (h rolesHandler) create(ctx context.Context, args *rolesCreateArgs) (results *rolesCreateResults, err error) {
results = &rolesCreateResults{}
results.Role, err = h.rSvc.Create(ctx, args.Role)
return
}
func (h rolesHandler) update(ctx context.Context, args *rolesUpdateArgs) (results *rolesUpdateResults, err error) {
results = &rolesUpdateResults{}
results.Role, err = h.rSvc.Update(ctx, args.Role)
return
}
func (h rolesHandler) delete(ctx context.Context, args *rolesDeleteArgs) error {
if id, err := getRoleID(ctx, h.rSvc, args); err != nil {
return err
} else {
return h.rSvc.Delete(ctx, id)
}
}
func (h rolesHandler) recover(ctx context.Context, args *rolesRecoverArgs) error {
if id, err := getRoleID(ctx, h.rSvc, args); err != nil {
return err
} else {
return h.rSvc.Undelete(ctx, id)
}
}
func (h rolesHandler) archive(ctx context.Context, args *rolesArchiveArgs) error {
if id, err := getRoleID(ctx, h.rSvc, args); err != nil {
return err
} else {
return h.rSvc.Archive(ctx, id)
}
}
func (h rolesHandler) unarchive(ctx context.Context, args *rolesUnarchiveArgs) error {
if id, err := getRoleID(ctx, h.rSvc, args); err != nil {
return err
} else {
return h.rSvc.Unarchive(ctx, id)
}
}
func getRoleID(ctx context.Context, svc roleService, args roleLookup) (uint64, error) {
_, ID, _, _ := args.GetLookup()
if ID > 0 {
return ID, nil
}
role, err := lookupRole(ctx, svc, args)
if err != nil {
return 0, err
}
return role.ID, nil
}
func lookupRole(ctx context.Context, svc roleService, args roleLookup) (*types.Role, error) {
_, ID, handle, role := args.GetLookup()
switch {
case role != nil:
return role, nil
case ID > 0:
return svc.FindByID(ctx, ID)
case len(handle) > 0:
return svc.FindByHandle(ctx, handle)
}
return nil, fmt.Errorf("empty lookup params")
}
func (i *roleSetIterator) More(context.Context, *Vars) (bool, error) {
a := wfexec.GenericResourceNextCheck(i.useIterLimit, i.ptr, uint(len(i.buffer)), i.total, i.iterLimit, i.filter.NextPage != nil)
return a, nil
}
func (i *roleSetIterator) Start(context.Context, *Vars) error { i.ptr = 0; return nil }
func (i *roleSetIterator) Next(context.Context, *Vars) (out *Vars, err error) {
if len(i.buffer)-int(i.ptr) <= 0 {
if err = i.loader(); err != nil {
panic(err)
}
}
out = &Vars{}
out.Set("role", Must(NewRole(i.buffer[i.ptr])))
out.Set("index", Must(NewInteger(i.total+i.ptr)))
out.Set("total", Must(NewInteger(i.resTotal)))
i.ptr++
return out, nil
}

View File

@@ -0,0 +1,237 @@
imports:
- github.com/cortezaproject/corteza/server/system/types
- labelTypes github.com/cortezaproject/corteza/server/pkg/label/types
params:
lookup: &lookup
meta: { label: Lookup }
required: true
types:
- { wf: ID, }
- { wf: Handle, }
- { wf: Role, suffix: res }
role: &role
meta: { label: Role }
types:
- { wf: Role }
rvRole: &rvRole
wf: Role
rvUser: &rvUser
wf: User
rvTotal: &rvTotal
wf: UnsignedInteger
rvPageCursor: &rvPageCursor
wf: String
lookupUser: &lookupUser
meta: { label: User }
required: true
types:
- { wf: ID }
- { wf: Handle }
- { wf: String, suffix: email }
- { wf: User, suffix: res }
filterParams: &filterParams
query:
meta: { label: Query }
types:
- { wf: String }
memberID:
meta: { label: Member ID }
types:
- { wf: ID }
handle:
meta: { label: Handle }
types:
- { wf: String }
name:
meta: { label: Name }
types:
- { wf: String }
labels:
meta: { label: Labels }
types:
- { wf: LabelValue }
deleted:
meta: { label: Deleted }
types:
- { wf: UnsignedInteger }
archived:
meta: { label: Archived }
types:
- { wf: UnsignedInteger }
sort:
meta: { label: Sort }
types:
- { wf: String }
limit:
meta: { label: Limit }
types:
- { wf: UnsignedInteger }
incTotal:
meta: { label: Include total }
types:
- { wf: Boolean }
incPageNavigation:
meta: { label: Include page navigation }
types:
- { wf: Boolean }
pageCursor:
meta: { label: Page cursor }
types:
- { wf: String }
labels: &labels
users: "step,workflow"
functions:
lookup:
meta:
short: Role lookup
description: Find specific role by ID or handle
labels:
<<: *labels
params:
lookup: *lookup
results:
role: *rvRole
searchMembers:
meta:
short: Role members search
description: Find members for a specific roleby ID or handle
labels:
<<: *labels
params:
lookup: *lookup
results:
users:
<<: *rvUser
isArray: true
total: *rvTotal
eachMember:
kind: iterator
meta:
short: Iterate over role members
labels:
<<: *labels
params:
lookup: *lookup
results:
user: *rvUser
total: *rvTotal
addMember:
meta:
short: Role membership add
labels:
<<: *labels
params:
role:
<<: *lookup
meta: { label: Role }
user: *lookupUser
removeMember:
meta:
short: Role membership remove
labels:
<<: *labels
params:
role:
<<: *lookup
meta: { label: Role }
user: *lookupUser
search:
meta:
short: Roles search
labels:
<<: *labels
params: *filterParams
results:
roles:
<<: *rvRole
isArray: true
total: *rvTotal
each:
kind: iterator
meta:
short: Roles
params: *filterParams
results:
role: *rvRole
total: *rvTotal
create:
meta:
short: Role creator
labels:
<<: *labels
create: "step"
params:
role:
<<: *role
meta: { label: Role }
required: true
results:
role: *rvRole
update:
meta:
short: Role update
labels:
<<: *labels
update: "step"
params:
role:
<<: *role
meta: { label: Role }
required: true
results:
role: *rvRole
delete:
meta:
short: Role delete
labels:
<<: *labels
delete: "step"
params:
lookup: *lookup
recover:
meta:
short: Role recover
labels:
<<: *labels
recover: "step"
params:
lookup: *lookup
archive:
meta:
short: Role archive
labels:
<<: *labels
archive: "step"
params:
lookup: *lookup
unarchive:
meta:
short: Role unarchive
labels:
<<: *labels
unarchive: "step"
params:
lookup: *lookup

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,341 @@
package automation
import (
"context"
"fmt"
"io"
. "github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/filter"
"github.com/cortezaproject/corteza/server/pkg/wfexec"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/spf13/cast"
)
type (
templateService interface {
FindByID(ctx context.Context, ID uint64) (*types.Template, error)
FindByHandle(ct context.Context, handle string) (*types.Template, error)
FindByAny(ctx context.Context, identifier interface{}) (*types.Template, error)
Search(context.Context, types.TemplateFilter) (types.TemplateSet, types.TemplateFilter, error)
Create(ctx context.Context, tpl *types.Template) (*types.Template, error)
Update(ctx context.Context, tpl *types.Template) (*types.Template, error)
DeleteByID(ctx context.Context, ID uint64) error
UndeleteByID(ctx context.Context, ID uint64) error
Render(ctx context.Context, templateID uint64, dstType string, variables map[string]interface{}, options map[string]string, aux types.TemplateRenderAux) (io.ReadSeeker, error)
}
templatesHandler struct {
reg templatesHandlerRegistry
tSvc templateService
}
templateSetIterator struct {
// Item buffer, current item pointer, and total items traversed
ptr uint
buffer types.TemplateSet
total uint
// When filter limit is set, this constraints it
iterLimit uint
useIterLimit bool
// Item loader for additional chunks
filter types.TemplateFilter
loader func() error
// Total reported by the first page
resTotal uint
}
templateLookup interface {
GetLookup() (bool, uint64, string, *types.Template)
}
)
func TemplatesHandler(reg templatesHandlerRegistry, tSvc templateService) *templatesHandler {
h := &templatesHandler{
reg: reg,
tSvc: tSvc,
}
h.register()
return h
}
func (h templatesHandler) lookup(ctx context.Context, args *templatesLookupArgs) (results *templatesLookupResults, err error) {
results = &templatesLookupResults{}
results.Template, err = lookupTemplate(ctx, h.tSvc, args)
return
}
func (h templatesHandler) search(ctx context.Context, args *templatesSearchArgs) (results *templatesSearchResults, err error) {
results = &templatesSearchResults{}
var (
f = types.TemplateFilter{
Handle: args.Handle,
Type: args.Type,
OwnerID: args.OwnerID,
Partial: args.Partial,
Labels: args.Labels,
}
)
if args.hasSort {
if err = f.Sort.Set(args.Sort); err != nil {
return
}
}
if args.hasPageCursor && args.PageCursor != "" {
f.PageCursor = &filter.PagingCursor{}
if err = f.PageCursor.UnmarshalJSON([]byte(args.PageCursor)); err != nil {
return
}
}
if args.hasLabels {
f.Labels = args.Labels
}
if args.hasLimit {
f.Limit = uint(args.Limit)
}
// Total cannot be fetched together with a page cursor
f.IncTotal = args.IncTotal && f.PageCursor == nil
var auxf types.TemplateFilter
results.Templates, auxf, err = h.tSvc.Search(ctx, f)
results.Total = uint64(auxf.Total)
return
}
func (h templatesHandler) each(ctx context.Context, args *templatesEachArgs) (out wfexec.IteratorHandler, err error) {
var (
i = &templateSetIterator{}
f = types.TemplateFilter{
Handle: args.Handle,
Type: args.Type,
OwnerID: args.OwnerID,
Partial: args.Partial,
Labels: args.Labels,
}
)
if args.hasSort {
if err = f.Sort.Set(args.Sort); err != nil {
return
}
}
if args.hasPageCursor && args.PageCursor != "" {
f.NextPage = &filter.PagingCursor{}
if err = f.NextPage.UnmarshalJSON([]byte(args.PageCursor)); err != nil {
return
}
}
if args.hasLabels {
f.Labels = args.Labels
}
if args.hasLimit {
i.useIterLimit = true
i.iterLimit = uint(args.Limit)
f.Limit = uint(args.Limit)
if args.Limit > uint64(wfexec.MaxIteratorBufferSize) {
f.Limit = wfexec.MaxIteratorBufferSize
}
i.iterLimit = uint(args.Limit)
} else {
f.Limit = wfexec.MaxIteratorBufferSize
}
f.IncTotal = args.IncTotal
i.filter = f
i.loader = func() (err error) {
// Edgecase
if i.filter.PageCursor != nil && i.filter.NextPage == nil {
return
}
i.total += i.ptr
i.ptr = 0
i.filter.PageCursor = i.filter.NextPage
i.filter.NextPage = nil
// Total is fetched with the first page only; a paged query cannot carry it
i.filter.IncTotal = i.filter.IncTotal && i.filter.PageCursor == nil
i.buffer, i.filter, err = h.tSvc.Search(ctx, i.filter)
if i.filter.IncTotal {
i.resTotal = i.filter.Total
}
return
}
// Initial load
return i, i.loader()
}
func (h templatesHandler) create(ctx context.Context, args *templatesCreateArgs) (results *templatesCreateResults, err error) {
results = &templatesCreateResults{}
results.Template, err = h.tSvc.Create(ctx, args.Template)
return
}
func (h templatesHandler) update(ctx context.Context, args *templatesUpdateArgs) (results *templatesUpdateResults, err error) {
results = &templatesUpdateResults{}
results.Template, err = h.tSvc.Update(ctx, args.Template)
return
}
func (h templatesHandler) delete(ctx context.Context, args *templatesDeleteArgs) error {
if id, err := getTemplateID(ctx, h.tSvc, args); err != nil {
return err
} else {
return h.tSvc.DeleteByID(ctx, id)
}
}
func (h templatesHandler) recover(ctx context.Context, args *templatesRecoverArgs) error {
if id, err := getTemplateID(ctx, h.tSvc, args); err != nil {
return err
} else {
return h.tSvc.UndeleteByID(ctx, id)
}
}
func (h templatesHandler) render(ctx context.Context, args *templatesRenderArgs) (*templatesRenderResults, error) {
var err error
opts := make(map[string]string)
if args.hasOptions {
opts, err = cast.ToStringMapStringE(args.Options)
if err != nil {
return nil, err
}
}
tplID, err := getTemplateID(ctx, h.tSvc, args)
if err != nil {
return nil, err
}
var vars map[string]interface{}
if args.hasVariables {
vars = args.Variables.Dict()
}
aux := types.TemplateRenderAux{}
aux.HeaderTemplateID, err = resolveTemplateID(ctx, h.tSvc, args.hasHeaderTemplate, args.headerTemplateID, args.headerTemplateHandle, args.headerTemplateRes)
if err != nil {
return nil, err
}
aux.FooterTemplateID, err = resolveTemplateID(ctx, h.tSvc, args.hasFooterTemplate, args.footerTemplateID, args.footerTemplateHandle, args.footerTemplateRes)
if err != nil {
return nil, err
}
doc, err := h.tSvc.Render(ctx, tplID, args.DocumentType, vars, opts, aux)
if err != nil {
return nil, err
}
rr := &templatesRenderResults{
Document: &renderedDocument{
Document: doc,
Name: args.DocumentName,
Type: args.DocumentType,
},
}
return rr, nil
}
func (i *templateSetIterator) More(context.Context, *Vars) (bool, error) {
return wfexec.GenericResourceNextCheck(i.useIterLimit, i.ptr, uint(len(i.buffer)), i.total, i.iterLimit, i.filter.NextPage != nil), nil
}
func (i *templateSetIterator) Start(context.Context, *Vars) error { i.ptr = 0; return nil }
func (i *templateSetIterator) Next(context.Context, *Vars) (out *Vars, err error) {
if len(i.buffer)-int(i.ptr) <= 0 {
if err = i.loader(); err != nil {
panic(err)
}
}
out = &Vars{}
out.Set("template", Must(NewTemplate(i.buffer[i.ptr])))
out.Set("index", Must(NewInteger(i.total+i.ptr)))
out.Set("total", Must(NewInteger(i.resTotal)))
i.ptr++
return out, nil
}
func lookupTemplate(ctx context.Context, svc templateService, args templateLookup) (*types.Template, error) {
_, ID, handle, template := args.GetLookup()
switch {
case template != nil:
return template, nil
case ID > 0:
return svc.FindByID(ctx, ID)
case len(handle) > 0:
return svc.FindByHandle(ctx, handle)
}
return nil, fmt.Errorf("empty lookup params")
}
func getTemplateID(ctx context.Context, svc templateService, args templateLookup) (uint64, error) {
_, ID, _, _ := args.GetLookup()
if ID > 0 {
return ID, nil
}
tpl, err := lookupTemplate(ctx, svc, args)
if err != nil {
return 0, err
}
return tpl.ID, nil
}
// resolveTemplateID resolves ID, handle or template argument into a template ID
//
// Unset argument resolves into zero ID
func resolveTemplateID(ctx context.Context, svc templateService, has bool, ID uint64, handle string, tpl *types.Template) (uint64, error) {
switch {
case !has:
return 0, nil
case tpl != nil:
return tpl.ID, nil
case ID > 0:
return ID, nil
case len(handle) > 0:
tpl, err := svc.FindByHandle(ctx, handle)
if err != nil {
return 0, err
}
return tpl.ID, nil
}
return 0, nil
}

View File

@@ -0,0 +1,207 @@
imports:
- github.com/cortezaproject/corteza/server/system/types
- labelTypes github.com/cortezaproject/corteza/server/pkg/label/types
snippets:
lookup: &lookup
meta: { label: Lookup }
required: true
types:
- { wf: ID }
- { wf: Handle }
- { wf: Template, suffix: res }
# same as lookup but labeled for the render function where it is the body of the document
bodyLookup: &bodyLookup
required: true
meta:
label: Body template
description: Template rendered as the body of the document
types:
- { wf: ID }
- { wf: Handle }
- { wf: Template, suffix: res }
auxTemplate: &auxTemplate
types:
- { wf: ID }
- { wf: Handle }
- { wf: Template, suffix: res }
template: &template
types:
- { wf: Template }
rvTemplate: &rvTemplate
wf: Template
rvRenderedDocument: &rvRenderedDocument
wf: RenderedDocument
rvTotal: &rvTotal
wf: UnsignedInteger
rvPageCursor: &rvPageCursor
wf: String
filterParams: &filterParams
handle:
meta: { label: Handle }
types:
- { wf: String }
type:
meta: { label: Type }
types:
- { wf: String }
ownerID:
meta: { label: Owner ID }
types:
- { wf: ID }
partial:
meta: { label: Partial }
types:
- { wf: Boolean }
labels:
meta: { label: Labels }
types:
- { wf: LabelValue }
sort:
meta: { label: Sort }
types:
- { wf: String }
limit:
meta: { label: Limit }
types:
- { wf: UnsignedInteger }
incTotal:
meta: { label: Include total }
types:
- { wf: Boolean }
incPageNavigation:
meta: { label: Include page navigation }
types:
- { wf: Boolean }
pageCursor:
meta: { label: Page cursor }
types:
- { wf: String }
labels: &labels
templates: "step,workflow"
functions:
lookup:
meta:
short: Template lookup
description: Find specific template by ID or handle
params:
lookup: *lookup
labels:
<<: *labels
results:
template: *rvTemplate
search:
meta:
short: Templates search
params: *filterParams
labels:
<<: *labels
results:
templates:
<<: *rvTemplate
isArray: true
total: *rvTotal
each:
kind: iterator
meta:
short: Templates
params: *filterParams
labels:
<<: *labels
results:
template: *rvTemplate
total: *rvTotal
create:
meta:
short: Template create
labels:
<<: *labels
params:
template:
<<: *template
meta: { label: Template }
required: true
results:
template: *rvTemplate
update:
meta:
short: Template update
labels:
<<: *labels
params:
template:
<<: *template
meta: { label: Template }
required: true
results:
template: *rvTemplate
delete:
labels:
<<: *labels
delete: "step"
meta:
short: Template delete
params:
lookup: *lookup
recover:
meta:
short: Template recover
labels:
<<: *labels
recover: "step"
params:
lookup: *lookup
render:
meta:
short: Template render
labels:
<<: *labels
render: "step"
params:
lookup: *bodyLookup
headerTemplate:
<<: *auxTemplate
meta:
label: Header template
description: Overrides the header template set on the body template; used by drivers that support it
footerTemplate:
<<: *auxTemplate
meta:
label: Footer template
description: Overrides the footer template set on the body template; used by drivers that support it
documentName:
meta: { label: Document name }
types:
- { wf: String }
documentType:
meta: { label: Document type }
types:
- { wf: String }
variables:
meta: { label: Variables }
types:
- { wf: Vars, go: '*expr.Vars' }
options:
meta: { label: Options }
types:
- { wf: RenderOptions }
results:
document: *rvRenderedDocument

View File

@@ -0,0 +1,141 @@
package automation
import (
"context"
"fmt"
"io"
"testing"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/stretchr/testify/require"
)
type (
templateServiceMocked struct {
templateService
byHandle func(context.Context, string) (*types.Template, error)
rendered func(uint64, types.TemplateRenderAux)
}
)
func (svc templateServiceMocked) FindByHandle(ctx context.Context, h string) (*types.Template, error) {
return svc.byHandle(ctx, h)
}
func (svc templateServiceMocked) Render(_ context.Context, templateID uint64, _ string, _ map[string]interface{}, _ map[string]string, aux types.TemplateRenderAux) (io.ReadSeeker, error) {
svc.rendered(templateID, aux)
return nil, nil
}
func TestResolveTemplateID(t *testing.T) {
var (
ctx = context.Background()
svc = templateServiceMocked{
byHandle: func(_ context.Context, h string) (*types.Template, error) {
if h != "header" {
return nil, fmt.Errorf("unknown template %q", h)
}
return &types.Template{ID: 200}, nil
},
}
)
t.Run("unset argument", func(t *testing.T) {
req := require.New(t)
ID, err := resolveTemplateID(ctx, svc, false, 0, "", nil)
req.NoError(err)
req.Zero(ID)
})
t.Run("by ID", func(t *testing.T) {
req := require.New(t)
ID, err := resolveTemplateID(ctx, svc, true, 100, "", nil)
req.NoError(err)
req.Equal(uint64(100), ID)
})
t.Run("by handle", func(t *testing.T) {
req := require.New(t)
ID, err := resolveTemplateID(ctx, svc, true, 0, "header", nil)
req.NoError(err)
req.Equal(uint64(200), ID)
})
t.Run("by template", func(t *testing.T) {
req := require.New(t)
ID, err := resolveTemplateID(ctx, svc, true, 0, "", &types.Template{ID: 300})
req.NoError(err)
req.Equal(uint64(300), ID)
})
t.Run("unknown handle", func(t *testing.T) {
req := require.New(t)
_, err := resolveTemplateID(ctx, svc, true, 0, "missing", nil)
req.Error(err)
})
}
func TestRenderPassesAuxTemplates(t *testing.T) {
var (
req = require.New(t)
renderedID uint64
renderedAux types.TemplateRenderAux
svc = templateServiceMocked{
byHandle: func(_ context.Context, h string) (*types.Template, error) {
return &types.Template{ID: 200}, nil
},
rendered: func(ID uint64, aux types.TemplateRenderAux) {
renderedID, renderedAux = ID, aux
},
}
h = templatesHandler{tSvc: svc}
)
args := &templatesRenderArgs{
hasLookup: true,
lookupID: 100,
DocumentType: "application/pdf",
hasHeaderTemplate: true,
headerTemplateID: 300,
hasFooterTemplate: true,
footerTemplateHandle: "footer",
}
_, err := h.render(context.Background(), args)
req.NoError(err)
req.Equal(uint64(100), renderedID)
req.Equal(uint64(300), renderedAux.HeaderTemplateID)
req.Equal(uint64(200), renderedAux.FooterTemplateID)
}
func TestRenderWithoutAuxTemplates(t *testing.T) {
var (
req = require.New(t)
renderedAux types.TemplateRenderAux
svc = templateServiceMocked{
rendered: func(_ uint64, aux types.TemplateRenderAux) {
renderedAux = aux
},
}
h = templatesHandler{tSvc: svc}
)
_, err := h.render(context.Background(), &templatesRenderArgs{hasLookup: true, lookupID: 100})
req.NoError(err)
// nothing set, service falls back to the template's own meta
req.Zero(renderedAux.HeaderTemplateID)
req.Zero(renderedAux.FooterTemplateID)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,383 @@
package automation
import (
"context"
"fmt"
"strconv"
. "github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/filter"
"github.com/cortezaproject/corteza/server/pkg/wfexec"
"github.com/cortezaproject/corteza/server/system/types"
)
type (
userService interface {
FindByID(ctx context.Context, userID uint64) (*types.User, error)
FindByHandle(ctx context.Context, handle string) (*types.User, error)
FindByEmail(ctx context.Context, email string) (*types.User, error)
Find(ctx context.Context, filter types.UserFilter) (set types.UserSet, f types.UserFilter, err error)
Create(ctx context.Context, user *types.User) (*types.User, error)
Update(ctx context.Context, user *types.User) (*types.User, error)
Delete(ctx context.Context, id uint64) error
Suspend(ctx context.Context, id uint64) error
Unsuspend(ctx context.Context, id uint64) error
Undelete(ctx context.Context, id uint64) error
}
usersHandler struct {
reg usersHandlerRegistry
uSvc userService
rSvc roleService
}
userSetIterator struct {
// Item buffer, current item pointer, and total items traversed
ptr uint
buffer types.UserSet
total uint
// When filter limit is set, this constraints it
iterLimit uint
useIterLimit bool
// Item loader for additional chunks
filter types.UserFilter
loader func() error
// Total reported by the first page
resTotal uint
}
userLookup interface {
GetLookup() (bool, uint64, string, string, *types.User)
}
)
func UsersHandler(reg usersHandlerRegistry, uSvc userService, rSvc roleService) *usersHandler {
h := &usersHandler{
reg: reg,
uSvc: uSvc,
rSvc: rSvc,
}
h.register()
return h
}
func (h usersHandler) lookup(ctx context.Context, args *usersLookupArgs) (results *usersLookupResults, err error) {
results = &usersLookupResults{}
results.User, err = lookupUser(ctx, h.uSvc, args)
return
}
func (h usersHandler) searchMembership(ctx context.Context, args *usersSearchMembershipArgs) (results *usersSearchMembershipResults, err error) {
results = &usersSearchMembershipResults{}
u, err := lookupUser(ctx, h.uSvc, args)
if err != nil {
return
}
if u == nil {
return nil, fmt.Errorf("user not found")
}
// Get the roles
mm, err := h.rSvc.Membership(ctx, u.ID)
if err != nil {
return
}
if len(mm) == 0 {
results.Roles = []*types.Role{}
return
}
rr := make([]string, len(mm))
for i, r := range mm {
rr[i] = strconv.FormatUint(r.RoleID, 10)
}
results.Roles, _, err = h.rSvc.Find(ctx, types.RoleFilter{
RoleID: rr,
})
results.Total = uint64(len(results.Roles))
return
}
func (h usersHandler) checkMembership(ctx context.Context, args *usersCheckMembershipArgs) (results *usersCheckMembershipResults, err error) {
results = &usersCheckMembershipResults{}
// Resolve user
u, err := lookupUser(ctx, h.uSvc, usersLookupArgs{
hasLookup: args.hasUser,
Lookup: args.User,
lookupID: args.userID,
lookupHandle: args.userHandle,
lookupEmail: args.userEmail,
lookupRes: args.userRes,
})
if err != nil {
return
}
if u == nil {
return nil, fmt.Errorf("user not found")
}
// Get user membershio
mm, err := h.rSvc.Membership(ctx, u.ID)
if err != nil {
return
}
if len(mm) == 0 {
return &usersCheckMembershipResults{
Member: false,
}, nil
}
// Resolve role
r, err := lookupRole(ctx, h.rSvc, rolesLookupArgs{
hasLookup: args.hasRole,
Lookup: args.Role,
lookupID: args.roleID,
lookupHandle: args.roleHandle,
lookupRes: args.roleRes,
})
if err != nil {
return
}
if r == nil {
return nil, fmt.Errorf("role not found")
}
// Check if there
for _, m := range mm {
if m.RoleID == r.ID {
return &usersCheckMembershipResults{
Member: true,
}, nil
}
}
return &usersCheckMembershipResults{
Member: false,
}, nil
}
func (h usersHandler) search(ctx context.Context, args *usersSearchArgs) (results *usersSearchResults, err error) {
results = &usersSearchResults{}
var (
f = types.UserFilter{
Query: args.Query,
Email: args.Email,
Handle: args.Handle,
Labels: args.Labels,
Deleted: filter.State(args.Deleted),
Suspended: filter.State(args.Suspended),
}
)
if args.hasSort {
if err = f.Sort.Set(args.Sort); err != nil {
return
}
}
if args.hasPageCursor && args.PageCursor != "" {
f.PageCursor = &filter.PagingCursor{}
if err = f.PageCursor.UnmarshalJSON([]byte(args.PageCursor)); err != nil {
return
}
}
if args.hasLabels {
f.Labels = args.Labels
}
if args.hasLimit {
f.Limit = uint(args.Limit)
}
// Total cannot be fetched together with a page cursor
f.IncTotal = args.IncTotal && f.PageCursor == nil
var auxf types.UserFilter
results.Users, auxf, err = h.uSvc.Find(ctx, f)
results.Total = uint64(auxf.Total)
return
}
func (h usersHandler) each(ctx context.Context, args *usersEachArgs) (out wfexec.IteratorHandler, err error) {
var (
i = &userSetIterator{}
f = types.UserFilter{
Query: args.Query,
Email: args.Email,
Handle: args.Handle,
Labels: args.Labels,
Deleted: filter.State(args.Deleted),
Suspended: filter.State(args.Suspended),
}
)
if args.hasSort {
if err = f.Sort.Set(args.Sort); err != nil {
return
}
}
if args.hasPageCursor && args.PageCursor != "" {
f.NextPage = &filter.PagingCursor{}
if err = f.NextPage.UnmarshalJSON([]byte(args.PageCursor)); err != nil {
return
}
}
f.IncTotal = args.IncTotal
if args.hasLabels {
f.Labels = args.Labels
}
if args.hasLimit {
i.useIterLimit = true
i.iterLimit = uint(args.Limit)
f.Limit = uint(args.Limit)
if args.Limit > uint64(wfexec.MaxIteratorBufferSize) {
f.Limit = wfexec.MaxIteratorBufferSize
}
i.iterLimit = uint(args.Limit)
} else {
f.Limit = wfexec.MaxIteratorBufferSize
}
i.filter = f
i.loader = func() (err error) {
// Edgecase
if i.filter.PageCursor != nil && i.filter.NextPage == nil {
return
}
i.total += i.ptr
i.ptr = 0
i.filter.PageCursor = i.filter.NextPage
i.filter.NextPage = nil
// Total is fetched with the first page only; a paged query cannot carry it
i.filter.IncTotal = i.filter.IncTotal && i.filter.PageCursor == nil
i.buffer, i.filter, err = h.uSvc.Find(ctx, i.filter)
if i.filter.IncTotal {
i.resTotal = i.filter.Total
}
return
}
// Initial load
return i, i.loader()
}
func (h usersHandler) create(ctx context.Context, args *usersCreateArgs) (results *usersCreateResults, err error) {
results = &usersCreateResults{}
results.User, err = h.uSvc.Create(ctx, args.User)
return
}
func (h usersHandler) update(ctx context.Context, args *usersUpdateArgs) (results *usersUpdateResults, err error) {
results = &usersUpdateResults{}
results.User, err = h.uSvc.Update(ctx, args.User)
return
}
func (h usersHandler) delete(ctx context.Context, args *usersDeleteArgs) error {
if id, err := getUserID(ctx, h.uSvc, args); err != nil {
return err
} else {
return h.uSvc.Delete(ctx, id)
}
}
func (h usersHandler) recover(ctx context.Context, args *usersRecoverArgs) error {
if id, err := getUserID(ctx, h.uSvc, args); err != nil {
return err
} else {
return h.uSvc.Undelete(ctx, id)
}
}
func (h usersHandler) suspend(ctx context.Context, args *usersSuspendArgs) error {
if id, err := getUserID(ctx, h.uSvc, args); err != nil {
return err
} else {
return h.uSvc.Suspend(ctx, id)
}
}
func (h usersHandler) unsuspend(ctx context.Context, args *usersUnsuspendArgs) error {
if id, err := getUserID(ctx, h.uSvc, args); err != nil {
return err
} else {
return h.uSvc.Unsuspend(ctx, id)
}
}
func getUserID(ctx context.Context, svc userService, args userLookup) (uint64, error) {
_, ID, _, _, _ := args.GetLookup()
if ID > 0 {
return ID, nil
}
user, err := lookupUser(ctx, svc, args)
if err != nil {
return 0, err
}
return user.ID, nil
}
func lookupUser(ctx context.Context, svc userService, args userLookup) (*types.User, error) {
_, ID, handle, email, user := args.GetLookup()
switch {
case user != nil:
return user, nil
case ID > 0:
return svc.FindByID(ctx, ID)
case len(email) > 0:
return svc.FindByEmail(ctx, email)
case len(handle) > 0:
return svc.FindByHandle(ctx, handle)
}
return nil, fmt.Errorf("empty lookup params")
}
func (i *userSetIterator) More(context.Context, *Vars) (bool, error) {
return wfexec.GenericResourceNextCheck(i.useIterLimit, i.ptr, uint(len(i.buffer)), i.total, i.iterLimit, i.filter.NextPage != nil), nil
}
func (i *userSetIterator) Start(context.Context, *Vars) error { i.ptr = 0; return nil }
func (i *userSetIterator) Next(context.Context, *Vars) (out *Vars, err error) {
if len(i.buffer)-int(i.ptr) <= 0 {
if err = i.loader(); err != nil {
panic(err)
}
}
out = &Vars{}
out.Set("user", Must(NewUser(i.buffer[i.ptr])))
out.Set("index", Must(NewInteger(i.total+i.ptr)))
out.Set("total", Must(NewInteger(i.resTotal)))
i.ptr++
return out, nil
}

View File

@@ -0,0 +1,215 @@
imports:
- github.com/cortezaproject/corteza/server/system/types
- labelTypes github.com/cortezaproject/corteza/server/pkg/label/types
snippets:
lookup: &lookup
meta: { label: Lookup }
required: true
types:
- { wf: ID }
- { wf: Handle }
- { wf: String, suffix: email }
- { wf: User, suffix: res }
lookupRole: &lookupRole
meta: { label: Role }
required: true
types:
- { wf: ID, }
- { wf: Handle, }
- { wf: Role, suffix: res }
user: &user
types:
- { wf: User }
rvUser: &rvUser
wf: User
rvRole: &rvRole
wf: Role
rvTotal: &rvTotal
wf: UnsignedInteger
rvInclusion: &rvInclusion
wf: Boolean
rvPageCursor: &rvPageCursor
wf: String
filterParams: &filterParams
query:
meta: { label: Query }
types:
- { wf: String }
email:
meta: { label: Email }
types:
- { wf: String }
handle:
meta: { label: Handle }
types:
- { wf: String }
labels:
meta: { label: Labels }
types:
- { wf: LabelValue }
deleted:
meta: { label: Deleted }
types:
- { wf: UnsignedInteger }
suspended:
meta: { label: Suspended }
types:
- { wf: UnsignedInteger }
sort:
meta: { label: Sort }
types:
- { wf: String }
limit:
meta: { label: Limit }
types:
- { wf: UnsignedInteger }
incTotal:
meta: { label: Include total }
types:
- { wf: Boolean }
incPageNavigation:
meta: { label: Include page navigation }
types:
- { wf: Boolean }
pageCursor:
meta: { label: Page cursor }
types:
- { wf: String }
labels: &labels
users: "step,workflow"
functions:
lookup:
meta:
short: User lookup
description: Find specific user by ID, handle or string
params:
lookup: *lookup
labels:
<<: *labels
results:
user: *rvUser
searchMembership:
meta:
short: User role search
description: Search user role membership by ID, handle or string
params:
lookup: *lookup
labels:
<<: *labels
results:
roles:
<<: *rvRole
isArray: true
total: *rvTotal
checkMembership:
meta:
short: User membership check
description: Find user role membership by ID, handle or string
params:
user:
<<: *lookup
meta: { label: User }
role: *lookupRole
labels:
<<: *labels
results:
member: *rvInclusion
search:
meta:
short: User search
params: *filterParams
labels:
<<: *labels
results:
users:
<<: *rvUser
isArray: true
total: *rvTotal
each:
kind: iterator
meta:
short: Users
params: *filterParams
labels:
<<: *labels
results:
user: *rvUser
total: *rvTotal
create:
meta:
short: User create
labels:
<<: *labels
params:
user:
<<: *user
meta: { label: User }
required: true
results:
user: *rvUser
update:
meta:
short: User update
labels:
<<: *labels
params:
user:
<<: *user
meta: { label: User }
required: true
results:
user: *rvUser
delete:
labels:
<<: *labels
delete: "step"
meta:
short: User delete
params:
lookup: *lookup
recover:
labels:
<<: *labels
recover: "step"
meta:
short: User recover
params:
lookup: *lookup
suspend:
labels:
<<: *labels
suspend: "step"
meta:
short: User suspend
params:
lookup: *lookup
unsuspend:
labels:
<<: *labels
unsuspend: "step"
meta:
short: User unsuspend
params:
lookup: *lookup

View File

@@ -0,0 +1,113 @@
package automation
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// system/automation/valuestore_handler.yaml
import (
"context"
atypes "github.com/cortezaproject/corteza/server/automation/types"
"github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/wfexec"
)
var _ wfexec.ExecResponse
type (
valuestoreHandlerRegistry interface {
AddFunctions(ff ...*atypes.Function)
Type(ref string) expr.Type
}
)
func (h valuestoreHandler) register() {
h.reg.AddFunctions(
h.Env(),
)
}
type (
valuestoreEnvArgs struct {
hasKey bool
Key string
}
valuestoreEnvResults struct {
Value interface{}
}
)
// Env function Get ENV variable
//
// expects implementation of env function:
//
// func (h valuestoreHandler) env(ctx context.Context, args *valuestoreEnvArgs) (results *valuestoreEnvResults, err error) {
// return
// }
func (h valuestoreHandler) Env() *atypes.Function {
return &atypes.Function{
Ref: "valuestoreEnv",
Kind: "function",
Labels: map[string]string{"templates": "step,workflow"},
Meta: &atypes.FunctionMeta{
Short: "Get ENV variable",
Description: "Get ENV variable for the specified key. If the key doesn't correspond to any value, nil is returned",
},
Parameters: []*atypes.Param{
{
Name: "key",
Types: []string{"String"}, Required: true,
Meta: &atypes.ParamMeta{
Label: "Key",
},
},
},
Results: []*atypes.Param{
{
Name: "value",
Types: []string{"Any"},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &valuestoreEnvArgs{
hasKey: in.Has("key"),
}
)
if err = in.Decode(args); err != nil {
return
}
var results *valuestoreEnvResults
if results, err = h.env(ctx, args); err != nil {
return
}
out = &expr.Vars{}
{
// converting results.Value (interface{}) to Any
var (
tval expr.TypedValue
)
if tval, err = h.reg.Type("Any").Cast(results.Value); err != nil {
return
} else if err = expr.Assign(out, "value", tval); err != nil {
return
}
}
return
},
}
}

View File

@@ -0,0 +1,33 @@
package automation
import (
"context"
)
type (
envGetter interface {
Env(k string) (v any)
}
valuestoreHandler struct {
reg valuestoreHandlerRegistry
getter envGetter
}
)
func ValuestoreHandler(reg valuestoreHandlerRegistry, getter envGetter) *valuestoreHandler {
h := &valuestoreHandler{
reg: reg,
getter: getter,
}
h.register()
return h
}
func (h valuestoreHandler) env(ctx context.Context, args *valuestoreEnvArgs) (results *valuestoreEnvResults, err error) {
results = &valuestoreEnvResults{
Value: h.getter.Env(args.Key),
}
return
}

View File

@@ -0,0 +1,21 @@
snippets:
labels: &labels
templates: "step,workflow"
functions:
env:
meta:
short: Get ENV variable
description: Get ENV variable for the specified key. If the key doesn't correspond to any value, nil is returned
params:
key:
meta: { label: Key }
required: true
types:
- { wf: String }
labels:
<<: *labels
results:
value:
wf: Any