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
}

View File

@@ -0,0 +1,69 @@
package actions
import (
"context"
"errors"
"testing"
"time"
"github.com/dop251/goja"
)
func TestRun(t *testing.T) {
type args struct {
timeout time.Duration
api apiFields
ctx contextFields
script string
name string
opts []Option
}
tests := []struct {
name string
args args
wantErr func(error) bool
}{
{
name: "simple script",
args: args{
api: nil,
script: `
function testFunc() {
for (i = 0; i < 10; i++) {}
}`,
name: "testFunc",
opts: []Option{},
},
wantErr: func(err error) bool { return err == nil },
},
{
name: "throw error",
args: args{
api: nil,
script: "function testFunc() {throw 'some error'}",
name: "testFunc",
opts: []Option{},
},
wantErr: func(err error) bool {
gojaErr := new(goja.Exception)
if errors.As(err, &gojaErr) {
return gojaErr.Value().String() == "some error"
}
return false
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.timeout == 0 {
tt.args.timeout = 10 * time.Second
}
ctx, cancel := context.WithTimeout(context.Background(), tt.args.timeout)
if err := Run(ctx, tt.args.ctx, tt.args.api, tt.args.script, tt.args.name, tt.args.opts...); !tt.wantErr(err) {
t.Errorf("Run() unexpected error = (%[1]T) %[1]v", err)
}
cancel()
})
}
}

View File

