feat(actions): add token customization flow and extend functionally with modules (#4337)

* fix: potential memory leak

* feat(actions): possibility to parse json
feat(actions): possibility to perform http calls

* add query call

* feat(api): list flow and trigger types
fix(api): switch flow and trigger types to dynamic objects

* fix(translations): add action translations

* use `domain.FlowType`

* localizers

* localization

* trigger types

* options on `query.Action`

* add functions for actions

* feat: management api: add list flow and trigger  (#4352)

* console changes

* cleanup

* fix: wrong localization

Co-authored-by: Max Peintner <max@caos.ch>

* id token works

* check if claims not nil

* feat(actions): metadata api

* refactor(actions): modules

* fix: allow prerelease

* fix: test

* feat(actions): deny list for http hosts

* feat(actions): deny list for http hosts

* refactor: actions

* fix: different error ids

* fix: rename statusCode to status

* Actions objects as options (#4418)

* fix: rename statusCode to status

* fix(actions): objects as options

* fix(actions): objects as options

* fix(actions): set fields

* add http client to old actions

* fix(actions): add log module

* fix(actions): add user to context where possible

* fix(actions): add user to ctx in external authorization/pre creation

* fix(actions): query correct flow in claims

* test: actions

* fix(id-generator): panic if no machine id

* tests

* maybe this?

* fix linting

* refactor: improve code

* fix: metadata and usergrant usage in actions

* fix: appendUserGrant

* fix: allowedToFail and timeout in action execution

* fix: allowed to fail in token complement flow

* docs: add action log claim

* Update defaults.yaml

* fix log claim

* remove prerelease build

Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Silvan
2022-10-06 14:23:59 +02:00
committed by GitHub
parent bffb10a4b4
commit 43fb3fd1a6
62 changed files with 2806 additions and 636 deletions

View File

@@ -1,112 +1,113 @@
package actions
import (
"context"
"errors"
"time"
"fmt"
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/console"
"github.com/dop251/goja_nodejs/require"
"github.com/zitadel/logging"
z_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query"
)
var ErrHalt = errors.New("interrupt")
type Config struct {
HTTP HTTPConfig
}
type jsAction func(*Context, *API) error
var (
ErrHalt = errors.New("interrupt")
)
func Run(ctx *Context, api *API, script, name string, timeout time.Duration, allowedToFail bool) error {
if timeout <= 0 || timeout > 20 {
timeout = 20 * time.Second
}
prepareTimeout := timeout
if prepareTimeout > 5 {
prepareTimeout = 5 * time.Second
}
vm, err := prepareRun(script, prepareTimeout)
type jsAction func(fields, fields) error
func Run(ctx context.Context, ctxParam contextFields, apiParam apiFields, script, name string, opts ...Option) error {
config, err := prepareRun(ctx, ctxParam, apiParam, script, opts)
if err != nil {
return err
}
var fn jsAction
jsFn := vm.Get(name)
jsFn := config.vm.Get(name)
if jsFn == nil {
return errors.New("function not found")
}
err = vm.ExportTo(jsFn, &fn)
err = config.vm.ExportTo(jsFn, &fn)
if err != nil {
return err
}
t := setInterrupt(vm, timeout)
t := config.Start()
defer func() {
t.Stop()
}()
errCh := make(chan error)
go func() {
defer func() {
r := recover()
if r != nil && !allowedToFail {
err, ok := r.(error)
if !ok {
e, ok := r.(string)
if ok {
err = errors.New(e)
}
}
errCh <- err
return
}
}()
err = fn(ctx, api)
if err != nil && !allowedToFail {
errCh <- err
return
}
errCh <- nil
}()
return <-errCh
return executeFn(config, fn)
}
func newRuntime() *goja.Runtime {
vm := goja.New()
func prepareRun(ctx context.Context, ctxParam contextFields, apiParam apiFields, script string, opts []Option) (config *runConfig, err error) {
config = newRunConfig(ctx, opts...)
if config.timeout == 0 {
return nil, z_errs.ThrowInternal(nil, "ACTIO-uCpCx", "Errrors.Internal")
}
t := config.Prepare()
defer func() {
t.Stop()
}()
if ctxParam != nil {
ctxParam(config.ctxParam)
}
if apiParam != nil {
apiParam(config.apiParam)
}
printer := console.PrinterFunc(func(s string) {
logging.Log("ACTIONS-dfgg2").Debug(s)
})
registry := new(require.Registry)
registry.Enable(vm)
registry.RegisterNativeModule("console", console.RequireWithPrinter(printer))
console.Enable(vm)
registry.Enable(config.vm)
return vm
}
for name, loader := range config.modules {
registry.RegisterNativeModule(name, loader)
}
func prepareRun(script string, timeout time.Duration) (*goja.Runtime, error) {
vm := newRuntime()
t := setInterrupt(vm, timeout)
// overload error if function panics
defer func() {
t.Stop()
}()
errCh := make(chan error)
go func() {
defer func() {
r := recover()
if r != nil {
errCh <- r.(error)
return
}
}()
_, err := vm.RunString(script)
if err != nil {
errCh <- err
r := recover()
if r != nil {
err = r.(error)
return
}
errCh <- nil
}()
return vm, <-errCh
_, err = config.vm.RunString(script)
return config, err
}
func setInterrupt(vm *goja.Runtime, timeout time.Duration) *time.Timer {
vm.ClearInterrupt()
return time.AfterFunc(timeout, func() {
vm.Interrupt(ErrHalt)
})
func executeFn(config *runConfig, fn jsAction) (err error) {
defer func() {
r := recover()
if r != nil && !config.allowedToFail {
var ok bool
if err, ok = r.(error); ok {
return
}
e, ok := r.(string)
if ok {
err = errors.New(e)
return
}
err = fmt.Errorf("unknown error occured: %v", r)
}
}()
err = fn(config.ctxParam.fields, config.apiParam.fields)
if err != nil && !config.allowedToFail {
return err
}
return nil
}
func ActionToOptions(a *query.Action) []Option {
opts := make([]Option, 0, 1)
if a.AllowedToFail {
opts = append(opts, WithAllowedToFail())
}
return opts
}