@@ -1,105 +1,19 @@
package actions
import (
"github.com/zitadel/zitadel/internal/domain"
"golang.org/x/text/language"
)
type API map[string]interface{}
func (a API) set(name string, value interface{}) {
map[string]interface{}(a)[name] = value
type apiConfig struct {
FieldConfig
}
func (a *API) SetHuman(human *domain.Human) *API {
a.set("setFirstName", func(firstName string) {
human.FirstName = firstName
})
a.set("setLastName", func(lastName string) {
human.LastName = lastName
})
a.set("setNickName", func(nickName string) {
human.NickName = nickName
})
a.set("setDisplayName", func(displayName string) {
human.DisplayName = displayName
})
a.set("setPreferredLanguage", func(preferredLanguage string) {
human.PreferredLanguage = language.Make(preferredLanguage)
})
a.set("setGender", func(gender domain.Gender) {
human.Gender = gender
})
a.set("setUsername", func(username string) {
human.Username = username
})
a.set("setEmail", func(email string) {
if human.Email == nil {
human.Email = &domain.Email{}
type apiFields func(*apiConfig)
func WithAPIFields(opts ...FieldOption) apiFields {
return func(p *apiConfig) {
if p.fields == nil {
p.fields = fields{}
}
human.Email.EmailAddress = email
})
a.set("setEmailVerified", func(verified bool) {
if human.Email == nil {
return
}
human.Email.IsEmailVerified = verified
})
a.set("setPhone", func(email string) {
if human.Phone == nil {
human.Phone = &domain.Phone{}
}
human.Phone.PhoneNumber = email
})
a.set("setPhoneVerified", func(verified bool) {
if human.Phone == nil {
return
}
human.Phone.IsPhoneVerified = verified
})
return a
}
func (a *API) SetExternalUser(user *domain.ExternalUser) *API {
a.set("setFirstName", func(firstName string) {
user.FirstName = firstName
})
a.set("setLastName", func(lastName string) {
user.LastName = lastName
})
a.set("setNickName", func(nickName string) {
user.NickName = nickName
})
a.set("setDisplayName", func(displayName string) {
user.DisplayName = displayName
})
a.set("setPreferredLanguage", func(preferredLanguage string) {
user.PreferredLanguage = language.Make(preferredLanguage)
})
a.set("setPreferredUsername", func(username string) {
user.PreferredUsername = username
})
a.set("setEmail", func(email string) {
user.Email = email
})
a.set("setEmailVerified", func(verified bool) {
user.IsEmailVerified = verified
})
a.set("setPhone", func(phone string) {
user.Phone = phone
})
a.set("setPhoneVerified", func(verified bool) {
user.IsPhoneVerified = verified
})
return a
}
func (a *API) SetMetadata(metadata *[]*domain.Metadata) *API {
a.set("metadata", metadata)
return a
}
func (a *API) SetUserGrants(usergrants *[]UserGrant) *API {
a.set("userGrants", usergrants)
return a
for _, opt := range opts {
opt(&p.FieldConfig)
}
}
}

View File

@@ -0,0 +1,86 @@
package actions
import (
"context"
"time"
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/require"
"github.com/zitadel/logging"
)
const (
maxPrepareTimeout = 5 * time.Second
)
type Option func(*runConfig)
func WithAllowedToFail() Option {
return func(c *runConfig) {
c.allowedToFail = true
}
}
type runConfig struct {
allowedToFail bool
timeout,
prepareTimeout time.Duration
modules map[string]require.ModuleLoader
vm *goja.Runtime
ctxParam *ctxConfig
apiParam *apiConfig
}
func newRunConfig(ctx context.Context, opts ...Option) *runConfig {
deadline, ok := ctx.Deadline()
if !ok {
logging.Warn("no timeout set on action run")
}
vm := goja.New()
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
config := &runConfig{
timeout: time.Until(deadline),
prepareTimeout: maxPrepareTimeout,
modules: map[string]require.ModuleLoader{},
vm: vm,
ctxParam: &ctxConfig{
FieldConfig: FieldConfig{
Runtime: vm,
fields: fields{},
},
},
apiParam: &apiConfig{
FieldConfig: FieldConfig{
Runtime: vm,
fields: fields{},
},
},
}
for _, opt := range opts {
opt(config)
}
if config.prepareTimeout > config.timeout {
config.prepareTimeout = config.timeout
}
return config
}
func (c *runConfig) Start() *time.Timer {
c.vm.ClearInterrupt()
return time.AfterFunc(c.timeout, func() {
c.vm.Interrupt(ErrHalt)
})
}
func (c *runConfig) Prepare() *time.Timer {
c.vm.ClearInterrupt()
return time.AfterFunc(c.prepareTimeout, func() {
c.vm.Interrupt(ErrHalt)
})
}

View File

@@ -1,36 +1,19 @@
package actions
import (
"encoding/json"
"github.com/zitadel/oidc/v2/pkg/oidc"
)
type Context map[string]interface{}
func (c Context) set(name string, value interface{}) {
map[string]interface{}(c)[name] = value
type ctxConfig struct {
FieldConfig
}
func (c *Context) SetToken(t *oidc.Tokens) *Context {
if t == nil {
return c
type contextFields func(*ctxConfig)
func SetContextFields(opts ...FieldOption) contextFields {
return func(p *ctxConfig) {
if p.fields == nil {
p.fields = fields{}
}
for _, opt := range opts {
opt(&p.FieldConfig)
}
}
if t.Token != nil && t.Token.AccessToken != "" {
c.set("accessToken", t.AccessToken)
}
if t.IDToken != "" {
c.set("idToken", t.IDToken)
}
if t.IDTokenClaims != nil {
c.set("getClaim", func(claim string) interface{} { return t.IDTokenClaims.GetClaim(claim) })
c.set("claimsJSON", func() (string, error) {
c, err := json.Marshal(t.IDTokenClaims)
if err != nil {
return "", err
}
return string(c), nil
})
}
return c
}

View File

@@ -0,0 +1,71 @@
package actions
import (
"github.com/dop251/goja"
"github.com/zitadel/logging"
)
type fields map[string]interface{}
type FieldOption func(*FieldConfig)
type FieldConfig struct {
fields
Runtime *goja.Runtime
}
func SetFields(name string, values ...interface{}) FieldOption {
return func(p *FieldConfig) {
if len(values) == 0 {
return
}
for _, value := range values {
val, ok := value.(FieldOption)
// is the lowest field and can be set without further checks
if !ok {
// {
// "value": "some value"
// }
p.set(name, value)
continue
}
var field fields
if f, ok := p.fields[name]; ok {
// check if the found field is an object
if field, ok = f.(fields); !ok {
// panic because overwriting fields is not allowed
logging.WithFields("sub", name).Warn("sub is not an object")
panic("unable to prepare parameter")
}
} else {
// field does not exist so far.
// sub object for field can be created
field = fields{}
p.fields[name] = field
}
fieldParam := FieldConfig{
Runtime: p.Runtime,
fields: field,
}
val(&fieldParam)
}
}
}
func (f *FieldConfig) set(name string, value interface{}) {
if _, ok := f.fields[name]; ok {
logging.WithFields("name", name).Error("tried to overwrite field")
panic("tried to overwrite field")
}
v, ok := value.(func(*FieldConfig) interface{})
if ok {
f.fields[name] = v(f)
return
}
f.fields[name] = value
}

View File

@@ -0,0 +1,186 @@
package actions
import (
"fmt"
"testing"
"github.com/dop251/goja"
)
func TestSetFields(t *testing.T) {
primitveFn := func(a string) { fmt.Println(a) }
complexFn := func(*FieldConfig) interface{} {
return primitveFn
}
tests := []struct {
name string
setFields FieldOption
want fields
shouldPanic bool
}{
{
name: "field is simple value",
setFields: SetFields("value", 5),
want: fields{
"value": 5,
},
},
{
name: "field is method",
setFields: SetFields("value", primitveFn),
want: fields{
"value": primitveFn,
},
},
{
name: "field is complex method",
setFields: SetFields("value", complexFn),
want: fields{
"value": primitveFn,
},
},
{
name: "field without value",
setFields: SetFields("value"),
want: fields{},
},
{
name: "field with empty value",
setFields: SetFields("value", ""),
want: fields{
"value": "",
},
},
{
name: "nested simple value",
setFields: SetFields(
"field",
SetFields("sub", 5),
),
want: fields{
"field": fields{
"sub": 5,
},
},
},
{
name: "nested multiple fields",
setFields: SetFields(
"field",
SetFields("sub1", 5),
SetFields("sub2", "asdf"),
SetFields("sub3", primitveFn),
),
want: fields{
"field": fields{
"sub1": 5,
"sub2": "asdf",
"sub3": primitveFn,
},
},
},
{
name: "try to overwrite field primitives",
setFields: SetFields(
"field",
SetFields("sub", 5),
SetFields("sub", primitveFn),
),
shouldPanic: true,
},
{
name: "try to overwrite primitives with fields",
setFields: SetFields(
"field",
SetFields("sub", 5),
SetFields("sub", SetFields("please", "panic")),
),
shouldPanic: true,
},
{
name: "try to overwrite fields with primitive",
setFields: SetFields(
"field",
SetFields("sub", SetFields("please", "panic")),
SetFields("sub", 5),
),
shouldPanic: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
panicked := false
if tt.shouldPanic {
defer func() {
if panicked != tt.shouldPanic {
t.Errorf("wanted panic: %v got %v", tt.shouldPanic, panicked)
}
}()
defer func() {
recover()
panicked = true
}()
}
config := &FieldConfig{
Runtime: goja.New(),
fields: fields{},
}
tt.setFields(config)
if !tt.shouldPanic && fmt.Sprint(config.fields) != fmt.Sprint(tt.want) {
t.Errorf("SetFields() = %v, want %v", fmt.Sprint(config.fields), fmt.Sprint(tt.want))
}
})
}
}
func TestSetFieldsExecuteMethods(t *testing.T) {
primitveFn := func(a string) { fmt.Println(a) }
complexFn := func(*FieldConfig) interface{} {
return primitveFn
}
tests := []struct {
name string
setFields FieldOption
want fields
shouldPanic bool
}{
{
name: "field is method",
setFields: SetFields("value", primitveFn),
want: fields{
"value": primitveFn,
},
},
{
name: "field is complex method",
setFields: SetFields("value", complexFn),
want: fields{
"value": primitveFn,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
panicked := false
if tt.shouldPanic {
defer func() {
if panicked != tt.shouldPanic {
t.Errorf("wanted panic: %v got %v", tt.shouldPanic, panicked)
}
}()
defer func() {
recover()
panicked = true
}()
}
config := &FieldConfig{
Runtime: goja.New(),
fields: fields{},
}
tt.setFields(config)
if !tt.shouldPanic && fmt.Sprint(config.fields) != fmt.Sprint(tt.want) {
t.Errorf("SetFields() = %v, want %v", fmt.Sprint(config.fields), fmt.Sprint(tt.want))
}
})
}
}

View File

@@ -0,0 +1,196 @@
package actions
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/dop251/goja"
"github.com/zitadel/logging"
z_errs "github.com/zitadel/zitadel/internal/errors"
)
func WithHTTP(ctx context.Context) Option {
return func(c *runConfig) {
c.modules["zitadel/http"] = func(runtime *goja.Runtime, module *goja.Object) {
requireHTTP(ctx, &http.Client{Transport: new(transport)}, runtime, module)
}
}
}
type HTTP struct {
runtime *goja.Runtime
client *http.Client
}
func requireHTTP(ctx context.Context, client *http.Client, runtime *goja.Runtime, module *goja.Object) {
c := &HTTP{
client: client,
runtime: runtime,
}
o := module.Get("exports").(*goja.Object)
logging.OnError(o.Set("fetch", c.fetch(ctx))).Warn("unable to set module")
}
type fetchConfig struct {
Method string
Headers http.Header
Body io.Reader
}
var defaultFetchConfig = fetchConfig{
Method: http.MethodGet,
Headers: http.Header{
"Content-Type": []string{"application/json"},
"Accept": []string{"application/json"},
},
}
func (c *HTTP) fetchConfigFromArg(arg *goja.Object, config *fetchConfig) (err error) {
for _, key := range arg.Keys() {
switch key {
case "headers":
config.Headers = parseHeaders(arg.Get(key).ToObject(c.runtime))
case "method":
config.Method = arg.Get(key).String()
case "body":
body, err := arg.Get(key).ToObject(c.runtime).MarshalJSON()
if err != nil {
return err
}
config.Body = bytes.NewReader(body)
default:
return z_errs.ThrowInvalidArgument(nil, "ACTIO-OfUeA", "key is invalid")
}
}
return nil
}
type response struct {
Body string
Status int
Headers map[string][]string
runtime *goja.Runtime
}
func (r *response) Json() goja.Value {
var val interface{}
if err := json.Unmarshal([]byte(r.Body), &val); err != nil {
panic(err)
}
return r.runtime.ToValue(val)
}
func (r *response) Text() goja.Value {
return r.runtime.ToValue(r.Body)
}
func (c *HTTP) fetch(ctx context.Context) func(call goja.FunctionCall) goja.Value {
return func(call goja.FunctionCall) goja.Value {
req := c.buildHTTPRequest(ctx, call.Arguments)
if deadline, ok := ctx.Deadline(); ok {
c.client.Timeout = time.Until(deadline)
}
res, err := c.client.Do(req)
if err != nil {
logging.WithError(err).Debug("call failed")
panic(err)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
logging.WithError(err).Warn("unable to parse body")
panic("unable to read response body")
}
return c.runtime.ToValue(&response{Status: res.StatusCode, Body: string(body), runtime: c.runtime})
}
}
// the first argument has to be a string and is required
// the second agrument is optional and an object with the following fields possible:
// - `Headers`: map with string key and value of type string or string array
// - `Body`: json body of the request
// - `Method`: http method type
func (c *HTTP) buildHTTPRequest(ctx context.Context, args []goja.Value) (req *http.Request) {
if len(args) > 2 {
logging.WithFields("count", len(args)).Debug("more than 2 args provided")
panic("too many args")
}
if len(args) == 0 {
panic("no url provided")
}
config := defaultFetchConfig
var err error
if len(args) == 2 {
if err = c.fetchConfigFromArg(args[1].ToObject(c.runtime), &config); err != nil {
panic(err)
}
}
req, err = http.NewRequestWithContext(ctx, config.Method, args[0].Export().(string), config.Body)
if err != nil {
panic(err)
}
req.Header = config.Headers
return req
}
func parseHeaders(headers *goja.Object) http.Header {
h := make(http.Header, len(headers.Keys()))
for _, k := range headers.Keys() {
header := headers.Get(k).Export()
var values []string
switch headerValue := header.(type) {
case string:
values = strings.Split(headerValue, ",")
case []any:
for _, v := range headerValue {
values = append(values, v.(string))
}
}
for _, v := range values {
h.Add(k, strings.TrimSpace(v))
}
}
return h
}
type transport struct{}
func (*transport) RoundTrip(req *http.Request) (*http.Response, error) {
if httpConfig == nil {
return http.DefaultTransport.RoundTrip(req)
}
if isHostBlocked(httpConfig.DenyList, req.URL) {
return nil, z_errs.ThrowInvalidArgument(nil, "ACTIO-N72d0", "host is denied")
}
return http.DefaultTransport.RoundTrip(req)
}
func isHostBlocked(denyList []AddressChecker, address *url.URL) bool {
for _, blocked := range denyList {
if blocked.Matches(address.Hostname()) {
return true
}
}
return false
}
type AddressChecker interface {
Matches(string) bool
}

View File

@@ -0,0 +1,98 @@
package actions
import (
"net"
"reflect"
"github.com/mitchellh/mapstructure"
z_errs "github.com/zitadel/zitadel/internal/errors"
)
func SetHTTPConfig(config *HTTPConfig) {
httpConfig = config
}
var httpConfig *HTTPConfig
type HTTPConfig struct {
DenyList []AddressChecker
}
func HTTPConfigDecodeHook(from, to reflect.Value) (interface{}, error) {
if to.Type() != reflect.TypeOf(HTTPConfig{}) {
return from.Interface(), nil
}
config := struct {
DenyList []string
}{}
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
WeaklyTypedInput: true,
Result: &config,
})
if err != nil {
return nil, err
}
if err = decoder.Decode(from.Interface()); err != nil {
return nil, err
}
c := HTTPConfig{
DenyList: make([]AddressChecker, len(config.DenyList)),
}
for i, entry := range config.DenyList {
if c.DenyList[i], err = parseDenyListEntry(entry); err != nil {
return nil, err
}
}
return c, nil
}
func parseDenyListEntry(entry string) (AddressChecker, error) {
if checker, err := NewIPChecker(entry); err == nil {
return checker, nil
}
return &DomainChecker{Domain: entry}, nil
}
func NewIPChecker(i string) (AddressChecker, error) {
_, network, err := net.ParseCIDR(i)
if err == nil {
return &IPChecker{Net: network}, nil
}
if ip := net.ParseIP(i); ip != nil {
return &IPChecker{IP: ip}, nil
}
return nil, z_errs.ThrowInvalidArgument(nil, "ACTIO-ddJ7h", "invalid ip")
}
type IPChecker struct {
Net *net.IPNet
IP net.IP
}
func (c *IPChecker) Matches(address string) bool {
ip := net.ParseIP(address)
if ip == nil {
return false
}
if c.IP != nil {
return c.IP.Equal(ip)
}
return c.Net.Contains(ip)
}
type DomainChecker struct {
Domain string
}
func (c *DomainChecker) Matches(domain string) bool {
//TODO: allow wild cards
return c.Domain == domain
}

View File

@@ -0,0 +1,433 @@
package actions
import (
"bytes"
"context"
"io"
"net/http"
"net/url"
"reflect"
"testing"
"github.com/dop251/goja"
"github.com/zitadel/zitadel/internal/errors"
)
func Test_isHostBlocked(t *testing.T) {
var denyList = []AddressChecker{
mustNewIPChecker(t, "192.168.5.0/24"),
mustNewIPChecker(t, "127.0.0.1"),
&DomainChecker{Domain: "test.com"},
}
type args struct {
address *url.URL
}
tests := []struct {
name string
args args
want bool
}{
{
name: "in range",
args: args{
address: mustNewURL(t, "https://192.168.5.4/hodor"),
},
want: true,
},
{
name: "exact ip",
args: args{
address: mustNewURL(t, "http://127.0.0.1:8080/hodor"),
},
want: true,
},
{
name: "address match",
args: args{
address: mustNewURL(t, "https://test.com:42/hodor"),
},
want: true,
},
{
name: "address not match",
args: args{
address: mustNewURL(t, "https://test2.com/hodor"),
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isHostBlocked(denyList, tt.args.address); got != tt.want {
t.Errorf("isHostBlocked() = %v, want %v", got, tt.want)
}
})
}
}
func mustNewIPChecker(t *testing.T, ip string) AddressChecker {
t.Helper()
checker, err := NewIPChecker(ip)
if err != nil {
t.Errorf("unable to parse cidr of %q because: %v", ip, err)
t.FailNow()
}
return checker
}
func mustNewURL(t *testing.T, raw string) *url.URL {
u, err := url.Parse(raw)
if err != nil {
t.Errorf("unable to parse address of %q because: %v", raw, err)
t.FailNow()
}
return u
}
func TestHTTP_fetchConfigFromArg(t *testing.T) {
runtime := goja.New()
runtime.SetFieldNameMapper(goja.UncapFieldNameMapper())
type args struct {
arg *goja.Object
}
tests := []struct {
name string
args args
wantConfig fetchConfig
wantErr func(error) bool
}{
{
name: "no fetch option provided",
args: args{
arg: runtime.ToValue(
struct{}{},
).ToObject(runtime),
},
wantConfig: fetchConfig{},
wantErr: func(err error) bool {
return err == nil
},
},
{
name: "header set as string",
args: args{
arg: runtime.ToValue(
&struct {
Headers map[string]string
}{
Headers: map[string]string{
"Authorization": "Bearer token",
},
},
).ToObject(runtime),
},
wantConfig: fetchConfig{
Headers: http.Header{
"Authorization": {"Bearer token"},
},
},
wantErr: func(err error) bool {
return err == nil
},
},
{
name: "header set as list",
args: args{
arg: runtime.ToValue(
&struct {
Headers map[string][]any
}{
Headers: map[string][]any{
"Authorization": {"Bearer token"},
},
},
).ToObject(runtime),
},
wantConfig: fetchConfig{
Headers: http.Header{
"Authorization": {"Bearer token"},
},
},
wantErr: func(err error) bool {
return err == nil
},
},
{
name: "method set",
args: args{
arg: runtime.ToValue(
&struct {
Method string
}{
Method: http.MethodPost,
},
).ToObject(runtime),
},
wantConfig: fetchConfig{
Method: http.MethodPost,
},
wantErr: func(err error) bool {
return err == nil
},
},
{
name: "body set",
args: args{
arg: runtime.ToValue(
&struct {
Body struct{ Id string }
}{
Body: struct{ Id string }{
Id: "asdf123",
},
},
).ToObject(runtime),
},
wantConfig: fetchConfig{
Body: bytes.NewReader([]byte(`{"id":"asdf123"}`)),
},
wantErr: func(err error) bool {
return err == nil
},
},
{
name: "invalid header",
args: args{
arg: runtime.ToValue(
&struct {
NotExists struct{}
}{
NotExists: struct{}{},
},
).ToObject(runtime),
},
wantConfig: fetchConfig{},
wantErr: func(err error) bool {
return errors.IsErrorInvalidArgument(err)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &HTTP{
runtime: runtime,
client: http.DefaultClient,
}
gotConfig := new(fetchConfig)
err := c.fetchConfigFromArg(tt.args.arg, gotConfig)
if !tt.wantErr(err) {
t.Errorf("HTTP.fetchConfigFromArg() error = %v", err)
return
}
if !reflect.DeepEqual(gotConfig.Headers, tt.wantConfig.Headers) {
t.Errorf("config.Headers got = %#v, want %#v", gotConfig.Headers, tt.wantConfig.Headers)
}
if gotConfig.Method != tt.wantConfig.Method {
t.Errorf("config.Method got = %#v, want %#v", gotConfig.Method, tt.wantConfig.Method)
}
if tt.wantConfig.Body == nil {
if gotConfig.Body != nil {
t.Errorf("didn't expect a body")
}
return
}
gotBody, _ := io.ReadAll(gotConfig.Body)
wantBody, _ := io.ReadAll(tt.wantConfig.Body)
if !reflect.DeepEqual(gotBody, wantBody) {
t.Errorf("config.Body got = %s, want %s", gotBody, wantBody)
}
})
}
}
func TestHTTP_buildHTTPRequest(t *testing.T) {
runtime := goja.New()
runtime.SetFieldNameMapper(goja.UncapFieldNameMapper())
type args struct {
args []goja.Value
}
tests := []struct {
name string
args args
wantReq *http.Request
shouldPanic bool
}{
{
name: "only url",
args: args{
args: []goja.Value{
runtime.ToValue("http://my-url.ch"),
},
},
wantReq: &http.Request{
Method: http.MethodGet,
URL: mustNewURL(t, "http://my-url.ch"),
Header: defaultFetchConfig.Headers,
Body: nil,
},
},
{
name: "no params",
args: args{
args: []goja.Value{
runtime.ToValue("http://my-url.ch"),
runtime.ToValue(&struct{}{}),
},
},
wantReq: &http.Request{
Method: http.MethodGet,
URL: mustNewURL(t, "http://my-url.ch"),
Header: defaultFetchConfig.Headers,
Body: nil,
},
},
{
name: "overwrite headers",
args: args{
args: []goja.Value{
runtime.ToValue("http://my-url.ch"),
runtime.ToValue(struct {
Headers map[string][]interface{}
}{
Headers: map[string][]interface{}{"Authorization": {"some token"}},
}),
},
},
wantReq: &http.Request{
Method: http.MethodGet,
URL: mustNewURL(t, "http://my-url.ch"),
Header: http.Header{
"Authorization": []string{"some token"},
},
Body: nil,
},
},
{
name: "post with body",
args: args{
args: []goja.Value{
runtime.ToValue("http://my-url.ch"),
runtime.ToValue(struct {
Body struct{ MyData string }
}{
Body: struct{ MyData string }{MyData: "hello world"},
}),
},
},
wantReq: &http.Request{
Method: http.MethodGet,
URL: mustNewURL(t, "http://my-url.ch"),
Header: defaultFetchConfig.Headers,
Body: io.NopCloser(bytes.NewReader([]byte(`{"myData":"hello world"}`))),
},
},
{
name: "too many args",
args: args{
args: []goja.Value{
runtime.ToValue("http://my-url.ch"),
runtime.ToValue("http://my-url.ch"),
runtime.ToValue("http://my-url.ch"),
},
},
wantReq: nil,
shouldPanic: true,
},
{
name: "no args",
args: args{
args: []goja.Value{},
},
wantReq: nil,
shouldPanic: true,
},
{
name: "invalid config",
args: args{
args: []goja.Value{
runtime.ToValue("http://my-url.ch"),
runtime.ToValue(struct {
Invalid bool
}{
Invalid: true,
}),
},
},
wantReq: nil,
shouldPanic: true,
},
{
name: "invalid method",
args: args{
args: []goja.Value{
runtime.ToValue("http://my-url.ch"),
runtime.ToValue(struct {
Method string
}{
Method: " asdf asdf",
}),
},
},
wantReq: nil,
shouldPanic: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
panicked := false
if tt.shouldPanic {
defer func() {
if panicked != tt.shouldPanic {
t.Errorf("wanted panic: %v got %v", tt.shouldPanic, panicked)
}
}()
defer func() {
recover()
panicked = true
}()
}
c := &HTTP{
runtime: runtime,
}
gotReq := c.buildHTTPRequest(context.Background(), tt.args.args)
if tt.shouldPanic {
return
}
if gotReq.URL.String() != tt.wantReq.URL.String() {
t.Errorf("url = %s, want %s", gotReq.URL, tt.wantReq.URL)
}
if !reflect.DeepEqual(gotReq.Header, tt.wantReq.Header) {
t.Errorf("headers = %v, want %v", gotReq.Header, tt.wantReq.Header)
}
if gotReq.Method != tt.wantReq.Method {
t.Errorf("method = %s, want %s", gotReq.Method, tt.wantReq.Method)
}
if tt.wantReq.Body == nil {
if gotReq.Body != nil {
t.Errorf("didn't expect a body")
}
return
}
gotBody, _ := io.ReadAll(gotReq.Body)
wantBody, _ := io.ReadAll(tt.wantReq.Body)
if !reflect.DeepEqual(gotBody, wantBody) {
t.Errorf("config.Body got = %s, want %s", gotBody, wantBody)
}
})
}
}

View File

@@ -0,0 +1,30 @@
package actions
import (
"github.com/zitadel/logging"
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/console"
)
var ServerLog *logrus
type logrus struct{}
func (*logrus) Log(s string) {
logging.WithFields("message", s).Info("log from action")
}
func (*logrus) Warn(s string) {
logging.WithFields("message", s).Info("warn from action")
}
func (*logrus) Error(s string) {
logging.WithFields("message", s).Info("error from action")
}
func WithLogger(logger console.Printer) Option {
return func(c *runConfig) {
c.modules["zitadel/log"] = func(runtime *goja.Runtime, module *goja.Object) {
console.RequireWithPrinter(logger)(runtime, module)
}
}
}

View File

@@ -0,0 +1,56 @@
package object
import (
"encoding/json"
"time"
"github.com/dop251/goja"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/query"
)
func UserMetadataListFromQuery(c *actions.FieldConfig, metadata *query.UserMetadataList) goja.Value {
result := &userMetadataList{
Count: metadata.Count,
Sequence: metadata.Sequence,
Timestamp: metadata.Timestamp,
Metadata: make([]*userMetadata, len(metadata.Metadata)),
}
for i, md := range metadata.Metadata {
var value interface{}
err := json.Unmarshal(md.Value, &value)
if err != nil {
logging.WithError(err).Debug("unable to unmarshal into map")
panic(err)
}
result.Metadata[i] = &userMetadata{
CreationDate: md.CreationDate,
ChangeDate: md.ChangeDate,
ResourceOwner: md.ResourceOwner,
Sequence: md.Sequence,
Key: md.Key,
Value: c.Runtime.ToValue(value),
}
}
return c.Runtime.ToValue(result)
}
type userMetadataList struct {
Count uint64
Sequence uint64
Timestamp time.Time
Metadata []*userMetadata
}
type userMetadata struct {
CreationDate time.Time
ChangeDate time.Time
ResourceOwner string
Sequence uint64
Key string
Value goja.Value
}

View File

@@ -0,0 +1,165 @@
package object
import (
"time"
"github.com/dop251/goja"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
)
func UserFromExternalUser(c *actions.FieldConfig, user *domain.ExternalUser) goja.Value {
return c.Runtime.ToValue(&externalUser{
ExternalId: user.ExternalUserID,
ExternalIdpId: user.ExternalUserID,
Human: human{
FirstName: user.FirstName,
LastName: user.LastName,
NickName: user.NickName,
DisplayName: user.DisplayName,
PreferredLanguage: user.PreferredLanguage.String(),
Email: user.Email,
IsEmailVerified: user.IsEmailVerified,
Phone: user.Phone,
IsPhoneVerified: user.IsPhoneVerified,
},
})
}
func UserFromHuman(c *actions.FieldConfig, user *domain.Human) goja.Value {
u := &humanUser{
Id: user.AggregateID,
CreationDate: user.CreationDate,
ChangeDate: user.ChangeDate,
ResourceOwner: user.ResourceOwner,
Sequence: user.Sequence,
State: user.State,
Username: user.Username,
LoginNames: user.LoginNames,
PreferredLoginName: user.PreferredLoginName,
}
if user.Profile != nil {
u.Human.FirstName = user.Profile.FirstName
u.Human.LastName = user.Profile.LastName
u.Human.NickName = user.Profile.NickName
u.Human.DisplayName = user.Profile.DisplayName
u.Human.PreferredLanguage = user.Profile.PreferredLanguage.String()
}
if user.Email != nil {
u.Human.Email = user.Email.EmailAddress
u.Human.IsEmailVerified = user.Email.IsEmailVerified
}
if user.Phone != nil {
u.Human.Phone = user.Phone.PhoneNumber
u.Human.IsPhoneVerified = user.Phone.IsPhoneVerified
}
return c.Runtime.ToValue(u)
}
func UserFromQuery(c *actions.FieldConfig, user *query.User) goja.Value {
if user.Human != nil {
return humanFromQuery(c, user)
}
return machineFromQuery(c, user)
}
func humanFromQuery(c *actions.FieldConfig, user *query.User) goja.Value {
return c.Runtime.ToValue(&humanUser{
Id: user.ID,
CreationDate: user.CreationDate,
ChangeDate: user.ChangeDate,
ResourceOwner: user.ResourceOwner,
Sequence: user.Sequence,
State: user.State,
Username: user.Username,
LoginNames: user.LoginNames,
PreferredLoginName: user.PreferredLoginName,
Human: human{
FirstName: user.Human.FirstName,
LastName: user.Human.LastName,
NickName: user.Human.NickName,
DisplayName: user.Human.DisplayName,
AvatarKey: user.Human.AvatarKey,
PreferredLanguage: user.Human.PreferredLanguage.String(),
Gender: user.Human.Gender,
Email: user.Human.Email,
IsEmailVerified: user.Human.IsEmailVerified,
Phone: user.Human.Phone,
IsPhoneVerified: user.Human.IsPhoneVerified,
},
})
}
func machineFromQuery(c *actions.FieldConfig, user *query.User) goja.Value {
return c.Runtime.ToValue(&machineUser{
Id: user.ID,
CreationDate: user.CreationDate,
ChangeDate: user.ChangeDate,
ResourceOwner: user.ResourceOwner,
Sequence: user.Sequence,
State: user.State,
Username: user.Username,
LoginNames: user.LoginNames,
PreferredLoginName: user.PreferredLoginName,
Machine: machine{
Name: user.Machine.Name,
Description: user.Machine.Description,
},
})
}
type externalUser struct {
ExternalId string
ExternalIdpId string
Human human
}
type humanUser struct {
Id string
CreationDate time.Time
ChangeDate time.Time
ResourceOwner string
Sequence uint64
State domain.UserState
Username string
LoginNames database.StringArray
PreferredLoginName string
Human human
}
type human struct {
FirstName string
LastName string
NickName string
DisplayName string
AvatarKey string
PreferredLanguage string
Gender domain.Gender
Email string
IsEmailVerified bool
Phone string
IsPhoneVerified bool
}
type machineUser struct {
Id string
CreationDate time.Time
ChangeDate time.Time
ResourceOwner string
Sequence uint64
State domain.UserState
Username string
LoginNames database.StringArray
PreferredLoginName string
Machine machine
}
type machine struct {
Name string
Description string
}

View File

@@ -1,55 +1,7 @@
package actions
import (
"errors"
"github.com/dop251/goja"
)
type UserGrant struct {
ProjectID string
ProjectGrantID string
Roles []string
}
func appendUserGrant(list *[]UserGrant) func(goja.FunctionCall) goja.Value {
return func(call goja.FunctionCall) goja.Value {
userGrantMap := call.Argument(0).Export()
userGrant, _ := userGrantFromMap(userGrantMap)
*list = append(*list, userGrant)
return nil
}
}
func userGrantFromMap(grantMap interface{}) (UserGrant, error) {
m, ok := grantMap.(map[string]interface{})
if !ok {
return UserGrant{}, errors.New("invalid")
}
projectID, ok := m["projectID"].(string)
if !ok {
return UserGrant{}, errors.New("invalid")
}
var projectGrantID string
if id, ok := m["projectGrantID"]; ok {
projectGrantID, ok = id.(string)
if !ok {
return UserGrant{}, errors.New("invalid")
}
}
var roles []string
if r := m["roles"]; r != nil {
rs, ok := r.([]interface{})
if !ok {
return UserGrant{}, errors.New("invalid")
}
for _, role := range rs {
roles = append(roles, role.(string))
}
}
return UserGrant{
ProjectID: projectID,
ProjectGrantID: projectGrantID,
Roles: roles,
}, nil
}

View File

@@ -7,30 +7,66 @@ import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
action_pb "github.com/zitadel/zitadel/pkg/grpc/action"
message_pb "github.com/zitadel/zitadel/pkg/grpc/message"
)
func FlowTypeToDomain(flowType action_pb.FlowType) domain.FlowType {
// for backward compatability: old enum identifiers are mapped as well
func FlowTypeToDomain(flowType string) domain.FlowType {
switch flowType {
case action_pb.FlowType_FLOW_TYPE_EXTERNAL_AUTHENTICATION:
case "FLOW_TYPE_EXTERNAL_AUTHENTICATION", domain.FlowTypeExternalAuthentication.ID():
return domain.FlowTypeExternalAuthentication
case domain.FlowTypeCustomiseToken.ID():
return domain.FlowTypeCustomiseToken
default:
return domain.FlowTypeUnspecified
}
}
func TriggerTypeToDomain(triggerType action_pb.TriggerType) domain.TriggerType {
func FlowTypeToPb(typ domain.FlowType) *action_pb.FlowType {
return &action_pb.FlowType{
Id: typ.ID(),
Name: &message_pb.LocalizedMessage{
Key: typ.LocalizationKey(),
},
}
}
// TriggerTypeToDomain maps the pb type to domain
// for backward compatability: old enum identifiers are mapped as well
func TriggerTypeToDomain(triggerType string) domain.TriggerType {
switch triggerType {
case action_pb.TriggerType_TRIGGER_TYPE_POST_AUTHENTICATION:
case "TRIGGER_TYPE_POST_AUTHENTICATION", domain.TriggerTypePostAuthentication.ID():
return domain.TriggerTypePostAuthentication
case action_pb.TriggerType_TRIGGER_TYPE_PRE_CREATION:
case "TRIGGER_TYPE_PRE_CREATION", domain.TriggerTypePreCreation.ID():
return domain.TriggerTypePreCreation
case action_pb.TriggerType_TRIGGER_TYPE_POST_CREATION:
case "TRIGGER_TYPE_POST_CREATION", domain.TriggerTypePostCreation.ID():
return domain.TriggerTypePostCreation
case domain.TriggerTypePreAccessTokenCreation.ID():
return domain.TriggerTypePreAccessTokenCreation
case domain.TriggerTypePreUserinfoCreation.ID():
return domain.TriggerTypePreUserinfoCreation
default:
return domain.TriggerTypeUnspecified
}
}
func TriggerTypesToPb(types []domain.TriggerType) []*action_pb.TriggerType {
list := make([]*action_pb.TriggerType, len(types))
for i, typ := range types {
list[i] = TriggerTypeToPb(typ)
}
return list
}
func TriggerTypeToPb(typ domain.TriggerType) *action_pb.TriggerType {
return &action_pb.TriggerType{
Id: typ.ID(),
Name: &message_pb.LocalizedMessage{
Key: typ.LocalizationKey(),
},
}
}
func FlowToPb(flow *query.Flow) *action_pb.Flow {
return &action_pb.Flow{
Type: FlowTypeToPb(flow.Type),
@@ -47,28 +83,6 @@ func TriggerActionToPb(trigger domain.TriggerType, actions []*query.Action) *act
}
}
func FlowTypeToPb(flowType domain.FlowType) action_pb.FlowType {
switch flowType {
case domain.FlowTypeExternalAuthentication:
return action_pb.FlowType_FLOW_TYPE_EXTERNAL_AUTHENTICATION
default:
return action_pb.FlowType_FLOW_TYPE_UNSPECIFIED
}
}
func TriggerTypeToPb(triggerType domain.TriggerType) action_pb.TriggerType {
switch triggerType {
case domain.TriggerTypePostAuthentication:
return action_pb.TriggerType_TRIGGER_TYPE_POST_AUTHENTICATION
case domain.TriggerTypePreCreation:
return action_pb.TriggerType_TRIGGER_TYPE_PRE_CREATION
case domain.TriggerTypePostCreation:
return action_pb.TriggerType_TRIGGER_TYPE_POST_CREATION
default:
return action_pb.TriggerType_TRIGGER_TYPE_UNSPECIFIED
}
}
func TriggerActionsToPb(triggers map[domain.TriggerType][]*query.Action) []*action_pb.TriggerAction {
list := make([]*action_pb.TriggerAction, 0)
for trigger, actions := range triggers {
@@ -92,7 +106,7 @@ func ActionToPb(action *query.Action) *action_pb.Action {
State: ActionStateToPb(action.State),
Name: action.Name,
Script: action.Script,
Timeout: durationpb.New(action.Timeout),
Timeout: durationpb.New(action.Timeout()),
AllowedToFail: action.AllowedToFail,
}
}

View File

@@ -2,12 +2,14 @@ package admin
import (
"context"
"google.golang.org/protobuf/types/known/durationpb"
text_grpc "github.com/zitadel/zitadel/internal/api/grpc/text"
"github.com/zitadel/zitadel/internal/domain"
caos_errors "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
action_pb "github.com/zitadel/zitadel/pkg/grpc/action"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
app_pb "github.com/zitadel/zitadel/pkg/grpc/app"
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp"
@@ -17,7 +19,6 @@ import (
project_pb "github.com/zitadel/zitadel/pkg/grpc/project"
user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
v1_pb "github.com/zitadel/zitadel/pkg/grpc/v1"
"google.golang.org/protobuf/types/known/durationpb"
)
func (s *Server) ExportData(ctx context.Context, req *admin_pb.ExportDataRequest) (_ *admin_pb.ExportDataResponse, err error) {
@@ -639,8 +640,8 @@ func (s *Server) getTriggerActions(ctx context.Context, org string, processedAct
}
triggerActions = append(triggerActions, &management_pb.SetTriggerActionsRequest{
FlowType: action_pb.FlowType(flowType),
TriggerType: action_pb.TriggerType(triggerType),
FlowType: flowType.ID(),
TriggerType: triggerType.ID(),
ActionIds: actions,
})
}
@@ -662,7 +663,7 @@ func (s *Server) getActions(ctx context.Context, org string) ([]*v1_pb.DataActio
return actions, nil
}
for i, action := range queriedActions.Actions {
timeout := durationpb.New(action.Timeout)
timeout := durationpb.New(action.Timeout())
actions[i] = &v1_pb.DataAction{
ActionId: action.ID,

View File

@@ -17,6 +17,7 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
action_grpc "github.com/zitadel/zitadel/internal/api/grpc/action"
"github.com/zitadel/zitadel/internal/api/grpc/management"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
@@ -693,9 +694,9 @@ func (s *Server) importData(ctx context.Context, orgs []*admin_pb.DataOrg) (*adm
if org.TriggerActions != nil {
for _, triggerAction := range org.GetTriggerActions() {
_, err := s.command.SetTriggerActions(ctx, domain.FlowType(triggerAction.FlowType), domain.TriggerType(triggerAction.TriggerType), triggerAction.ActionIds, org.GetOrgId())
_, err := s.command.SetTriggerActions(ctx, action_grpc.FlowTypeToDomain(triggerAction.FlowType), action_grpc.TriggerTypeToDomain(triggerAction.TriggerType), triggerAction.ActionIds, org.GetOrgId())
if err != nil {
errors = append(errors, &admin_pb.ImportDataError{Type: "trigger_action", Id: triggerAction.FlowType.String() + "_" + triggerAction.TriggerType.String(), Message: err.Error()})
errors = append(errors, &admin_pb.ImportDataError{Type: "trigger_action", Id: triggerAction.FlowType + "_" + triggerAction.TriggerType, Message: err.Error()})
continue
}
successOrg.TriggerActions = append(successOrg.TriggerActions, &management_pb.SetTriggerActionsRequest{FlowType: triggerAction.FlowType, TriggerType: triggerAction.TriggerType, ActionIds: triggerAction.GetActionIds()})

View File

@@ -6,9 +6,31 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
action_grpc "github.com/zitadel/zitadel/internal/api/grpc/action"
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
action_pb "github.com/zitadel/zitadel/pkg/grpc/action"
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management"
)
func (s *Server) ListFlowTypes(ctx context.Context, _ *mgmt_pb.ListFlowTypesRequest) (*mgmt_pb.ListFlowTypesResponse, error) {
return &mgmt_pb.ListFlowTypesResponse{
Result: []*action_pb.FlowType{
action_grpc.FlowTypeToPb(domain.FlowTypeExternalAuthentication),
action_grpc.FlowTypeToPb(domain.FlowTypeCustomiseToken),
},
}, nil
}
func (s *Server) ListFlowTriggerTypes(ctx context.Context, req *mgmt_pb.ListFlowTriggerTypesRequest) (*mgmt_pb.ListFlowTriggerTypesResponse, error) {
triggerTypes := action_grpc.FlowTypeToDomain(req.Type).TriggerTypes()
if len(triggerTypes) == 0 {
return nil, errors.ThrowNotFound(nil, "MANAG-P2OBk", "Errors.NotFound")
}
return &mgmt_pb.ListFlowTriggerTypesResponse{
Result: action_grpc.TriggerTypesToPb(triggerTypes),
}, nil
}
func (s *Server) GetFlow(ctx context.Context, req *mgmt_pb.GetFlowRequest) (*mgmt_pb.GetFlowResponse, error) {
flow, err := s.query.GetFlow(ctx, action_grpc.FlowTypeToDomain(req.Type), authz.GetCtxData(ctx).OrgID)
if err != nil {

View File

@@ -3,14 +3,20 @@ package oidc
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"github.com/dop251/goja"
"github.com/zitadel/logging"
"github.com/zitadel/oidc/v2/pkg/oidc"
"github.com/zitadel/oidc/v2/pkg/op"
"gopkg.in/square/go-jose.v2"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/actions/object"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/http"
api_http "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
@@ -25,6 +31,7 @@ const (
ClaimUserMetaData = ScopeUserMetaData
ScopeResourceOwner = "urn:zitadel:iam:user:resourceowner"
ClaimResourceOwner = ScopeResourceOwner + ":"
ClaimActionLogFormat = "urn:zitadel:iam:action:%s:log"
oidcCtx = "oidc"
)
@@ -141,7 +148,7 @@ func (o *OPStorage) SetUserinfoFromToken(ctx context.Context, userInfo oidc.User
if err != nil {
return err
}
if origin != "" && !http.IsOriginAllowed(app.OIDCConfig.AllowedOrigins, origin) {
if origin != "" && !api_http.IsOriginAllowed(app.OIDCConfig.AllowedOrigins, origin) {
return errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed")
}
}
@@ -276,8 +283,9 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
}
}
}
if len(roles) == 0 || applicationID == "" {
return nil
return o.userinfoFlows(ctx, user.ResourceOwner, userInfo)
}
projectRoles, err := o.assertRoles(ctx, userID, applicationID, roles)
if err != nil {
@@ -286,6 +294,106 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
if len(projectRoles) > 0 {
userInfo.AppendClaims(ClaimProjectRoles, projectRoles)
}
return o.userinfoFlows(ctx, user.ResourceOwner, userInfo)
}
func (o *OPStorage) userinfoFlows(ctx context.Context, resourceOwner string, userInfo oidc.UserInfoSetter) error {
queriedActions, err := o.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreUserinfoCreation, resourceOwner)
if err != nil {
return err
}
ctxFields := actions.SetContextFields(
actions.SetFields("v1",
actions.SetFields("user",
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
return func(goja.FunctionCall) goja.Value {
resourceOwnerQuery, err := query.NewUserMetadataResourceOwnerSearchQuery(resourceOwner)
if err != nil {
logging.WithError(err).Debug("unable to create search query")
panic(err)
}
metadata, err := o.query.SearchUserMetadata(
ctx,
true,
userInfo.GetSubject(),
&query.UserMetadataSearchQueries{Queries: []query.SearchQuery{resourceOwnerQuery}},
)
if err != nil {
logging.WithError(err).Info("unable to get md in action")
panic(err)
}
return object.UserMetadataListFromQuery(c, metadata)
}
}),
),
),
)
for _, action := range queriedActions {
actionCtx, cancel := context.WithTimeout(ctx, action.Timeout())
claimLogs := []string{}
apiFields := actions.WithAPIFields(
actions.SetFields("v1",
actions.SetFields("userinfo",
actions.SetFields("setClaim", func(key string, value interface{}) {
if userInfo.GetClaim(key) == nil {
userInfo.AppendClaims(key, value)
return
}
claimLogs = append(claimLogs, fmt.Sprintf("key %q already exists", key))
}),
actions.SetFields("appendLogIntoClaims", func(entry string) {
claimLogs = append(claimLogs, entry)
}),
),
actions.SetFields("user",
actions.SetFields("setMetadata", func(call goja.FunctionCall) goja.Value {
if len(call.Arguments) != 2 {
panic("exactly 2 (key, value) arguments expected")
}
key := call.Arguments[0].Export().(string)
val := call.Arguments[1].Export()
value, err := json.Marshal(val)
if err != nil {
logging.WithError(err).Debug("unable to marshal")
panic(err)
}
metadata := &domain.Metadata{
Key: key,
Value: value,
}
if _, err = o.command.SetUserMetadata(ctx, metadata, userInfo.GetSubject(), resourceOwner); err != nil {
logging.WithError(err).Info("unable to set md in action")
panic(err)
}
return nil
}),
),
),
)
err = actions.Run(
actionCtx,
ctxFields,
apiFields,
action.Script,
action.Name,
append(actions.ActionToOptions(action), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
)
cancel()
if err != nil {
return err
}
if len(claimLogs) > 0 {
userInfo.AppendClaims(fmt.Sprintf(ClaimActionLogFormat, action.Name), claimLogs)
}
}
return nil
}
@@ -327,8 +435,9 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
}
}
}
if len(roles) == 0 || clientID == "" {
return claims, nil
return o.privateClaimsFlows(ctx, userID, claims)
}
projectRoles, err := o.assertRoles(ctx, userID, clientID, roles)
if err != nil {
@@ -337,7 +446,111 @@ func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clie
if len(projectRoles) > 0 {
claims = appendClaim(claims, ClaimProjectRoles, projectRoles)
}
return claims, err
return o.privateClaimsFlows(ctx, userID, claims)
}
func (o *OPStorage) privateClaimsFlows(ctx context.Context, userID string, claims map[string]interface{}) (map[string]interface{}, error) {
user, err := o.query.GetUserByID(ctx, true, userID)
if err != nil {
return nil, err
}
queriedActions, err := o.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreAccessTokenCreation, user.ResourceOwner)
if err != nil {
return nil, err
}
ctxFields := actions.SetContextFields(
actions.SetFields("v1",
actions.SetFields("user",
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
return func(goja.FunctionCall) goja.Value {
resourceOwnerQuery, err := query.NewUserMetadataResourceOwnerSearchQuery(user.ResourceOwner)
if err != nil {
logging.WithError(err).Debug("unable to create search query")
panic(err)
}
metadata, err := o.query.SearchUserMetadata(
ctx,
true,
userID,
&query.UserMetadataSearchQueries{Queries: []query.SearchQuery{resourceOwnerQuery}},
)
if err != nil {
logging.WithError(err).Info("unable to get md in action")
panic(err)
}
return object.UserMetadataListFromQuery(c, metadata)
}
}),
),
),
)
for _, action := range queriedActions {
claimLogs := []string{}
actionCtx, cancel := context.WithTimeout(ctx, action.Timeout())
apiFields := actions.WithAPIFields(
actions.SetFields("v1",
actions.SetFields("claims",
actions.SetFields("setClaim", func(key string, value interface{}) {
if _, ok := claims[key]; !ok {
claims[key] = value
return
}
claimLogs = append(claimLogs, fmt.Sprintf("key %q already exists", key))
}),
actions.SetFields("appendLogIntoClaims", func(entry string) {
claimLogs = append(claimLogs, entry)
}),
),
actions.SetFields("user",
actions.SetFields("setMetadata", func(call goja.FunctionCall) {
if len(call.Arguments) != 2 {
panic("exactly 2 (key, value) arguments expected")
}
key := call.Arguments[0].Export().(string)
val := call.Arguments[1].Export()
value, err := json.Marshal(val)
if err != nil {
logging.WithError(err).Debug("unable to marshal")
panic(err)
}
metadata := &domain.Metadata{
Key: key,
Value: value,
}
if _, err = o.command.SetUserMetadata(ctx, metadata, userID, user.ResourceOwner); err != nil {
logging.WithError(err).Info("unable to set md in action")
panic(err)
}
}),
),
),
)
err = actions.Run(
actionCtx,
ctxFields,
apiFields,
action.Script,
action.Name,
append(actions.ActionToOptions(action), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
)
cancel()
if err != nil {
return nil, err
}
if len(claimLogs) > 0 {
claims = appendClaim(claims, fmt.Sprintf(ClaimActionLogFormat, action.Name), claimLogs)
claimLogs = nil
}
}
return claims, nil
}
func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID string, requestedRoles []string) (map[string]map[string]string, error) {

View File

@@ -2,10 +2,15 @@ package login
import (
"context"
"encoding/json"
"github.com/dop251/goja"
"github.com/zitadel/logging"
"github.com/zitadel/oidc/v2/pkg/oidc"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/actions/object"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
iam_model "github.com/zitadel/zitadel/internal/iam/model"
@@ -24,10 +29,95 @@ func (l *Login) customExternalUserMapping(ctx context.Context, user *domain.Exte
if err != nil {
return nil, err
}
actionCtx := (&actions.Context{}).SetToken(tokens)
api := (&actions.API{}).SetExternalUser(user).SetMetadata(&user.Metadatas)
ctxFields := actions.SetContextFields(
actions.SetFields("accessToken", tokens.AccessToken),
actions.SetFields("idToken", tokens.IDToken),
actions.SetFields("getClaim", func(claim string) interface{} {
return tokens.IDTokenClaims.GetClaim(claim)
}),
actions.SetFields("claimsJSON", func() (string, error) {
c, err := json.Marshal(tokens.IDTokenClaims)
if err != nil {
return "", err
}
return string(c), nil
}),
actions.SetFields("v1",
actions.SetFields("externalUser", func(c *actions.FieldConfig) interface{} {
return object.UserFromExternalUser(c, user)
}),
),
)
apiFields := actions.WithAPIFields(
actions.SetFields("setFirstName", func(firstName string) {
user.FirstName = firstName
}),
actions.SetFields("setLastName", func(lastName string) {
user.LastName = lastName
}),
actions.SetFields("setNickName", func(nickName string) {
user.NickName = nickName
}),
actions.SetFields("setDisplayName", func(displayName string) {
user.DisplayName = displayName
}),
actions.SetFields("setPreferredLanguage", func(preferredLanguage string) {
user.PreferredLanguage = language.Make(preferredLanguage)
}),
actions.SetFields("setPreferredUsername", func(username string) {
user.PreferredUsername = username
}),
actions.SetFields("setEmail", func(email string) {
user.Email = email
}),
actions.SetFields("setEmailVerified", func(verified bool) {
user.IsEmailVerified = verified
}),
actions.SetFields("setPhone", func(phone string) {
user.Phone = phone
}),
actions.SetFields("setPhoneVerified", func(verified bool) {
user.IsPhoneVerified = verified
}),
actions.SetFields("metadata", &user.Metadatas),
actions.SetFields("v1",
actions.SetFields("user",
actions.SetFields("appendMetadata", func(call goja.FunctionCall) goja.Value {
if len(call.Arguments) != 2 {
panic("exactly 2 (key, value) arguments expected")
}
key := call.Arguments[0].Export().(string)
val := call.Arguments[1].Export()
value, err := json.Marshal(val)
if err != nil {
logging.WithError(err).Debug("unable to marshal")
panic(err)
}
user.Metadatas = append(user.Metadatas,
&domain.Metadata{
Key: key,
Value: value,
})
return nil
}),
),
),
)
for _, a := range triggerActions {
err = actions.Run(actionCtx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail)
actionCtx, cancel := context.WithTimeout(ctx, a.Timeout())
err = actions.Run(
actionCtx,
ctxFields,
apiFields,
a.Script,
a.Name,
append(actions.ActionToOptions(a), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
)
cancel()
if err != nil {
return nil, err
}
@@ -40,10 +130,98 @@ func (l *Login) customExternalUserToLoginUserMapping(ctx context.Context, user *
if err != nil {
return nil, nil, err
}
actionCtx := (&actions.Context{}).SetToken(tokens)
api := (&actions.API{}).SetHuman(user).SetMetadata(&metadata)
ctxOpts := actions.SetContextFields(
actions.SetFields("v1",
actions.SetFields("user", func(c *actions.FieldConfig) interface{} {
return object.UserFromHuman(c, user)
}),
),
)
apiFields := actions.WithAPIFields(
actions.SetFields("setFirstName", func(firstName string) {
user.FirstName = firstName
}),
actions.SetFields("setLastName", func(lastName string) {
user.LastName = lastName
}),
actions.SetFields("setNickName", func(nickName string) {
user.NickName = nickName
}),
actions.SetFields("setDisplayName", func(displayName string) {
user.DisplayName = displayName
}),
actions.SetFields("setPreferredLanguage", func(preferredLanguage string) {
user.PreferredLanguage = language.Make(preferredLanguage)
}),
actions.SetFields("setGender", func(gender domain.Gender) {
user.Gender = gender
}),
actions.SetFields("setUsername", func(username string) {
user.Username = username
}),
actions.SetFields("setEmail", func(email string) {
if user.Email == nil {
user.Email = &domain.Email{}
}
user.Email.EmailAddress = email
}),
actions.SetFields("setEmailVerified", func(verified bool) {
if user.Email == nil {
return
}
user.Email.IsEmailVerified = verified
}),
actions.SetFields("setPhone", func(email string) {
if user.Phone == nil {
user.Phone = &domain.Phone{}
}
user.Phone.PhoneNumber = email
}),
actions.SetFields("setPhoneVerified", func(verified bool) {
if user.Phone == nil {
return
}
user.Phone.IsPhoneVerified = verified
}),
actions.SetFields("metadata", metadata),
actions.SetFields("v1",
actions.SetFields("user",
actions.SetFields("appendMetadata", func(call goja.FunctionCall) goja.Value {
if len(call.Arguments) != 2 {
panic("exactly 2 (key, value) arguments expected")
}
key := call.Arguments[0].Export().(string)
val := call.Arguments[1].Export()
value, err := json.Marshal(val)
if err != nil {
logging.WithError(err).Debug("unable to marshal")
panic(err)
}
metadata = append(metadata,
&domain.Metadata{
Key: key,
Value: value,
})
return nil
}),
),
),
)
for _, a := range triggerActions {
err = actions.Run(actionCtx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail)
actionCtx, cancel := context.WithTimeout(ctx, a.Timeout())
err = actions.Run(
actionCtx,
ctxOpts,
apiFields,
a.Script,
a.Name,
append(actions.ActionToOptions(a), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
)
cancel()
if err != nil {
return nil, nil, err
}
@@ -56,11 +234,78 @@ func (l *Login) customGrants(ctx context.Context, userID string, tokens *oidc.To
if err != nil {
return nil, err
}
actionCtx := (&actions.Context{}).SetToken(tokens)
actionUserGrants := make([]actions.UserGrant, 0)
api := (&actions.API{}).SetUserGrants(&actionUserGrants)
apiFields := actions.WithAPIFields(
actions.SetFields("userGrants", &actionUserGrants),
actions.SetFields("v1",
actions.SetFields("appendUserGrant", func(c *actions.FieldConfig) interface{} {
return func(call goja.FunctionCall) goja.Value {
if len(call.Arguments) != 1 {
panic("exactly one argument expected")
}
object := call.Arguments[0].ToObject(c.Runtime)
if object == nil {
panic("unable to unmarshal arg")
}
grant := actions.UserGrant{}
for _, key := range object.Keys() {
switch key {
case "projectId":
grant.ProjectID = object.Get(key).String()
case "projectGrantId":
grant.ProjectGrantID = object.Get(key).String()
case "roles":
if roles, ok := object.Get(key).Export().([]interface{}); ok {
for _, role := range roles {
if r, ok := role.(string); ok {
grant.Roles = append(grant.Roles, r)
}
}
}
}
}
if grant.ProjectID == "" {
panic("projectId not set")
}
actionUserGrants = append(actionUserGrants, grant)
return nil
}
}),
),
)
for _, a := range triggerActions {
err = actions.Run(actionCtx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail)
actionCtx, cancel := context.WithTimeout(ctx, a.Timeout())
ctxFields := actions.SetContextFields(
actions.SetFields("v1",
actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} {
return func(call goja.FunctionCall) goja.Value {
user, err := l.query.GetUserByID(actionCtx, true, userID)
if err != nil {
panic(err)
}
return object.UserFromQuery(c, user)
}
}),
),
)
err = actions.Run(
actionCtx,
ctxFields,
apiFields,
a.Script,
a.Name,
append(actions.ActionToOptions(a), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
)
cancel()
if err != nil {
return nil, err
}

View File

@@ -17,7 +17,6 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
caos_errors "github.com/zitadel/zitadel/internal/errors"
iam_model "github.com/zitadel/zitadel/internal/iam/model"
"github.com/zitadel/zitadel/internal/query"
)
@@ -121,7 +120,7 @@ func (l *Login) handleJWTAuthorize(w http.ResponseWriter, r *http.Request, authR
q.Set(QueryAuthRequestID, authReq.ID)
userAgentID, ok := http_mw.UserAgentIDFromCtx(r.Context())
if !ok {
l.renderLogin(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "LOGIN-dsgg3", "Errors.AuthRequest.UserAgentNotFound"))
l.renderLogin(w, r, authReq, errors.ThrowPreconditionFailed(nil, "LOGIN-dsgg3", "Errors.AuthRequest.UserAgentNotFound"))
return
}
nonce, err := l.idpConfigAlg.Encrypt([]byte(userAgentID))
@@ -166,7 +165,7 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
l.handleExternalUserAuthenticated(w, r, authReq, idpConfig, userAgentID, tokens)
return
}
l.renderError(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "RP-asff2", "Errors.ExternalIDP.IDPTypeNotImplemented"))
l.renderError(w, r, authReq, errors.ThrowPreconditionFailed(nil, "RP-asff2", "Errors.ExternalIDP.IDPTypeNotImplemented"))
}
func (l *Login) getRPConfig(ctx context.Context, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) (rp.RelyingParty, error) {
@@ -178,7 +177,7 @@ func (l *Login) getRPConfig(ctx context.Context, idpConfig *iam_model.IDPConfigV
return rp.NewRelyingPartyOIDC(idpConfig.OIDCIssuer, idpConfig.OIDCClientID, oidcClientSecret, l.baseURL(ctx)+callbackEndpoint, idpConfig.OIDCScopes, rp.WithVerifierOpts(rp.WithIssuedAtOffset(3*time.Second)))
}
if idpConfig.OAuthAuthorizationEndpoint == "" || idpConfig.OAuthTokenEndpoint == "" {
return nil, caos_errors.ThrowPreconditionFailed(nil, "RP-4n0fs", "Errors.IdentityProvider.InvalidConfig")
return nil, errors.ThrowPreconditionFailed(nil, "RP-4n0fs", "Errors.IdentityProvider.InvalidConfig")
}
oauth2Config := &oauth2.Config{
ClientID: idpConfig.OIDCClientID,
@@ -361,7 +360,7 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
if len(authReq.LinkingUsers) == 0 {
l.renderError(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "LOGIN-asfg3", "Errors.ExternalIDP.NoExternalUserData"))
l.renderError(w, r, authReq, errors.ThrowPreconditionFailed(nil, "LOGIN-asfg3", "Errors.ExternalIDP.NoExternalUserData"))
return
}
@@ -407,19 +406,19 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
}
func (l *Login) mapExternalNotFoundOptionFormDataToLoginUser(formData *externalNotFoundOptionFormData) *domain.ExternalUser {
isEmailVerified := formData.externalRegisterFormData.ExternalEmailVerified && formData.externalRegisterFormData.Email == formData.externalRegisterFormData.ExternalEmail
isPhoneVerified := formData.externalRegisterFormData.ExternalPhoneVerified && formData.externalRegisterFormData.Phone == formData.externalRegisterFormData.ExternalPhone
isEmailVerified := formData.ExternalEmailVerified && formData.Email == formData.ExternalEmail
isPhoneVerified := formData.ExternalPhoneVerified && formData.Phone == formData.ExternalPhone
return &domain.ExternalUser{
IDPConfigID: formData.externalRegisterFormData.ExternalIDPConfigID,
ExternalUserID: formData.externalRegisterFormData.ExternalIDPExtUserID,
PreferredUsername: formData.externalRegisterFormData.Username,
DisplayName: formData.externalRegisterFormData.Email,
FirstName: formData.externalRegisterFormData.Firstname,
LastName: formData.externalRegisterFormData.Lastname,
NickName: formData.externalRegisterFormData.Nickname,
Email: formData.externalRegisterFormData.Email,
IDPConfigID: formData.ExternalIDPConfigID,
ExternalUserID: formData.ExternalIDPExtUserID,
PreferredUsername: formData.Username,
DisplayName: formData.Email,
FirstName: formData.Firstname,
LastName: formData.Lastname,
NickName: formData.Nickname,
Email: formData.Email,
IsEmailVerified: isEmailVerified,
Phone: formData.externalRegisterFormData.Phone,
Phone: formData.Phone,
IsPhoneVerified: isPhoneVerified,
}
}

View File

@@ -4,19 +4,17 @@ import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
)
func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPConfig) (*domain.IDPConfig, error) {
if config.OIDCConfig == nil && config.JWTConfig == nil {
return nil, caos_errs.ThrowInvalidArgument(nil, "IDP-s8nn3", "Errors.IDPConfig.Invalid")
return nil, errors.ThrowInvalidArgument(nil, "IDP-s8nn3", "Errors.IDPConfig.Invalid")
}
idpConfigID, err := c.idGenerator.Next()
if err != nil {
@@ -86,13 +84,13 @@ func (c *Commands) ChangeDefaultIDPConfig(ctx context.Context, config *domain.ID
return nil, err
}
if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified {
return nil, caos_errs.ThrowNotFound(nil, "INSTANCE-m0e3r", "Errors.IDPConfig.NotExisting")
return nil, errors.ThrowNotFound(nil, "INSTANCE-m0e3r", "Errors.IDPConfig.NotExisting")
}
instanceAgg := InstanceAggregateFromWriteModel(&existingIDP.WriteModel)
changedEvent, hasChanged := existingIDP.NewChangedEvent(ctx, instanceAgg, config.IDPConfigID, config.Name, config.StylingType, config.AutoRegister)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-3k0fs", "Errors.IAM.IDPConfig.NotChanged")
return nil, errors.ThrowPreconditionFailed(nil, "INSTANCE-3k0fs", "Errors.IAM.IDPConfig.NotChanged")
}
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
if err != nil {
@@ -111,7 +109,7 @@ func (c *Commands) DeactivateDefaultIDPConfig(ctx context.Context, idpID string)
return nil, err
}
if existingIDP.State != domain.IDPConfigStateActive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-2n0fs", "Errors.IAM.IDPConfig.NotActive")
return nil, errors.ThrowPreconditionFailed(nil, "INSTANCE-2n0fs", "Errors.IAM.IDPConfig.NotActive")
}
instanceAgg := InstanceAggregateFromWriteModel(&existingIDP.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, instance.NewIDPConfigDeactivatedEvent(ctx, instanceAgg, idpID))
@@ -131,7 +129,7 @@ func (c *Commands) ReactivateDefaultIDPConfig(ctx context.Context, idpID string)
return nil, err
}
if existingIDP.State != domain.IDPConfigStateInactive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-5Mo0d", "Errors.IAM.IDPConfig.NotInactive")
return nil, errors.ThrowPreconditionFailed(nil, "INSTANCE-5Mo0d", "Errors.IAM.IDPConfig.NotInactive")
}
instanceAgg := InstanceAggregateFromWriteModel(&existingIDP.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, instance.NewIDPConfigReactivatedEvent(ctx, instanceAgg, idpID))
@@ -151,7 +149,7 @@ func (c *Commands) RemoveDefaultIDPConfig(ctx context.Context, idpID string, idp
return nil, err
}
if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified {
return nil, caos_errs.ThrowNotFound(nil, "INSTANCE-4M0xy", "Errors.IDPConfig.NotExisting")
return nil, errors.ThrowNotFound(nil, "INSTANCE-4M0xy", "Errors.IDPConfig.NotExisting")
}
instanceAgg := InstanceAggregateFromWriteModel(&existingIDP.WriteModel)
@@ -186,7 +184,7 @@ func (c *Commands) getInstanceIDPConfigByID(ctx context.Context, idpID string) (
return nil, err
}
if !config.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "INSTANCE-p0pFF", "Errors.IDPConfig.NotExisting")
return nil, errors.ThrowNotFound(nil, "INSTANCE-p0pFF", "Errors.IDPConfig.NotExisting")
}
return writeModelToIDPConfig(&config.IDPConfigWriteModel), nil
}

View File

@@ -81,7 +81,7 @@ func (c *Commands) SetUpOrg(ctx context.Context, o *OrgSetup, userIDs ...string)
return c.setUpOrgWithIDs(ctx, o, orgID, userID, userIDs...)
}
//AddOrgCommand defines the commands to create a new org,
// AddOrgCommand defines the commands to create a new org,
// this includes the verified default domain
func AddOrgCommand(ctx context.Context, a *org.Aggregate, name string, userIDs ...string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
@@ -117,7 +117,7 @@ func (c *Commands) checkOrgExists(ctx context.Context, orgID string) error {
return err
}
if orgWriteModel.State == domain.OrgStateUnspecified || orgWriteModel.State == domain.OrgStateRemoved {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0fs", "Errors.Org.NotFound")
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-QXPGs", "Errors.Org.NotFound")
}
return nil
}

View File

@@ -165,7 +165,7 @@ func (c *Commands) checkProjectExists(ctx context.Context, projectID, resourceOw
return err
}
if projectWriteModel.State == domain.ProjectStateUnspecified || projectWriteModel.State == domain.ProjectStateRemoved {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0fs", "Errors.Project.NotFound")
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-EbFMN", "Errors.Project.NotFound")
}
return nil
}

View File

@@ -391,7 +391,7 @@ func (c *Commands) checkUserExists(ctx context.Context, userID, resourceOwner st
return err
}
if !isUserStateExists(existingUser.UserState) {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0fs", "Errors.User.NotFound")
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-uXHNj", "Errors.User.NotFound")
}
return nil
}

View File

@@ -31,7 +31,7 @@ func (c *Commands) AddUserGrant(ctx context.Context, usergrant *domain.UserGrant
func (c *Commands) addUserGrant(ctx context.Context, userGrant *domain.UserGrant, resourceOwner string) (command eventstore.Command, _ *UserGrantWriteModel, err error) {
if !userGrant.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0fs", "Errors.UserGrant.Invalid")
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-kVfMa", "Errors.UserGrant.Invalid")
}
err = c.checkUserGrantPreCondition(ctx, userGrant, resourceOwner)
if err != nil {

View File

@@ -1,5 +1,7 @@
package domain
import "strconv"
type FlowState int32
const (
@@ -17,6 +19,7 @@ type FlowType int32
const (
FlowTypeUnspecified FlowType = iota
FlowTypeExternalAuthentication
FlowTypeCustomiseToken
flowTypeCount
)
@@ -25,15 +28,51 @@ func (s FlowType) Valid() bool {
}
func (s FlowType) HasTrigger(triggerType TriggerType) bool {
switch triggerType {
case TriggerTypePostAuthentication:
return s == FlowTypeExternalAuthentication
case TriggerTypePreCreation:
return s == FlowTypeExternalAuthentication
case TriggerTypePostCreation:
return s == FlowTypeExternalAuthentication
for _, trigger := range s.TriggerTypes() {
if trigger == triggerType {
return true
}
}
return false
}
func (s FlowType) TriggerTypes() []TriggerType {
switch s {
case FlowTypeExternalAuthentication:
return []TriggerType{
TriggerTypePostAuthentication,
TriggerTypePreCreation,
TriggerTypePostCreation,
}
case FlowTypeCustomiseToken:
return []TriggerType{
TriggerTypePreUserinfoCreation,
TriggerTypePreAccessTokenCreation,
}
default:
return false
return nil
}
}
func (s FlowType) ID() string {
if s < 0 && s >= flowTypeCount {
return FlowTypeUnspecified.ID()
}
return strconv.Itoa(int(s))
}
func (s FlowType) LocalizationKey() string {
if s < 0 && s >= flowTypeCount {
return FlowTypeUnspecified.LocalizationKey()
}
switch s {
case FlowTypeExternalAuthentication:
return "Action.Flow.Type.ExternalAuthentication"
case FlowTypeCustomiseToken:
return "Action.Flow.Type.CustomiseToken"
default:
return "Action.Flow.Type.Unspecified"
}
}
@@ -44,9 +83,39 @@ const (
TriggerTypePostAuthentication
TriggerTypePreCreation
TriggerTypePostCreation
TriggerTypePreUserinfoCreation
TriggerTypePreAccessTokenCreation
triggerTypeCount
)
func (s TriggerType) Valid() bool {
return s >= 0 && s < triggerTypeCount
}
func (s TriggerType) ID() string {
if !s.Valid() {
return TriggerTypeUnspecified.ID()
}
return strconv.Itoa(int(s))
}
func (s TriggerType) LocalizationKey() string {
if !s.Valid() {
return FlowTypeUnspecified.LocalizationKey()
}
switch s {
case TriggerTypePostAuthentication:
return "Action.TriggerType.PostAuthentication"
case TriggerTypePreCreation:
return "Action.TriggerType.PreCreation"
case TriggerTypePostCreation:
return "Action.TriggerType.PostCreation"
case TriggerTypePreUserinfoCreation:
return "Action.TriggerType.PreUserinfoCreation"
case TriggerTypePreAccessTokenCreation:
return "Action.TriggerType.PreAccessTokenCreation"
default:
return "Action.TriggerType.Unspecified"
}
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/drone/envsubst"
"github.com/jarcoal/jpath"
"github.com/sony/sonyflake"
"github.com/zitadel/logging"
)
type sonyflakeGenerator struct {
@@ -31,10 +32,12 @@ func (s *sonyflakeGenerator) Next() (string, error) {
}
var (
GeneratorConfig *Config = nil
sonyFlakeGenerator *Generator = nil
GeneratorConfig *Config = nil
sonyFlakeGenerator Generator = nil
)
// SonyFlakeGenerator creates a new id generator
// the function panics if the generator cannot be created
func SonyFlakeGenerator() Generator {
if sonyFlakeGenerator == nil {
sfg := Generator(&sonyflakeGenerator{
@@ -44,10 +47,10 @@ func SonyFlakeGenerator() Generator {
}),
})
sonyFlakeGenerator = &sfg
sonyFlakeGenerator = sfg
}
return *sonyFlakeGenerator
return sonyFlakeGenerator
}
// the following is a copy of sonyflake (https://github.com/sony/sonyflake/blob/master/sonyflake.go)
@@ -88,40 +91,41 @@ func isPrivateIPv4(ip net.IP) bool {
func machineID() (uint16, error) {
if GeneratorConfig == nil {
return 0, errors.New("cannot create a unique id for the machine, generator has not been configured")
logging.Panic("cannot create a unique id for the machine, generator has not been configured")
}
errors := []string{}
if GeneratorConfig.Identification.PrivateIp.Enabled {
ip, ipErr := lower16BitPrivateIP()
if ipErr == nil {
ip, err := lower16BitPrivateIP()
if err == nil {
return ip, nil
}
errors = append(errors, fmt.Sprintf("failed to get Private IP address %s", ipErr))
errors = append(errors, fmt.Sprintf("failed to get Private IP address %s", err))
}
if GeneratorConfig.Identification.Hostname.Enabled {
hn, hostErr := hostname()
if hostErr == nil {
hn, err := hostname()
if err == nil {
return hn, nil
}
errors = append(errors, fmt.Sprintf("failed to get Hostname %s", hostErr))
errors = append(errors, fmt.Sprintf("failed to get Hostname %s", err))
}
if GeneratorConfig.Identification.Webhook.Enabled {
cid, cidErr := metadataWebhookID()
if cidErr == nil {
cid, err := metadataWebhookID()
if err == nil {
return cid, nil
}
errors = append(errors, fmt.Sprintf("failed to query metadata webhook %s", cidErr))
errors = append(errors, fmt.Sprintf("failed to query metadata webhook %s", err))
}
if len(errors) == 0 {
errors = append(errors, "No machine identification method enabled.")
}
return 0, fmt.Errorf("none of the enabled methods for identifying the machine succeeded: %s", strings.Join(errors, ". "))
logging.WithFields("errors", strings.Join(errors, ", ")).Panic("none of the enabled methods for identifying the machine succeeded")
//this return will never happen because of panic one line before
return 0, nil
}
func lower16BitPrivateIP() (uint16, error) {

View File

@@ -9,12 +9,15 @@ import (
sq "github.com/Masterminds/squirrel"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query/projection"
)
const (
maxTimeout = 20 * time.Second
)
var (
actionTable = table{
name: projection.ActionTable,
@@ -80,10 +83,17 @@ type Action struct {
Name string
Script string
Timeout time.Duration
timeout time.Duration
AllowedToFail bool
}
func (a *Action) Timeout() time.Duration {
if a.timeout > 0 && a.timeout < maxTimeout {
return a.timeout
}
return maxTimeout
}
type ActionSearchQueries struct {
SearchRequest
Queries []SearchQuery
@@ -180,7 +190,7 @@ func prepareActionsQuery() (sq.SelectBuilder, func(rows *sql.Rows) (*Actions, er
&action.State,
&action.Name,
&action.Script,
&action.Timeout,
&action.timeout,
&action.AllowedToFail,
&count,
)
@@ -227,7 +237,7 @@ func prepareActionQuery() (sq.SelectBuilder, func(row *sql.Row) (*Action, error)
&action.State,
&action.Name,
&action.Script,
&action.Timeout,
&action.timeout,
&action.AllowedToFail,
)
if err != nil {

View File

@@ -8,7 +8,6 @@ import (
sq "github.com/Masterminds/squirrel"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query/projection"
@@ -155,6 +154,8 @@ func prepareTriggerActionsQuery() (sq.SelectBuilder, func(*sql.Rows) ([]*Action,
ActionColumnSequence.identifier(),
ActionColumnName.identifier(),
ActionColumnScript.identifier(),
ActionColumnAllowedToFail.identifier(),
ActionColumnTimeout.identifier(),
).
From(flowsTriggersTable.name).
LeftJoin(join(ActionColumnID, FlowsTriggersColumnActionID)).
@@ -172,6 +173,8 @@ func prepareTriggerActionsQuery() (sq.SelectBuilder, func(*sql.Rows) ([]*Action,
&action.Sequence,
&action.Name,
&action.Script,
&action.AllowedToFail,
&action.timeout,
)
if err != nil {
return nil, err
@@ -197,6 +200,8 @@ func prepareFlowQuery(flowType domain.FlowType) (sq.SelectBuilder, func(*sql.Row
ActionColumnSequence.identifier(),
ActionColumnName.identifier(),
ActionColumnScript.identifier(),
ActionColumnAllowedToFail.identifier(),
ActionColumnTimeout.identifier(),
FlowsTriggersColumnTriggerType.identifier(),
FlowsTriggersColumnTriggerSequence.identifier(),
FlowsTriggersColumnFlowType.identifier(),
@@ -222,6 +227,8 @@ func prepareFlowQuery(flowType domain.FlowType) (sq.SelectBuilder, func(*sql.Row
actionSequence sql.NullInt64
actionName sql.NullString
actionScript sql.NullString
actionAllowedToFail sql.NullBool
actionTimeout sql.NullInt64
triggerType domain.TriggerType
triggerSequence int
@@ -235,6 +242,8 @@ func prepareFlowQuery(flowType domain.FlowType) (sq.SelectBuilder, func(*sql.Row
&actionSequence,
&actionName,
&actionScript,
&actionAllowedToFail,
&actionTimeout,
&triggerType,
&triggerSequence,
&flow.Type,
@@ -257,6 +266,8 @@ func prepareFlowQuery(flowType domain.FlowType) (sq.SelectBuilder, func(*sql.Row
Sequence: uint64(actionSequence.Int64),
Name: actionName.String,
Script: actionScript.String,
AllowedToFail: actionAllowedToFail.Bool,
timeout: time.Duration(actionTimeout.Int64),
})
}

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"regexp"
"testing"
"time"
sq "github.com/Masterminds/squirrel"
@@ -39,6 +40,8 @@ func Test_FlowPrepares(t *testing.T) {
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.actions2.allowed_to_fail,`+
` projections.actions2.timeout,`+
` projections.flows_triggers.trigger_type,`+
` projections.flows_triggers.trigger_sequence,`+
` projections.flows_triggers.flow_type,`+
@@ -71,6 +74,8 @@ func Test_FlowPrepares(t *testing.T) {
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.actions2.allowed_to_fail,`+
` projections.actions2.timeout,`+
` projections.flows_triggers.trigger_type,`+
` projections.flows_triggers.trigger_sequence,`+
` projections.flows_triggers.flow_type,`+
@@ -88,6 +93,8 @@ func Test_FlowPrepares(t *testing.T) {
"sequence",
"name",
"script",
"allowed_to_fail",
"timeout",
//flow
"trigger_type",
"trigger_sequence",
@@ -106,6 +113,8 @@ func Test_FlowPrepares(t *testing.T) {
uint64(20211115),
"action-name",
"script",
true,
10000000000,
domain.TriggerTypePreCreation,
uint64(20211109),
domain.FlowTypeExternalAuthentication,
@@ -132,6 +141,8 @@ func Test_FlowPrepares(t *testing.T) {
Sequence: 20211115,
Name: "action-name",
Script: "script",
AllowedToFail: true,
timeout: 10 * time.Second,
},
},
},
@@ -152,6 +163,8 @@ func Test_FlowPrepares(t *testing.T) {
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.actions2.allowed_to_fail,`+
` projections.actions2.timeout,`+
` projections.flows_triggers.trigger_type,`+
` projections.flows_triggers.trigger_sequence,`+
` projections.flows_triggers.flow_type,`+
@@ -169,6 +182,8 @@ func Test_FlowPrepares(t *testing.T) {
"sequence",
"name",
"script",
"allowed_to_fail",
"timeout",
//flow
"trigger_type",
"trigger_sequence",
@@ -187,6 +202,8 @@ func Test_FlowPrepares(t *testing.T) {
uint64(20211115),
"action-name-pre",
"script",
true,
10000000000,
domain.TriggerTypePreCreation,
uint64(20211109),
domain.FlowTypeExternalAuthentication,
@@ -203,6 +220,8 @@ func Test_FlowPrepares(t *testing.T) {
uint64(20211115),
"action-name-post",
"script",
false,
5000000000,
domain.TriggerTypePostCreation,
uint64(20211109),
domain.FlowTypeExternalAuthentication,
@@ -229,6 +248,8 @@ func Test_FlowPrepares(t *testing.T) {
Sequence: 20211115,
Name: "action-name-pre",
Script: "script",
AllowedToFail: true,
timeout: 10 * time.Second,
},
},
domain.TriggerTypePostCreation: {
@@ -241,6 +262,8 @@ func Test_FlowPrepares(t *testing.T) {
Sequence: 20211115,
Name: "action-name-post",
Script: "script",
AllowedToFail: false,
timeout: 5 * time.Second,
},
},
},
@@ -261,6 +284,8 @@ func Test_FlowPrepares(t *testing.T) {
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.actions2.allowed_to_fail,`+
` projections.actions2.timeout,`+
` projections.flows_triggers.trigger_type,`+
` projections.flows_triggers.trigger_sequence,`+
` projections.flows_triggers.flow_type,`+
@@ -278,6 +303,8 @@ func Test_FlowPrepares(t *testing.T) {
"sequence",
"name",
"script",
"allowed_to_fail",
"timeout",
//flow
"trigger_type",
"trigger_sequence",
@@ -296,6 +323,8 @@ func Test_FlowPrepares(t *testing.T) {
nil,
nil,
nil,
nil,
nil,
domain.TriggerTypePostCreation,
uint64(20211109),
domain.FlowTypeExternalAuthentication,
@@ -329,6 +358,8 @@ func Test_FlowPrepares(t *testing.T) {
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script,`+
` projections.actions2.allowed_to_fail,`+
` projections.actions2.timeout,`+
` projections.flows_triggers.trigger_type,`+
` projections.flows_triggers.trigger_sequence,`+
` projections.flows_triggers.flow_type,`+
@@ -360,7 +391,9 @@ func Test_FlowPrepares(t *testing.T) {
` projections.actions2.action_state,`+
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script`+
` projections.actions2.script,`+
` projections.actions2.allowed_to_fail,`+
` projections.actions2.timeout`+
` FROM projections.flows_triggers`+
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
nil,
@@ -381,7 +414,9 @@ func Test_FlowPrepares(t *testing.T) {
` projections.actions2.action_state,`+
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script`+
` projections.actions2.script,`+
` projections.actions2.allowed_to_fail,`+
` projections.actions2.timeout`+
` FROM projections.flows_triggers`+
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
[]string{
@@ -393,6 +428,8 @@ func Test_FlowPrepares(t *testing.T) {
"sequence",
"name",
"script",
"allowed_to_fail",
"timeout",
},
[][]driver.Value{
{
@@ -404,6 +441,8 @@ func Test_FlowPrepares(t *testing.T) {
uint64(20211115),
"action-name",
"script",
true,
10000000000,
},
},
),
@@ -418,6 +457,8 @@ func Test_FlowPrepares(t *testing.T) {
Sequence: 20211115,
Name: "action-name",
Script: "script",
AllowedToFail: true,
timeout: 10 * time.Second,
},
},
},
@@ -433,7 +474,9 @@ func Test_FlowPrepares(t *testing.T) {
` projections.actions2.action_state,`+
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script`+
` projections.actions2.script,`+
` projections.actions2.allowed_to_fail,`+
` projections.actions2.timeout`+
` FROM projections.flows_triggers`+
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
[]string{
@@ -445,6 +488,8 @@ func Test_FlowPrepares(t *testing.T) {
"sequence",
"name",
"script",
"allowed_to_fail",
"timeout",
},
[][]driver.Value{
{
@@ -456,6 +501,8 @@ func Test_FlowPrepares(t *testing.T) {
uint64(20211115),
"action-name-1",
"script",
true,
10000000000,
},
{
"action-id-2",
@@ -466,6 +513,8 @@ func Test_FlowPrepares(t *testing.T) {
uint64(20211115),
"action-name-2",
"script",
false,
5000000000,
},
},
),
@@ -480,6 +529,8 @@ func Test_FlowPrepares(t *testing.T) {
Sequence: 20211115,
Name: "action-name-1",
Script: "script",
AllowedToFail: true,
timeout: 10 * time.Second,
},
{
ID: "action-id-2",
@@ -490,6 +541,8 @@ func Test_FlowPrepares(t *testing.T) {
Sequence: 20211115,
Name: "action-name-2",
Script: "script",
AllowedToFail: false,
timeout: 5 * time.Second,
},
},
},
@@ -505,7 +558,9 @@ func Test_FlowPrepares(t *testing.T) {
` projections.actions2.action_state,`+
` projections.actions2.sequence,`+
` projections.actions2.name,`+
` projections.actions2.script`+
` projections.actions2.script,`+
` projections.actions2.allowed_to_fail,`+
` projections.actions2.timeout`+
` FROM projections.flows_triggers`+
` LEFT JOIN projections.actions2 ON projections.flows_triggers.action_id = projections.actions2.id`),
sql.ErrConnDone,

View File

@@ -107,7 +107,7 @@ func Test_ActionPrepares(t *testing.T) {
Sequence: 20211109,
Name: "action-name",
Script: "script",
Timeout: 1 * time.Second,
timeout: 1 * time.Second,
AllowedToFail: true,
},
},
@@ -185,7 +185,7 @@ func Test_ActionPrepares(t *testing.T) {
Sequence: 20211109,
Name: "action-name-1",
Script: "script",
Timeout: 1 * time.Second,
timeout: 1 * time.Second,
AllowedToFail: true,
},
{
@@ -197,7 +197,7 @@ func Test_ActionPrepares(t *testing.T) {
Sequence: 20211109,
Name: "action-name-2",
Script: "script",
Timeout: 1 * time.Second,
timeout: 1 * time.Second,
AllowedToFail: true,
},
},
@@ -310,7 +310,7 @@ func Test_ActionPrepares(t *testing.T) {
Sequence: 20211109,
Name: "action-name",
Script: "script",
Timeout: 1 * time.Second,
timeout: 1 * time.Second,
AllowedToFail: true,
},
},

View File

@@ -10,11 +10,10 @@ import (
"github.com/rakyll/statik/fs"
"golang.org/x/text/language"
sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/api/authz"
sd "github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/repository/action"

View File

@@ -160,7 +160,7 @@ const (
textCompareMax
)
//Deprecated: Use TextComparison, will be removed as soon as all calls are changed to query
// Deprecated: Use TextComparison, will be removed as soon as all calls are changed to query
func TextComparisonFromMethod(m domain.SearchMethod) TextComparison {
switch m {
case domain.SearchMethodEquals:
@@ -244,7 +244,7 @@ const (
numberCompareMax
)
//Deprecated: Use NumberComparison, will be removed as soon as all calls are changed to query
// Deprecated: Use NumberComparison, will be removed as soon as all calls are changed to query
func NumberComparisonFromMethod(m domain.SearchMethod) NumberComparison {
switch m {
case domain.SearchMethodEquals:

View File

@@ -911,3 +911,17 @@ Application:
GrantType:
Refresh:
NoAuthCode: Refresh Token nur in Kombination mit Authorization Code erlaubt.
Action:
Flow:
Type:
Unspecified: Unspezifiziert
ExternalAuthentication: Externe Authentifizierung
CustomiseToken: Token ergänzen
TriggerType:
Unspecified: Unspezifiziert
PostAuthentication: Nach Authentifizierung
PreCreation: Vor Erstellung
PostCreation: Nach Erstellung
PreUserinfoCreation: Vor Userinfo Erstellung
PreAccessTokenCreation: Vor Access Token Erstellung

View File

@@ -911,3 +911,17 @@ Application:
GrantType:
Refresh:
NoAuthCode: Refresh Token only allowed in combination with Authorization Code.
Action:
Flow:
Type:
Unspecified: Unspecified
ExternalAuthentication: External Authentication
CustomiseToken: Complement Token
TriggerType:
Unspecified: Unspecified
PostAuthentication: Post Authentication
PreCreation: Pre Creation
PostCreation: Post Creation
PreUserinfoCreation: Pre Userinfo creation
PreAccessTokenCreation: Pre access token creation

View File

@@ -911,3 +911,17 @@ Application:
GrantType:
Refresh:
NoAuthCode: Le jeton de rafraîchissement n'est autorisé qu'en combinaison avec le code d'autorisation.
Action:
Flow:
Type:
Unspecified: Non spécifié
ExternalAuthentication: Authentification externe
CustomiseToken: Compléter Token
TriggerType:
Unspecified: Non spécifié
PostAuthentication: Authentification postérieure
PreCreation: Pré création
PostCreation: Post-création
PreUserinfoCreation: Pré Userinfo création
PreAccessTokenCreation: Pré access token création

View File

@@ -911,3 +911,17 @@ Application:
GrantType:
Refresh:
NoAuthCode: Refresh Token consentito solo in combinazione con Authorization Code.
Action:
Flow:
Type:
Unspecified: Non specificato
ExternalAuthentication: Autenticazione esterna
CustomiseToken: Completare Token
TriggerType:
Unspecified: Non specificato
PostAuthentication: Post-autenticazione
PreCreation: Pre-creazione
PostCreation: Creazione successiva
PreUserinfoCreation: Pre userinfo creazione
PreAccessTokenCreation: Pre access token creazione

View File

@@ -905,3 +905,17 @@ Application:
GrantType:
Refresh:
NoAuthCode: Refresh Token 仅允许与授权码Authorization Code模式一起使用。
Action:
Flow:
Type:
Unspecified: 未指定的
ExternalAuthentication: 外部认证
CustomiseToken: Complement Token
TriggerType:
Unspecified: 未指定的
PostAuthentication: 后期认证
PreCreation: 创建前
PostCreation: 创建后
PreUserinfoCreation: Pre Userinfo creation
PreAccessTokenCreation: Pre access token creation