diff --git a/cmd/start/start.go b/cmd/start/start.go index 42967ab0bdf..01fe78b7ab6 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -170,9 +170,15 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server if err != nil { return err } + q, err := queue.NewQueue(&queue.Config{ + Client: dbClient, + }) + if err != nil { + return err + } - config.Eventstore.Pusher = new_es.NewEventstore(dbClient) - config.Eventstore.Searcher = new_es.NewEventstore(dbClient) + config.Eventstore.Pusher = new_es.NewEventstore(dbClient, new_es.WithExecutionQueueOption(q)) + config.Eventstore.Searcher = new_es.NewEventstore(dbClient, new_es.WithExecutionQueueOption(q)) config.Eventstore.Querier = old_es.NewPostgres(dbClient) eventstoreClient := eventstore.NewEventstore(config.Eventstore) eventstoreV4 := es_v4.NewEventstoreFromOne(es_v4_pg.New(dbClient, &es_v4_pg.Config{ @@ -282,13 +288,6 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server actionsLogstoreSvc := logstore.New(queries, actionsExecutionDBEmitter, actionsExecutionStdoutEmitter) actions.SetLogstoreService(actionsLogstoreSvc) - q, err := queue.NewQueue(&queue.Config{ - Client: dbClient, - }) - if err != nil { - return err - } - notification.Register( ctx, config.Projections.Customizations["notifications"], @@ -315,12 +314,9 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server notification.Start(ctx) execution.Register( - ctx, - config.Projections.Customizations["execution_handler"], config.Executions, - queries, - eventstoreClient.EventTypes(), q, + keys.Target, ) execution.Start(ctx) @@ -440,7 +436,7 @@ func startAPIs( http_util.WithMaxAge(int(math.Floor(config.Quotas.Access.ExhaustedCookieMaxAge.Seconds()))), ) limitingAccessInterceptor := middleware.NewAccessInterceptor(accessSvc, exhaustedCookieHandler, &config.Quotas.Access.AccessConfig) - apis, err := api.New(ctx, config.Port, router, queries, verifier, config.SystemAuthZ, config.InternalAuthZ, tlsConfig, config.ExternalDomain, append(config.InstanceHostHeaders, config.PublicHostHeaders...), limitingAccessInterceptor) + apis, err := api.New(ctx, config.Port, router, queries, verifier, config.SystemAuthZ, config.InternalAuthZ, tlsConfig, config.ExternalDomain, append(config.InstanceHostHeaders, config.PublicHostHeaders...), limitingAccessInterceptor, keys.Target) if err != nil { return nil, fmt.Errorf("error creating api %w", err) } @@ -582,6 +578,7 @@ func startAPIs( queries, authRepo, keys.OIDC, + keys.Target, keys.OIDCKey, eventstore, userAgentInterceptor, @@ -596,7 +593,7 @@ func startAPIs( } apis.RegisterHandlerPrefixes(oidcServer, oidcPrefixes...) - samlProvider, err := saml.NewProvider(config.SAML, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.SAML, eventstore, dbClient, instanceInterceptor.Handler, userAgentInterceptor, limitingAccessInterceptor) + samlProvider, err := saml.NewProvider(config.SAML, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.SAML, keys.Target, eventstore, dbClient, instanceInterceptor.Handler, userAgentInterceptor, limitingAccessInterceptor) if err != nil { return nil, fmt.Errorf("unable to start saml provider: %w", err) } diff --git a/go.mod b/go.mod index eb4856d0874..7e7b1745fe1 100644 --- a/go.mod +++ b/go.mod @@ -65,9 +65,10 @@ require ( github.com/pquerna/otp v1.5.0 github.com/rakyll/statik v0.1.7 github.com/redis/go-redis/v9 v9.8.0 - github.com/riverqueue/river v0.22.0 - github.com/riverqueue/river/riverdriver v0.22.0 - github.com/riverqueue/river/rivertype v0.22.0 + github.com/riverqueue/river v0.24.0 + github.com/riverqueue/river/riverdriver v0.24.0 + github.com/riverqueue/river/riverdriver/riverdatabasesql v0.24.0 + github.com/riverqueue/river/rivertype v0.24.0 github.com/riverqueue/rivercontrib/otelriver v0.5.0 github.com/robfig/cron/v3 v3.0.1 github.com/rs/cors v1.11.1 @@ -77,7 +78,7 @@ require ( github.com/sony/sonyflake v1.2.1 github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.20.1 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 github.com/ttacon/libphonenumber v1.2.1 github.com/twilio/twilio-go v1.26.1 github.com/zitadel/exifremove v0.1.0 @@ -97,12 +98,12 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.35.0 go.opentelemetry.io/otel/trace v1.35.0 go.uber.org/mock v0.5.2 - golang.org/x/crypto v0.38.0 + golang.org/x/crypto v0.41.0 golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 - golang.org/x/net v0.40.0 + golang.org/x/net v0.42.0 golang.org/x/oauth2 v0.30.0 golang.org/x/sync v0.16.0 - golang.org/x/text v0.27.0 + golang.org/x/text v0.28.0 google.golang.org/api v0.233.0 google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9 google.golang.org/grpc v1.72.1 @@ -150,7 +151,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/riverqueue/river/rivershared v0.22.0 // indirect + github.com/riverqueue/river/rivershared v0.24.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect @@ -225,7 +226,6 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/riverqueue/river/riverdriver/riverpgxv5 v0.22.0 github.com/rs/xid v1.6.0 // indirect github.com/russellhaering/goxmldsig v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 @@ -238,7 +238,7 @@ require ( github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect - golang.org/x/sys v0.33.0 + golang.org/x/sys v0.35.0 gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect nhooyr.io/websocket v1.8.11 // indirect diff --git a/go.sum b/go.sum index 8cff345217f..aff7620eefe 100644 --- a/go.sum +++ b/go.sum @@ -676,18 +676,18 @@ github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhi github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo= github.com/redis/rueidis v1.0.19/go.mod h1:8B+r5wdnjwK3lTFml5VtxjzGOQAC+5UmujoD12pDrEo= -github.com/riverqueue/river v0.22.0 h1:PO4Ula2RqViQqNs6xjze7yFV6Zq4T3Ffv092+f4S8xQ= -github.com/riverqueue/river v0.22.0/go.mod h1:IRoWoK4RGCiPuVJUV4EWcCl9d/TMQYkk0EEYV/Wgq+U= -github.com/riverqueue/river/riverdriver v0.22.0 h1:i7OSFkUi6x4UKvttdFOIg7NYLYaBOFLJZvkZ0+JWS/8= -github.com/riverqueue/river/riverdriver v0.22.0/go.mod h1:oNdjJCeAJhN/UiZGLNL+guNqWaxMFuSD4lr5x/v/was= -github.com/riverqueue/river/riverdriver/riverdatabasesql v0.22.0 h1:+no3gToOK9SmWg0pDPKfOGSCsrxqqaFdD8K1NQndRbY= -github.com/riverqueue/river/riverdriver/riverdatabasesql v0.22.0/go.mod h1:mygiHa1dnlKRjxT1//wIvfT2fMTbfXKm37NcsxoyBoQ= -github.com/riverqueue/river/riverdriver/riverpgxv5 v0.22.0 h1:2TWbVL73gipJ2/4JNCQbifaNj+BCC/Zxpp30o1D8RTg= -github.com/riverqueue/river/riverdriver/riverpgxv5 v0.22.0/go.mod h1:TZY/BG8w/nDxkraAEvvgyVupIz0b4+PQVUW0kIiy1fc= -github.com/riverqueue/river/rivershared v0.22.0 h1:hLPHr98d6OEfmUJ4KpIXgoy2tbQ14htWILcRBHJF11U= -github.com/riverqueue/river/rivershared v0.22.0/go.mod h1:BK+hvhECfdDLWNDH3xiGI95m2YoPfVtECZLT+my8XM8= -github.com/riverqueue/river/rivertype v0.22.0 h1:rSRhbd5uV/BaFTPxReCxuYTAzx+/riBZJlZdREADvO4= -github.com/riverqueue/river/rivertype v0.22.0/go.mod h1:lmdl3vLNDfchDWbYdW2uAocIuwIN+ZaXqAukdSCFqWs= +github.com/riverqueue/river v0.24.0 h1:CesL6vymWgz0d+zNwtnSGRWaB+E8Dax+o9cxD7sUmKc= +github.com/riverqueue/river v0.24.0/go.mod h1:UZ3AxU5t6WtyqNssaea/AkRS8h/kJ+E9ImSB3xyb3ns= +github.com/riverqueue/river/riverdriver v0.24.0 h1:HqGgGkls11u+YKDA7cKOdYKlQwRNJyHuGa3UtOvpdT0= +github.com/riverqueue/river/riverdriver v0.24.0/go.mod h1:dEew9DDIKenNvzpm8Edw8+PkqP3c0zl1fKjiQTq2n/w= +github.com/riverqueue/river/riverdriver/riverdatabasesql v0.24.0 h1:PFm6nKOqSkqo70yuibl42PxnHzfeAwC4JLL0rg9g8qs= +github.com/riverqueue/river/riverdriver/riverdatabasesql v0.24.0/go.mod h1:/7qlKzZvzkN04++TxcvUw7FmKSJ7j1qq7lvzL30MhJQ= +github.com/riverqueue/river/riverdriver/riverpgxv5 v0.24.0 h1:yV37OIbRrhRwIiGeRT7P4D3szhAemu87BgCf8gTCoU4= +github.com/riverqueue/river/riverdriver/riverpgxv5 v0.24.0/go.mod h1:QfznySVKC4ljx53syd/bA/LRSsydAyuD3Q9/EbSniKA= +github.com/riverqueue/river/rivershared v0.24.0 h1:KysokksW75pug2a5RTOc6WESOupWmsylVc6VWvAx+4Y= +github.com/riverqueue/river/rivershared v0.24.0/go.mod h1:UIBfSdai0oWFlwFcoqG4DZX83iA/fLWTEBGrj7Oe1ho= +github.com/riverqueue/river/rivertype v0.24.0 h1:xrQZm/h6U8TBPyTsQPYD5leOapuoBAcdz30bdBwTqOg= +github.com/riverqueue/river/rivertype v0.24.0/go.mod h1:lmdl3vLNDfchDWbYdW2uAocIuwIN+ZaXqAukdSCFqWs= github.com/riverqueue/rivercontrib/otelriver v0.5.0 h1:dZF4Fy7/3RaIRsyCPdpIJtzEip0pCvoJ44YpSDum8e4= github.com/riverqueue/rivercontrib/otelriver v0.5.0/go.mod h1:rXANcBrlgRvg+auD3/O6Xfs59AWeWNpa/kim62mkxGo= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= @@ -761,8 +761,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= @@ -880,8 +880,8 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= @@ -933,8 +933,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -985,15 +985,15 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= diff --git a/internal/api/api.go b/internal/api/api.go index 18c554ca377..c6f6401c853 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -22,6 +22,7 @@ import ( http_util "github.com/zitadel/zitadel/internal/api/http" http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" "github.com/zitadel/zitadel/internal/api/ui/login" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/telemetry/metrics" "github.com/zitadel/zitadel/internal/telemetry/tracing" @@ -48,6 +49,8 @@ type API struct { authConfig authz.Config systemAuthZ authz.Config connectServices map[string][]string + + targetEncryptionAlgorithm crypto.EncryptionAlgorithm } func (a *API) ListGrpcServices() []string { @@ -99,22 +102,24 @@ func New( externalDomain string, hostHeaders []string, accessInterceptor *http_mw.AccessInterceptor, + targetEncryptionAlgorithm crypto.EncryptionAlgorithm, ) (_ *API, err error) { api := &API{ - port: port, - externalDomain: externalDomain, - verifier: verifier, - health: queries, - router: router, - queries: queries, - accessInterceptor: accessInterceptor, - hostHeaders: hostHeaders, - authConfig: authZ, - systemAuthZ: systemAuthz, - connectServices: make(map[string][]string), + port: port, + externalDomain: externalDomain, + verifier: verifier, + health: queries, + router: router, + queries: queries, + accessInterceptor: accessInterceptor, + hostHeaders: hostHeaders, + authConfig: authZ, + systemAuthZ: systemAuthz, + connectServices: make(map[string][]string), + targetEncryptionAlgorithm: targetEncryptionAlgorithm, } - api.grpcServer = server.CreateServer(api.verifier, systemAuthz, authZ, queries, externalDomain, tlsConfig, accessInterceptor.AccessService()) + api.grpcServer = server.CreateServer(api.verifier, systemAuthz, authZ, queries, externalDomain, tlsConfig, accessInterceptor.AccessService(), targetEncryptionAlgorithm) api.grpcGateway, err = server.CreateGateway(ctx, port, hostHeaders, accessInterceptor, tlsConfig) if err != nil { return nil, err @@ -190,7 +195,7 @@ func (a *API) registerConnectServer(service server.ConnectServer) { connect_middleware.AuthorizationInterceptor(a.verifier, a.systemAuthZ, a.authConfig), connect_middleware.TranslationHandler(), connect_middleware.QuotaExhaustedInterceptor(a.accessInterceptor.AccessService(), system_pb.SystemService_ServiceDesc.ServiceName), - connect_middleware.ExecutionHandler(a.queries), + connect_middleware.ExecutionHandler(a.targetEncryptionAlgorithm), connect_middleware.ValidationHandler(), connect_middleware.ServiceHandler(), connect_middleware.ActivityInterceptor(), diff --git a/internal/api/authz/instance.go b/internal/api/authz/instance.go index 0fe6d6c8aa0..1eacb2383c5 100644 --- a/internal/api/authz/instance.go +++ b/internal/api/authz/instance.go @@ -6,6 +6,7 @@ import ( "golang.org/x/text/language" + "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/feature" ) @@ -23,6 +24,7 @@ type Instance interface { Block() *bool AuditLogRetention() *time.Duration Features() feature.Features + ExecutionRouter() target.Router } type InstanceVerifier interface { @@ -31,13 +33,14 @@ type InstanceVerifier interface { } type instance struct { - id string - projectID string - appID string - clientID string - orgID string - defaultLanguage language.Tag - features feature.Features + id string + projectID string + appID string + clientID string + orgID string + defaultLanguage language.Tag + features feature.Features + executionTargets target.Router } func (i *instance) Block() *bool { @@ -84,6 +87,10 @@ func (i *instance) Features() feature.Features { return i.features } +func (i *instance) ExecutionRouter() target.Router { + return i.executionTargets +} + func GetInstance(ctx context.Context) Instance { instance, ok := ctx.Value(instanceKey).(Instance) if !ok { @@ -142,3 +149,12 @@ func WithFeatures(ctx context.Context, f feature.Features) context.Context { i.features = f return context.WithValue(ctx, instanceKey, i) } + +func WithExecutionRouter(ctx context.Context, router target.Router) context.Context { + i, ok := ctx.Value(instanceKey).(*instance) + if !ok { + i = new(instance) + } + i.executionTargets = router + return context.WithValue(ctx, instanceKey, i) +} diff --git a/internal/api/authz/instance_test.go b/internal/api/authz/instance_test.go index f71cbac5d18..8f533d5c5d6 100644 --- a/internal/api/authz/instance_test.go +++ b/internal/api/authz/instance_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "golang.org/x/text/language" + "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/feature" ) @@ -129,3 +130,7 @@ func (m *mockInstance) EnableImpersonation() bool { func (m *mockInstance) Features() feature.Features { return feature.Features{} } + +func (m *mockInstance) ExecutionRouter() target.Router { + return target.NewRouter(nil) +} diff --git a/internal/api/grpc/action/v2/integration_test/execution_target_test.go b/internal/api/grpc/action/v2/integration_test/execution_target_test.go index 15c9e338e93..36636563bfb 100644 --- a/internal/api/grpc/action/v2/integration_test/execution_target_test.go +++ b/internal/api/grpc/action/v2/integration_test/execution_target_test.go @@ -26,6 +26,7 @@ import ( oidc_api "github.com/zitadel/zitadel/internal/api/oidc" saml_api "github.com/zitadel/zitadel/internal/api/saml" "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/integration" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/pkg/grpc/action/v2" @@ -73,7 +74,7 @@ func TestServer_ExecutionTarget(t *testing.T) { targetCreatedName := gofakeit.Name() targetCreatedURL := "https://nonexistent" - targetCreated := instance.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false) + targetCreated := instance.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, target_domain.TargetTypeCall, false) // request received by target wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instance.ID(), OrgID: orgID, ProjectID: projectID, UserID: userID, Request: middleware.Message{Message: request}} @@ -81,7 +82,7 @@ func TestServer_ExecutionTarget(t *testing.T) { // replace original request with different targetID urlRequest, closeRequest, calledRequest, _ := integration.TestServerCallProto(wantRequest, 0, http.StatusOK, changedRequest) - targetRequest := waitForTarget(ctx, t, instance, urlRequest, domain.TargetTypeCall, false) + targetRequest := waitForTarget(ctx, t, instance, urlRequest, target_domain.TargetTypeCall, false) waitForExecutionOnCondition(ctx, t, instance, conditionRequestFullMethod(fullMethod), []string{targetRequest.GetId()}) @@ -148,7 +149,7 @@ func TestServer_ExecutionTarget(t *testing.T) { // after request with different targetID, return changed response targetResponseURL, closeResponse, calledResponse, _ := integration.TestServerCallProto(wantResponse, 0, http.StatusOK, changedResponse) - targetResponse := waitForTarget(ctx, t, instance, targetResponseURL, domain.TargetTypeCall, false) + targetResponse := waitForTarget(ctx, t, instance, targetResponseURL, target_domain.TargetTypeCall, false) waitForExecutionOnCondition(ctx, t, instance, conditionResponseFullMethod(fullMethod), []string{targetResponse.GetId()}) return func() { closeRequest() @@ -186,7 +187,7 @@ func TestServer_ExecutionTarget(t *testing.T) { wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instance.ID(), OrgID: orgID, ProjectID: projectID, UserID: userID, Request: middleware.Message{Message: request}} urlRequest, closeRequest, calledRequest, _ := integration.TestServerCallProto(wantRequest, 0, http.StatusInternalServerError, nil) - targetRequest := waitForTarget(ctx, t, instance, urlRequest, domain.TargetTypeCall, true) + targetRequest := waitForTarget(ctx, t, instance, urlRequest, target_domain.TargetTypeCall, true) waitForExecutionOnCondition(ctx, t, instance, conditionRequestFullMethod(fullMethod), []string{targetRequest.GetId()}) // GetTarget with used target request.Id = targetRequest.GetId() @@ -214,7 +215,7 @@ func TestServer_ExecutionTarget(t *testing.T) { targetCreatedName := gofakeit.Name() targetCreatedURL := "https://nonexistent" - targetCreated := instance.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false) + targetCreated := instance.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, target_domain.TargetTypeCall, false) // GetTarget with used target request.Id = targetCreated.GetId() @@ -250,7 +251,7 @@ func TestServer_ExecutionTarget(t *testing.T) { // after request with different targetID, return changed response targetResponseURL, closeResponse, calledResponse, _ := integration.TestServerCallProto(wantResponse, 0, http.StatusInternalServerError, nil) - targetResponse := waitForTarget(ctx, t, instance, targetResponseURL, domain.TargetTypeCall, true) + targetResponse := waitForTarget(ctx, t, instance, targetResponseURL, target_domain.TargetTypeCall, true) waitForExecutionOnCondition(ctx, t, instance, conditionResponseFullMethod(fullMethod), []string{targetResponse.GetId()}) return func() { closeResponse() @@ -298,7 +299,7 @@ func TestServer_ExecutionTarget_Event(t *testing.T) { urlRequest, closeF, calledF, resetF := integration.TestServerCall(nil, 0, http.StatusOK, nil) defer closeF() - targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, domain.TargetTypeWebhook, true) + targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, target_domain.TargetTypeWebhook, true) waitForExecutionOnCondition(isolatedIAMOwnerCTX, t, instance, conditionEvent(event), []string{targetResponse.GetId()}) tests := []struct { @@ -356,7 +357,7 @@ func TestServer_ExecutionTarget_Event_LongerThanTargetTimeout(t *testing.T) { urlRequest, closeF, calledF, resetF := integration.TestServerCall(nil, 5*time.Second, http.StatusOK, nil) defer closeF() - targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, domain.TargetTypeWebhook, true) + targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, target_domain.TargetTypeWebhook, true) waitForExecutionOnCondition(isolatedIAMOwnerCTX, t, instance, conditionEvent(event), []string{targetResponse.GetId()}) tests := []struct { @@ -407,7 +408,7 @@ func TestServer_ExecutionTarget_Event_LongerThanTransactionTimeout(t *testing.T) urlRequest, closeF, calledF, resetF := integration.TestServerCall(nil, 1*time.Second, http.StatusOK, nil) defer closeF() - targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, domain.TargetTypeWebhook, true) + targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, target_domain.TargetTypeWebhook, true) waitForExecutionOnCondition(isolatedIAMOwnerCTX, t, instance, conditionEvent(event), []string{targetResponse.GetId()}) tests := []struct { @@ -490,7 +491,7 @@ func waitForExecutionOnCondition(ctx context.Context, t *testing.T, instance *in }, retryDuration, tick, "timeout waiting for expected execution result") } -func waitForTarget(ctx context.Context, t *testing.T, instance *integration.Instance, endpoint string, ty domain.TargetType, interrupt bool) *action.CreateTargetResponse { +func waitForTarget(ctx context.Context, t *testing.T, instance *integration.Instance, endpoint string, ty target_domain.TargetType, interrupt bool) *action.CreateTargetResponse { resp := instance.CreateTarget(ctx, t, "", endpoint, ty, interrupt) retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute) @@ -511,14 +512,14 @@ func waitForTarget(ctx context.Context, t *testing.T, instance *integration.Inst config := got.GetTargets()[0] assert.Equal(ttt, config.GetEndpoint(), endpoint) switch ty { - case domain.TargetTypeWebhook: + case target_domain.TargetTypeWebhook: if !assert.NotNil(ttt, config.GetRestWebhook()) { return } assert.Equal(ttt, interrupt, config.GetRestWebhook().GetInterruptOnError()) - case domain.TargetTypeAsync: + case target_domain.TargetTypeAsync: assert.NotNil(ttt, config.GetRestAsync()) - case domain.TargetTypeCall: + case target_domain.TargetTypeCall: if !assert.NotNil(ttt, config.GetRestCall()) { return } @@ -770,7 +771,7 @@ func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *int targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response) - targetResp := waitForTarget(ctx, t, instance, targetURL, domain.TargetTypeCall, true) + targetResp := waitForTarget(ctx, t, instance, targetURL, target_domain.TargetTypeCall, true) waitForExecutionOnCondition(ctx, t, instance, conditionFunction("preuserinfo"), []string{targetResp.GetId()}) return userResp.GetUserId(), closeF } @@ -1078,7 +1079,7 @@ func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance * targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response) - targetResp := waitForTarget(ctx, t, instance, targetURL, domain.TargetTypeCall, true) + targetResp := waitForTarget(ctx, t, instance, targetURL, target_domain.TargetTypeCall, true) waitForExecutionOnCondition(ctx, t, instance, conditionFunction("preaccesstoken"), []string{targetResp.GetId()}) return userResp.GetUserId(), closeF } @@ -1243,7 +1244,7 @@ func expectPreSAMLResponseExecution(ctx context.Context, t *testing.T, instance targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response) - targetResp := waitForTarget(ctx, t, instance, targetURL, domain.TargetTypeCall, true) + targetResp := waitForTarget(ctx, t, instance, targetURL, target_domain.TargetTypeCall, true) waitForExecutionOnCondition(ctx, t, instance, conditionFunction("presamlresponse"), []string{targetResp.GetId()}) return userResp.GetUserId(), closeF diff --git a/internal/api/grpc/action/v2/integration_test/execution_test.go b/internal/api/grpc/action/v2/integration_test/execution_test.go index da31fe5a0bd..1495a7d0741 100644 --- a/internal/api/grpc/action/v2/integration_test/execution_test.go +++ b/internal/api/grpc/action/v2/integration_test/execution_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/integration" "github.com/zitadel/zitadel/pkg/grpc/action/v2" ) @@ -18,7 +18,7 @@ import ( func TestServer_SetExecution_Request(t *testing.T) { instance := integration.NewInstance(CTX) isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) - targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) + targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", target_domain.TargetTypeWebhook, false) tests := []struct { name string @@ -175,7 +175,7 @@ func assertSetExecutionResponse(t *testing.T, creationDate, setDate time.Time, e func TestServer_SetExecution_Response(t *testing.T) { instance := integration.NewInstance(CTX) isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) - targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) + targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", target_domain.TargetTypeWebhook, false) tests := []struct { name string @@ -319,7 +319,7 @@ func TestServer_SetExecution_Response(t *testing.T) { func TestServer_SetExecution_Event(t *testing.T) { instance := integration.NewInstance(CTX) isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) - targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) + targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", target_domain.TargetTypeWebhook, false) tests := []struct { name string @@ -482,7 +482,7 @@ func TestServer_SetExecution_Event(t *testing.T) { func TestServer_SetExecution_Function(t *testing.T) { instance := integration.NewInstance(CTX) isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) - targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) + targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", target_domain.TargetTypeWebhook, false) tests := []struct { name string diff --git a/internal/api/grpc/action/v2/integration_test/query_test.go b/internal/api/grpc/action/v2/integration_test/query_test.go index be241092c29..02c71a42032 100644 --- a/internal/api/grpc/action/v2/integration_test/query_test.go +++ b/internal/api/grpc/action/v2/integration_test/query_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/durationpb" - "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/integration" "github.com/zitadel/zitadel/pkg/grpc/action/v2" "github.com/zitadel/zitadel/pkg/grpc/filter/v2" @@ -54,7 +54,7 @@ func TestServer_GetTarget(t *testing.T) { ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { name := integration.TargetName() - resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false) + resp := instance.CreateTarget(ctx, t, name, "https://example.com", target_domain.TargetTypeWebhook, false) request.Id = resp.GetId() response.Target.Id = resp.GetId() response.Target.Name = name @@ -81,7 +81,7 @@ func TestServer_GetTarget(t *testing.T) { ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { name := integration.TargetName() - resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeAsync, false) + resp := instance.CreateTarget(ctx, t, name, "https://example.com", target_domain.TargetTypeAsync, false) request.Id = resp.GetId() response.Target.Id = resp.GetId() response.Target.Name = name @@ -108,7 +108,7 @@ func TestServer_GetTarget(t *testing.T) { ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { name := integration.TargetName() - resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, true) + resp := instance.CreateTarget(ctx, t, name, "https://example.com", target_domain.TargetTypeWebhook, true) request.Id = resp.GetId() response.Target.Id = resp.GetId() response.Target.Name = name @@ -137,7 +137,7 @@ func TestServer_GetTarget(t *testing.T) { ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { name := integration.TargetName() - resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, false) + resp := instance.CreateTarget(ctx, t, name, "https://example.com", target_domain.TargetTypeCall, false) request.Id = resp.GetId() response.Target.Id = resp.GetId() response.Target.Name = name @@ -166,7 +166,7 @@ func TestServer_GetTarget(t *testing.T) { ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { name := integration.TargetName() - resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, true) + resp := instance.CreateTarget(ctx, t, name, "https://example.com", target_domain.TargetTypeCall, true) request.Id = resp.GetId() response.Target.Id = resp.GetId() response.Target.Name = name @@ -261,7 +261,7 @@ func TestServer_ListTargets(t *testing.T) { ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) { name := integration.TargetName() - resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false) + resp := instance.CreateTarget(ctx, t, name, "https://example.com", target_domain.TargetTypeWebhook, false) request.Filters[0].Filter = &action.TargetSearchFilter_InTargetIdsFilter{ InTargetIdsFilter: &action.InTargetIDsFilter{ TargetIds: []string{resp.GetId()}, @@ -301,7 +301,7 @@ func TestServer_ListTargets(t *testing.T) { ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) { name := integration.TargetName() - resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false) + resp := instance.CreateTarget(ctx, t, name, "https://example.com", target_domain.TargetTypeWebhook, false) request.Filters[0].Filter = &action.TargetSearchFilter_TargetNameFilter{ TargetNameFilter: &action.TargetNameFilter{ TargetName: name, @@ -344,9 +344,9 @@ func TestServer_ListTargets(t *testing.T) { name1 := integration.TargetName() name2 := integration.TargetName() name3 := integration.TargetName() - resp1 := instance.CreateTarget(ctx, t, name1, "https://example.com", domain.TargetTypeWebhook, false) - resp2 := instance.CreateTarget(ctx, t, name2, "https://example.com", domain.TargetTypeCall, true) - resp3 := instance.CreateTarget(ctx, t, name3, "https://example.com", domain.TargetTypeAsync, false) + resp1 := instance.CreateTarget(ctx, t, name1, "https://example.com", target_domain.TargetTypeWebhook, false) + resp2 := instance.CreateTarget(ctx, t, name2, "https://example.com", target_domain.TargetTypeCall, true) + resp3 := instance.CreateTarget(ctx, t, name3, "https://example.com", target_domain.TargetTypeAsync, false) request.Filters[0].Filter = &action.TargetSearchFilter_InTargetIdsFilter{ InTargetIdsFilter: &action.InTargetIDsFilter{ TargetIds: []string{resp1.GetId(), resp2.GetId(), resp3.GetId()}, @@ -445,7 +445,7 @@ func assertPaginationResponse(t *assert.CollectT, expected *filter.PaginationRes func TestServer_ListExecutions(t *testing.T) { instance := integration.NewInstance(CTX) isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) - targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false) + targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false) type args struct { ctx context.Context @@ -523,7 +523,7 @@ func TestServer_ListExecutions(t *testing.T) { args: args{ ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) { - target := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false) + target := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false) // add target as Filter to the request request.Filters[0] = &action.ExecutionSearchFilter{ Filter: &action.ExecutionSearchFilter_TargetFilter{ diff --git a/internal/api/grpc/action/v2/integration_test/target_test.go b/internal/api/grpc/action/v2/integration_test/target_test.go index 69447be5198..6aa7f39fae0 100644 --- a/internal/api/grpc/action/v2/integration_test/target_test.go +++ b/internal/api/grpc/action/v2/integration_test/target_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" "google.golang.org/protobuf/types/known/durationpb" - "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/integration" "github.com/zitadel/zitadel/pkg/grpc/action/v2" ) @@ -26,7 +26,7 @@ func TestServer_CreateTarget(t *testing.T) { signingKey bool } alreadyExistingTargetName := integration.TargetName() - instance.CreateTarget(isolatedIAMOwnerCTX, t, alreadyExistingTargetName, "https://example.com", domain.TargetTypeAsync, false) + instance.CreateTarget(isolatedIAMOwnerCTX, t, alreadyExistingTargetName, "https://example.com", target_domain.TargetTypeAsync, false) tests := []struct { name string ctx context.Context @@ -263,7 +263,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "missing permission", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID }, args: args{ @@ -290,7 +290,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "no change, ok", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID }, args: args{ @@ -308,7 +308,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "change name, ok", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID }, args: args{ @@ -326,7 +326,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "regenerate signingkey, ok", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID }, args: args{ @@ -344,7 +344,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "change type, ok", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID }, args: args{ @@ -366,7 +366,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "change url, ok", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID }, args: args{ @@ -384,7 +384,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "change timeout, ok", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID }, args: args{ @@ -402,7 +402,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "change type async, ok", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeAsync, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeAsync, false).GetId() request.Id = targetID }, args: args{ @@ -498,7 +498,7 @@ func TestServer_DeleteTarget(t *testing.T) { ctx: iamOwnerCtx, prepare: func(request *action.DeleteTargetRequest) (time.Time, time.Time) { creationDate := time.Now().UTC() - targetID := instance.CreateTarget(iamOwnerCtx, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(iamOwnerCtx, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID return creationDate, time.Time{} }, @@ -510,7 +510,7 @@ func TestServer_DeleteTarget(t *testing.T) { ctx: iamOwnerCtx, prepare: func(request *action.DeleteTargetRequest) (time.Time, time.Time) { creationDate := time.Now().UTC() - targetID := instance.CreateTarget(iamOwnerCtx, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(iamOwnerCtx, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID instance.DeleteTarget(iamOwnerCtx, t, targetID) return creationDate, time.Now().UTC() diff --git a/internal/api/grpc/action/v2/query.go b/internal/api/grpc/action/v2/query.go index c900d29d75d..1b09984ed9a 100644 --- a/internal/api/grpc/action/v2/query.go +++ b/internal/api/grpc/action/v2/query.go @@ -11,6 +11,7 @@ import ( "github.com/zitadel/zitadel/internal/api/grpc/filter/v2" "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/pkg/grpc/action/v2" @@ -89,11 +90,11 @@ func targetToPb(t *query.Target) *action.Target { SigningKey: t.SigningKey, } switch t.TargetType { - case domain.TargetTypeWebhook: + case target_domain.TargetTypeWebhook: target.TargetType = &action.Target_RestWebhook{RestWebhook: &action.RESTWebhook{InterruptOnError: t.InterruptOnError}} - case domain.TargetTypeCall: + case target_domain.TargetTypeCall: target.TargetType = &action.Target_RestCall{RestCall: &action.RESTCall{InterruptOnError: t.InterruptOnError}} - case domain.TargetTypeAsync: + case target_domain.TargetTypeAsync: target.TargetType = &action.Target_RestAsync{RestAsync: &action.RESTAsync{}} default: target.TargetType = nil diff --git a/internal/api/grpc/action/v2/target.go b/internal/api/grpc/action/v2/target.go index 971a6b871e5..28cb59b12d8 100644 --- a/internal/api/grpc/action/v2/target.go +++ b/internal/api/grpc/action/v2/target.go @@ -9,8 +9,8 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/command" - "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore/v1/models" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/pkg/grpc/action/v2" ) @@ -66,18 +66,18 @@ func (s *Server) DeleteTarget(ctx context.Context, req *connect.Request[action.D func createTargetToCommand(req *action.CreateTargetRequest) *command.AddTarget { var ( - targetType domain.TargetType + targetType target_domain.TargetType interruptOnError bool ) switch t := req.GetTargetType().(type) { case *action.CreateTargetRequest_RestWebhook: - targetType = domain.TargetTypeWebhook + targetType = target_domain.TargetTypeWebhook interruptOnError = t.RestWebhook.InterruptOnError case *action.CreateTargetRequest_RestCall: - targetType = domain.TargetTypeCall + targetType = target_domain.TargetTypeCall interruptOnError = t.RestCall.InterruptOnError case *action.CreateTargetRequest_RestAsync: - targetType = domain.TargetTypeAsync + targetType = target_domain.TargetTypeAsync } return &command.AddTarget{ Name: req.GetName(), @@ -106,13 +106,13 @@ func updateTargetToCommand(req *action.UpdateTargetRequest) *command.ChangeTarge if req.TargetType != nil { switch t := req.GetTargetType().(type) { case *action.UpdateTargetRequest_RestWebhook: - target.TargetType = gu.Ptr(domain.TargetTypeWebhook) + target.TargetType = gu.Ptr(target_domain.TargetTypeWebhook) target.InterruptOnError = gu.Ptr(t.RestWebhook.InterruptOnError) case *action.UpdateTargetRequest_RestCall: - target.TargetType = gu.Ptr(domain.TargetTypeCall) + target.TargetType = gu.Ptr(target_domain.TargetTypeCall) target.InterruptOnError = gu.Ptr(t.RestCall.InterruptOnError) case *action.UpdateTargetRequest_RestAsync: - target.TargetType = gu.Ptr(domain.TargetTypeAsync) + target.TargetType = gu.Ptr(target_domain.TargetTypeAsync) target.InterruptOnError = gu.Ptr(false) } } diff --git a/internal/api/grpc/action/v2/target_test.go b/internal/api/grpc/action/v2/target_test.go index f41932933a4..6a7c04cb4c6 100644 --- a/internal/api/grpc/action/v2/target_test.go +++ b/internal/api/grpc/action/v2/target_test.go @@ -9,7 +9,7 @@ import ( "google.golang.org/protobuf/types/known/durationpb" "github.com/zitadel/zitadel/internal/command" - "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/pkg/grpc/action/v2" ) @@ -44,7 +44,7 @@ func Test_createTargetToCommand(t *testing.T) { }}, want: &command.AddTarget{ Name: "target 1", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Endpoint: "https://example.com/hooks/1", Timeout: 10 * time.Second, InterruptOnError: false, @@ -62,7 +62,7 @@ func Test_createTargetToCommand(t *testing.T) { }}, want: &command.AddTarget{ Name: "target 1", - TargetType: domain.TargetTypeAsync, + TargetType: target_domain.TargetTypeAsync, Endpoint: "https://example.com/hooks/1", Timeout: 10 * time.Second, InterruptOnError: false, @@ -82,7 +82,7 @@ func Test_createTargetToCommand(t *testing.T) { }}, want: &command.AddTarget{ Name: "target 1", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Endpoint: "https://example.com/hooks/1", Timeout: 10 * time.Second, InterruptOnError: true, @@ -155,7 +155,7 @@ func Test_updateTargetToCommand(t *testing.T) { }}, want: &command.ChangeTarget{ Name: gu.Ptr("target 1"), - TargetType: gu.Ptr(domain.TargetTypeWebhook), + TargetType: gu.Ptr(target_domain.TargetTypeWebhook), Endpoint: gu.Ptr("https://example.com/hooks/1"), Timeout: gu.Ptr(10 * time.Second), InterruptOnError: gu.Ptr(false), @@ -175,7 +175,7 @@ func Test_updateTargetToCommand(t *testing.T) { }}, want: &command.ChangeTarget{ Name: gu.Ptr("target 1"), - TargetType: gu.Ptr(domain.TargetTypeWebhook), + TargetType: gu.Ptr(target_domain.TargetTypeWebhook), Endpoint: gu.Ptr("https://example.com/hooks/1"), Timeout: gu.Ptr(10 * time.Second), InterruptOnError: gu.Ptr(true), @@ -193,7 +193,7 @@ func Test_updateTargetToCommand(t *testing.T) { }}, want: &command.ChangeTarget{ Name: gu.Ptr("target 1"), - TargetType: gu.Ptr(domain.TargetTypeAsync), + TargetType: gu.Ptr(target_domain.TargetTypeAsync), Endpoint: gu.Ptr("https://example.com/hooks/1"), Timeout: gu.Ptr(10 * time.Second), InterruptOnError: gu.Ptr(false), @@ -213,7 +213,7 @@ func Test_updateTargetToCommand(t *testing.T) { }}, want: &command.ChangeTarget{ Name: gu.Ptr("target 1"), - TargetType: gu.Ptr(domain.TargetTypeCall), + TargetType: gu.Ptr(target_domain.TargetTypeCall), Endpoint: gu.Ptr("https://example.com/hooks/1"), Timeout: gu.Ptr(10 * time.Second), InterruptOnError: gu.Ptr(true), diff --git a/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go b/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go index 4db254fe306..a749972c005 100644 --- a/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go +++ b/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go @@ -26,6 +26,7 @@ import ( oidc_api "github.com/zitadel/zitadel/internal/api/oidc" saml_api "github.com/zitadel/zitadel/internal/api/saml" "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/integration" "github.com/zitadel/zitadel/internal/query" action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta" @@ -73,7 +74,7 @@ func TestServer_ExecutionTarget(t *testing.T) { targetCreatedName := gofakeit.Name() targetCreatedURL := "https://nonexistent" - targetCreated := instance.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false) + targetCreated := instance.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, target_domain.TargetTypeCall, false) // request received by target wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instance.ID(), OrgID: orgID, ProjectID: projectID, UserID: userID, Request: middleware.Message{Message: request}} @@ -81,7 +82,7 @@ func TestServer_ExecutionTarget(t *testing.T) { // replace original request with different targetID urlRequest, closeRequest, calledRequest, _ := integration.TestServerCallProto(wantRequest, 0, http.StatusOK, changedRequest) - targetRequest := waitForTarget(ctx, t, instance, urlRequest, domain.TargetTypeCall, false) + targetRequest := waitForTarget(ctx, t, instance, urlRequest, target_domain.TargetTypeCall, false) waitForExecutionOnCondition(ctx, t, instance, conditionRequestFullMethod(fullMethod), []string{targetRequest.GetId()}) @@ -148,7 +149,7 @@ func TestServer_ExecutionTarget(t *testing.T) { // after request with different targetID, return changed response targetResponseURL, closeResponse, calledResponse, _ := integration.TestServerCallProto(wantResponse, 0, http.StatusOK, changedResponse) - targetResponse := waitForTarget(ctx, t, instance, targetResponseURL, domain.TargetTypeCall, false) + targetResponse := waitForTarget(ctx, t, instance, targetResponseURL, target_domain.TargetTypeCall, false) waitForExecutionOnCondition(ctx, t, instance, conditionResponseFullMethod(fullMethod), []string{targetResponse.GetId()}) return func() { closeRequest() @@ -186,7 +187,7 @@ func TestServer_ExecutionTarget(t *testing.T) { wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instance.ID(), OrgID: orgID, ProjectID: projectID, UserID: userID, Request: middleware.Message{Message: request}} urlRequest, closeRequest, calledRequest, _ := integration.TestServerCallProto(wantRequest, 0, http.StatusInternalServerError, nil) - targetRequest := waitForTarget(ctx, t, instance, urlRequest, domain.TargetTypeCall, true) + targetRequest := waitForTarget(ctx, t, instance, urlRequest, target_domain.TargetTypeCall, true) waitForExecutionOnCondition(ctx, t, instance, conditionRequestFullMethod(fullMethod), []string{targetRequest.GetId()}) // GetTarget with used target request.Id = targetRequest.GetId() @@ -214,7 +215,7 @@ func TestServer_ExecutionTarget(t *testing.T) { targetCreatedName := gofakeit.Name() targetCreatedURL := "https://nonexistent" - targetCreated := instance.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false) + targetCreated := instance.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, target_domain.TargetTypeCall, false) // GetTarget with used target request.Id = targetCreated.GetId() @@ -250,7 +251,7 @@ func TestServer_ExecutionTarget(t *testing.T) { // after request with different targetID, return changed response targetResponseURL, closeResponse, calledResponse, _ := integration.TestServerCallProto(wantResponse, 0, http.StatusInternalServerError, nil) - targetResponse := waitForTarget(ctx, t, instance, targetResponseURL, domain.TargetTypeCall, true) + targetResponse := waitForTarget(ctx, t, instance, targetResponseURL, target_domain.TargetTypeCall, true) waitForExecutionOnCondition(ctx, t, instance, conditionResponseFullMethod(fullMethod), []string{targetResponse.GetId()}) return func() { closeResponse() @@ -305,7 +306,7 @@ func TestServer_ExecutionTarget_Event(t *testing.T) { urlRequest, closeF, calledF, resetF := integration.TestServerCall(nil, 0, http.StatusOK, nil) defer closeF() - targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, domain.TargetTypeWebhook, true) + targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, target_domain.TargetTypeWebhook, true) waitForExecutionOnCondition(isolatedIAMOwnerCTX, t, instance, conditionEvent(event), []string{targetResponse.GetId()}) tests := []struct { @@ -363,7 +364,7 @@ func TestServer_ExecutionTarget_Event_LongerThanTargetTimeout(t *testing.T) { urlRequest, closeF, calledF, resetF := integration.TestServerCall(nil, 5*time.Second, http.StatusOK, nil) defer closeF() - targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, domain.TargetTypeWebhook, true) + targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, target_domain.TargetTypeWebhook, true) waitForExecutionOnCondition(isolatedIAMOwnerCTX, t, instance, conditionEvent(event), []string{targetResponse.GetId()}) tests := []struct { @@ -414,7 +415,7 @@ func TestServer_ExecutionTarget_Event_LongerThanTransactionTimeout(t *testing.T) urlRequest, closeF, calledF, resetF := integration.TestServerCall(nil, 1*time.Second, http.StatusOK, nil) defer closeF() - targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, domain.TargetTypeWebhook, true) + targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, target_domain.TargetTypeWebhook, true) waitForExecutionOnCondition(isolatedIAMOwnerCTX, t, instance, conditionEvent(event), []string{targetResponse.GetId()}) tests := []struct { @@ -506,7 +507,7 @@ func setExecution(ctx context.Context, t *testing.T, instance *integration.Insta return target } -func waitForTarget(ctx context.Context, t *testing.T, instance *integration.Instance, endpoint string, ty domain.TargetType, interrupt bool) *action.CreateTargetResponse { +func waitForTarget(ctx context.Context, t *testing.T, instance *integration.Instance, endpoint string, ty target_domain.TargetType, interrupt bool) *action.CreateTargetResponse { resp := createTarget(ctx, t, instance, "", endpoint, ty, interrupt) retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute) @@ -527,14 +528,14 @@ func waitForTarget(ctx context.Context, t *testing.T, instance *integration.Inst config := got.GetTargets()[0] assert.Equal(ttt, config.GetEndpoint(), endpoint) switch ty { - case domain.TargetTypeWebhook: + case target_domain.TargetTypeWebhook: if !assert.NotNil(ttt, config.GetRestWebhook()) { return } assert.Equal(ttt, interrupt, config.GetRestWebhook().GetInterruptOnError()) - case domain.TargetTypeAsync: + case target_domain.TargetTypeAsync: assert.NotNil(ttt, config.GetRestAsync()) - case domain.TargetTypeCall: + case target_domain.TargetTypeCall: if !assert.NotNil(ttt, config.GetRestCall()) { return } @@ -544,7 +545,7 @@ func waitForTarget(ctx context.Context, t *testing.T, instance *integration.Inst return resp } -func createTarget(ctx context.Context, t *testing.T, instance *integration.Instance, name, endpoint string, ty domain.TargetType, interrupt bool) *action.CreateTargetResponse { +func createTarget(ctx context.Context, t *testing.T, instance *integration.Instance, name, endpoint string, ty target_domain.TargetType, interrupt bool) *action.CreateTargetResponse { if name == "" { name = gofakeit.Name() } @@ -554,19 +555,19 @@ func createTarget(ctx context.Context, t *testing.T, instance *integration.Insta Timeout: durationpb.New(5 * time.Second), } switch ty { - case domain.TargetTypeWebhook: + case target_domain.TargetTypeWebhook: req.TargetType = &action.CreateTargetRequest_RestWebhook{ RestWebhook: &action.RESTWebhook{ InterruptOnError: interrupt, }, } - case domain.TargetTypeCall: + case target_domain.TargetTypeCall: req.TargetType = &action.CreateTargetRequest_RestCall{ RestCall: &action.RESTCall{ InterruptOnError: interrupt, }, } - case domain.TargetTypeAsync: + case target_domain.TargetTypeAsync: req.TargetType = &action.CreateTargetRequest_RestAsync{ RestAsync: &action.RESTAsync{}, } @@ -818,7 +819,7 @@ func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *int targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response) - targetResp := waitForTarget(ctx, t, instance, targetURL, domain.TargetTypeCall, true) + targetResp := waitForTarget(ctx, t, instance, targetURL, target_domain.TargetTypeCall, true) waitForExecutionOnCondition(ctx, t, instance, conditionFunction("preuserinfo"), []string{targetResp.GetId()}) return userResp.GetUserId(), closeF } @@ -1126,7 +1127,7 @@ func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance * targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response) - targetResp := waitForTarget(ctx, t, instance, targetURL, domain.TargetTypeCall, true) + targetResp := waitForTarget(ctx, t, instance, targetURL, target_domain.TargetTypeCall, true) waitForExecutionOnCondition(ctx, t, instance, conditionFunction("preaccesstoken"), []string{targetResp.GetId()}) return userResp.GetUserId(), closeF } @@ -1291,7 +1292,7 @@ func expectPreSAMLResponseExecution(ctx context.Context, t *testing.T, instance targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response) - targetResp := waitForTarget(ctx, t, instance, targetURL, domain.TargetTypeCall, true) + targetResp := waitForTarget(ctx, t, instance, targetURL, target_domain.TargetTypeCall, true) waitForExecutionOnCondition(ctx, t, instance, conditionFunction("presamlresponse"), []string{targetResp.GetId()}) return userResp.GetUserId(), closeF diff --git a/internal/api/grpc/action/v2beta/integration_test/execution_test.go b/internal/api/grpc/action/v2beta/integration_test/execution_test.go index dee736991b3..78f4f9006b7 100644 --- a/internal/api/grpc/action/v2beta/integration_test/execution_test.go +++ b/internal/api/grpc/action/v2beta/integration_test/execution_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/integration" action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta" ) @@ -18,7 +18,7 @@ import ( func TestServer_SetExecution_Request(t *testing.T) { instance := integration.NewInstance(CTX) isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) - targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) + targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", target_domain.TargetTypeWebhook, false) tests := []struct { name string @@ -175,7 +175,7 @@ func assertSetExecutionResponse(t *testing.T, creationDate, setDate time.Time, e func TestServer_SetExecution_Response(t *testing.T) { instance := integration.NewInstance(CTX) isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) - targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) + targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", target_domain.TargetTypeWebhook, false) tests := []struct { name string @@ -319,7 +319,7 @@ func TestServer_SetExecution_Response(t *testing.T) { func TestServer_SetExecution_Event(t *testing.T) { instance := integration.NewInstance(CTX) isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) - targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) + targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", target_domain.TargetTypeWebhook, false) tests := []struct { name string @@ -482,7 +482,7 @@ func TestServer_SetExecution_Event(t *testing.T) { func TestServer_SetExecution_Function(t *testing.T) { instance := integration.NewInstance(CTX) isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) - targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false) + targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", target_domain.TargetTypeWebhook, false) tests := []struct { name string diff --git a/internal/api/grpc/action/v2beta/integration_test/query_test.go b/internal/api/grpc/action/v2beta/integration_test/query_test.go index 1118311bd2b..913217bd8e5 100644 --- a/internal/api/grpc/action/v2beta/integration_test/query_test.go +++ b/internal/api/grpc/action/v2beta/integration_test/query_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/durationpb" - "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/integration" action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta" filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta" @@ -55,7 +55,7 @@ func TestServer_GetTarget(t *testing.T) { ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { name := gofakeit.Name() - resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false) + resp := instance.CreateTarget(ctx, t, name, "https://example.com", target_domain.TargetTypeWebhook, false) request.Id = resp.GetId() response.Target.Id = resp.GetId() response.Target.Name = name @@ -82,7 +82,7 @@ func TestServer_GetTarget(t *testing.T) { ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { name := gofakeit.Name() - resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeAsync, false) + resp := instance.CreateTarget(ctx, t, name, "https://example.com", target_domain.TargetTypeAsync, false) request.Id = resp.GetId() response.Target.Id = resp.GetId() response.Target.Name = name @@ -109,7 +109,7 @@ func TestServer_GetTarget(t *testing.T) { ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { name := gofakeit.Name() - resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, true) + resp := instance.CreateTarget(ctx, t, name, "https://example.com", target_domain.TargetTypeWebhook, true) request.Id = resp.GetId() response.Target.Id = resp.GetId() response.Target.Name = name @@ -138,7 +138,7 @@ func TestServer_GetTarget(t *testing.T) { ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { name := gofakeit.Name() - resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, false) + resp := instance.CreateTarget(ctx, t, name, "https://example.com", target_domain.TargetTypeCall, false) request.Id = resp.GetId() response.Target.Id = resp.GetId() response.Target.Name = name @@ -167,7 +167,7 @@ func TestServer_GetTarget(t *testing.T) { ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) error { name := gofakeit.Name() - resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeCall, true) + resp := instance.CreateTarget(ctx, t, name, "https://example.com", target_domain.TargetTypeCall, true) request.Id = resp.GetId() response.Target.Id = resp.GetId() response.Target.Name = name @@ -262,7 +262,7 @@ func TestServer_ListTargets(t *testing.T) { ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) { name := gofakeit.Name() - resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false) + resp := instance.CreateTarget(ctx, t, name, "https://example.com", target_domain.TargetTypeWebhook, false) request.Filters[0].Filter = &action.TargetSearchFilter_InTargetIdsFilter{ InTargetIdsFilter: &action.InTargetIDsFilter{ TargetIds: []string{resp.GetId()}, @@ -302,7 +302,7 @@ func TestServer_ListTargets(t *testing.T) { ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.ListTargetsRequest, response *action.ListTargetsResponse) { name := gofakeit.Name() - resp := instance.CreateTarget(ctx, t, name, "https://example.com", domain.TargetTypeWebhook, false) + resp := instance.CreateTarget(ctx, t, name, "https://example.com", target_domain.TargetTypeWebhook, false) request.Filters[0].Filter = &action.TargetSearchFilter_TargetNameFilter{ TargetNameFilter: &action.TargetNameFilter{ TargetName: name, @@ -345,9 +345,9 @@ func TestServer_ListTargets(t *testing.T) { name1 := gofakeit.Name() name2 := gofakeit.Name() name3 := gofakeit.Name() - resp1 := instance.CreateTarget(ctx, t, name1, "https://example.com", domain.TargetTypeWebhook, false) - resp2 := instance.CreateTarget(ctx, t, name2, "https://example.com", domain.TargetTypeCall, true) - resp3 := instance.CreateTarget(ctx, t, name3, "https://example.com", domain.TargetTypeAsync, false) + resp1 := instance.CreateTarget(ctx, t, name1, "https://example.com", target_domain.TargetTypeWebhook, false) + resp2 := instance.CreateTarget(ctx, t, name2, "https://example.com", target_domain.TargetTypeCall, true) + resp3 := instance.CreateTarget(ctx, t, name3, "https://example.com", target_domain.TargetTypeAsync, false) request.Filters[0].Filter = &action.TargetSearchFilter_InTargetIdsFilter{ InTargetIdsFilter: &action.InTargetIDsFilter{ TargetIds: []string{resp1.GetId(), resp2.GetId(), resp3.GetId()}, @@ -446,7 +446,7 @@ func assertPaginationResponse(t *assert.CollectT, expected *filter.PaginationRes func TestServer_ListExecutions(t *testing.T) { instance := integration.NewInstance(CTX) isolatedIAMOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner) - targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false) + targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false) type args struct { ctx context.Context @@ -524,7 +524,7 @@ func TestServer_ListExecutions(t *testing.T) { args: args{ ctx: isolatedIAMOwnerCTX, dep: func(ctx context.Context, request *action.ListExecutionsRequest, response *action.ListExecutionsResponse) { - target := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false) + target := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false) // add target as Filter to the request request.Filters[0] = &action.ExecutionSearchFilter{ Filter: &action.ExecutionSearchFilter_TargetFilter{ diff --git a/internal/api/grpc/action/v2beta/integration_test/target_test.go b/internal/api/grpc/action/v2beta/integration_test/target_test.go index 25a4e5f5506..7354598307a 100644 --- a/internal/api/grpc/action/v2beta/integration_test/target_test.go +++ b/internal/api/grpc/action/v2beta/integration_test/target_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" "google.golang.org/protobuf/types/known/durationpb" - "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/integration" action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta" ) @@ -26,7 +26,7 @@ func TestServer_CreateTarget(t *testing.T) { signingKey bool } alreadyExistingTargetName := gofakeit.AppName() - instance.CreateTarget(isolatedIAMOwnerCTX, t, alreadyExistingTargetName, "https://example.com", domain.TargetTypeAsync, false) + instance.CreateTarget(isolatedIAMOwnerCTX, t, alreadyExistingTargetName, "https://example.com", target_domain.TargetTypeAsync, false) tests := []struct { name string ctx context.Context @@ -263,7 +263,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "missing permission", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID }, args: args{ @@ -290,7 +290,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "no change, ok", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID }, args: args{ @@ -308,7 +308,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "change name, ok", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID }, args: args{ @@ -326,7 +326,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "regenerate signingkey, ok", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID }, args: args{ @@ -344,7 +344,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "change type, ok", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID }, args: args{ @@ -366,7 +366,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "change url, ok", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID }, args: args{ @@ -384,7 +384,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "change timeout, ok", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID }, args: args{ @@ -402,7 +402,7 @@ func TestServer_UpdateTarget(t *testing.T) { { name: "change type async, ok", prepare: func(request *action.UpdateTargetRequest) { - targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeAsync, false).GetId() + targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", target_domain.TargetTypeAsync, false).GetId() request.Id = targetID }, args: args{ @@ -498,7 +498,7 @@ func TestServer_DeleteTarget(t *testing.T) { ctx: iamOwnerCtx, prepare: func(request *action.DeleteTargetRequest) (time.Time, time.Time) { creationDate := time.Now().UTC() - targetID := instance.CreateTarget(iamOwnerCtx, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(iamOwnerCtx, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID return creationDate, time.Time{} }, @@ -510,7 +510,7 @@ func TestServer_DeleteTarget(t *testing.T) { ctx: iamOwnerCtx, prepare: func(request *action.DeleteTargetRequest) (time.Time, time.Time) { creationDate := time.Now().UTC() - targetID := instance.CreateTarget(iamOwnerCtx, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId() + targetID := instance.CreateTarget(iamOwnerCtx, t, "", "https://example.com", target_domain.TargetTypeWebhook, false).GetId() request.Id = targetID instance.DeleteTarget(iamOwnerCtx, t, targetID) return creationDate, time.Now().UTC() diff --git a/internal/api/grpc/action/v2beta/query.go b/internal/api/grpc/action/v2beta/query.go index 164283b8904..8615cb1ecfd 100644 --- a/internal/api/grpc/action/v2beta/query.go +++ b/internal/api/grpc/action/v2beta/query.go @@ -11,6 +11,7 @@ import ( filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2beta" "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/zerrors" action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta" @@ -89,11 +90,11 @@ func targetToPb(t *query.Target) *action.Target { SigningKey: t.SigningKey, } switch t.TargetType { - case domain.TargetTypeWebhook: + case target_domain.TargetTypeWebhook: target.TargetType = &action.Target_RestWebhook{RestWebhook: &action.RESTWebhook{InterruptOnError: t.InterruptOnError}} - case domain.TargetTypeCall: + case target_domain.TargetTypeCall: target.TargetType = &action.Target_RestCall{RestCall: &action.RESTCall{InterruptOnError: t.InterruptOnError}} - case domain.TargetTypeAsync: + case target_domain.TargetTypeAsync: target.TargetType = &action.Target_RestAsync{RestAsync: &action.RESTAsync{}} default: target.TargetType = nil diff --git a/internal/api/grpc/action/v2beta/target.go b/internal/api/grpc/action/v2beta/target.go index b13f3461f0c..fc91d1913a9 100644 --- a/internal/api/grpc/action/v2beta/target.go +++ b/internal/api/grpc/action/v2beta/target.go @@ -9,8 +9,8 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/command" - "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore/v1/models" + target_domain "github.com/zitadel/zitadel/internal/execution/target" action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta" ) @@ -66,18 +66,18 @@ func (s *Server) DeleteTarget(ctx context.Context, req *connect.Request[action.D func createTargetToCommand(req *action.CreateTargetRequest) *command.AddTarget { var ( - targetType domain.TargetType + targetType target_domain.TargetType interruptOnError bool ) switch t := req.GetTargetType().(type) { case *action.CreateTargetRequest_RestWebhook: - targetType = domain.TargetTypeWebhook + targetType = target_domain.TargetTypeWebhook interruptOnError = t.RestWebhook.InterruptOnError case *action.CreateTargetRequest_RestCall: - targetType = domain.TargetTypeCall + targetType = target_domain.TargetTypeCall interruptOnError = t.RestCall.InterruptOnError case *action.CreateTargetRequest_RestAsync: - targetType = domain.TargetTypeAsync + targetType = target_domain.TargetTypeAsync } return &command.AddTarget{ Name: req.GetName(), @@ -109,13 +109,13 @@ func updateTargetToCommand(req *action.UpdateTargetRequest) *command.ChangeTarge if req.TargetType != nil { switch t := req.GetTargetType().(type) { case *action.UpdateTargetRequest_RestWebhook: - target.TargetType = gu.Ptr(domain.TargetTypeWebhook) + target.TargetType = gu.Ptr(target_domain.TargetTypeWebhook) target.InterruptOnError = gu.Ptr(t.RestWebhook.InterruptOnError) case *action.UpdateTargetRequest_RestCall: - target.TargetType = gu.Ptr(domain.TargetTypeCall) + target.TargetType = gu.Ptr(target_domain.TargetTypeCall) target.InterruptOnError = gu.Ptr(t.RestCall.InterruptOnError) case *action.UpdateTargetRequest_RestAsync: - target.TargetType = gu.Ptr(domain.TargetTypeAsync) + target.TargetType = gu.Ptr(target_domain.TargetTypeAsync) target.InterruptOnError = gu.Ptr(false) } } diff --git a/internal/api/grpc/action/v2beta/target_test.go b/internal/api/grpc/action/v2beta/target_test.go index b18ee521608..09d4c4039e0 100644 --- a/internal/api/grpc/action/v2beta/target_test.go +++ b/internal/api/grpc/action/v2beta/target_test.go @@ -9,7 +9,7 @@ import ( "google.golang.org/protobuf/types/known/durationpb" "github.com/zitadel/zitadel/internal/command" - "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta" ) @@ -44,7 +44,7 @@ func Test_createTargetToCommand(t *testing.T) { }}, want: &command.AddTarget{ Name: "target 1", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Endpoint: "https://example.com/hooks/1", Timeout: 10 * time.Second, InterruptOnError: false, @@ -62,7 +62,7 @@ func Test_createTargetToCommand(t *testing.T) { }}, want: &command.AddTarget{ Name: "target 1", - TargetType: domain.TargetTypeAsync, + TargetType: target_domain.TargetTypeAsync, Endpoint: "https://example.com/hooks/1", Timeout: 10 * time.Second, InterruptOnError: false, @@ -82,7 +82,7 @@ func Test_createTargetToCommand(t *testing.T) { }}, want: &command.AddTarget{ Name: "target 1", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Endpoint: "https://example.com/hooks/1", Timeout: 10 * time.Second, InterruptOnError: true, @@ -155,7 +155,7 @@ func Test_updateTargetToCommand(t *testing.T) { }}, want: &command.ChangeTarget{ Name: gu.Ptr("target 1"), - TargetType: gu.Ptr(domain.TargetTypeWebhook), + TargetType: gu.Ptr(target_domain.TargetTypeWebhook), Endpoint: gu.Ptr("https://example.com/hooks/1"), Timeout: gu.Ptr(10 * time.Second), InterruptOnError: gu.Ptr(false), @@ -175,7 +175,7 @@ func Test_updateTargetToCommand(t *testing.T) { }}, want: &command.ChangeTarget{ Name: gu.Ptr("target 1"), - TargetType: gu.Ptr(domain.TargetTypeWebhook), + TargetType: gu.Ptr(target_domain.TargetTypeWebhook), Endpoint: gu.Ptr("https://example.com/hooks/1"), Timeout: gu.Ptr(10 * time.Second), InterruptOnError: gu.Ptr(true), @@ -193,7 +193,7 @@ func Test_updateTargetToCommand(t *testing.T) { }}, want: &command.ChangeTarget{ Name: gu.Ptr("target 1"), - TargetType: gu.Ptr(domain.TargetTypeAsync), + TargetType: gu.Ptr(target_domain.TargetTypeAsync), Endpoint: gu.Ptr("https://example.com/hooks/1"), Timeout: gu.Ptr(10 * time.Second), InterruptOnError: gu.Ptr(false), @@ -213,7 +213,7 @@ func Test_updateTargetToCommand(t *testing.T) { }}, want: &command.ChangeTarget{ Name: gu.Ptr("target 1"), - TargetType: gu.Ptr(domain.TargetTypeCall), + TargetType: gu.Ptr(target_domain.TargetTypeCall), Endpoint: gu.Ptr("https://example.com/hooks/1"), Timeout: gu.Ptr(10 * time.Second), InterruptOnError: gu.Ptr(true), diff --git a/internal/api/grpc/server/connect_middleware/execution_interceptor.go b/internal/api/grpc/server/connect_middleware/execution_interceptor.go index 879496a33f3..6402eabda26 100644 --- a/internal/api/grpc/server/connect_middleware/execution_interceptor.go +++ b/internal/api/grpc/server/connect_middleware/execution_interceptor.go @@ -9,18 +9,18 @@ import ( "google.golang.org/protobuf/proto" "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/execution" - "github.com/zitadel/zitadel/internal/query" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/telemetry/tracing" ) -func ExecutionHandler(queries *query.Queries) connect.UnaryInterceptorFunc { +func ExecutionHandler(alg crypto.EncryptionAlgorithm) connect.UnaryInterceptorFunc { return func(handler connect.UnaryFunc) connect.UnaryFunc { return func(ctx context.Context, req connect.AnyRequest) (_ connect.AnyResponse, err error) { - requestTargets, responseTargets := execution.QueryExecutionTargetsForRequestAndResponse(ctx, queries, req.Spec().Procedure) - // call targets otherwise return req - handledReq, err := executeTargetsForRequest(ctx, requestTargets, req.Spec().Procedure, req) + requestTargets := execution.QueryExecutionTargetsForRequest(ctx, req.Spec().Procedure) + handledReq, err := executeTargetsForRequest(ctx, requestTargets, req.Spec().Procedure, req, alg) if err != nil { return nil, err } @@ -30,12 +30,13 @@ func ExecutionHandler(queries *query.Queries) connect.UnaryInterceptorFunc { return nil, err } - return executeTargetsForResponse(ctx, responseTargets, req.Spec().Procedure, handledReq, response) + responseTargets := execution.QueryExecutionTargetsForResponse(ctx, req.Spec().Procedure) + return executeTargetsForResponse(ctx, responseTargets, req.Spec().Procedure, handledReq, response, alg) } } } -func executeTargetsForRequest(ctx context.Context, targets []execution.Target, fullMethod string, req connect.AnyRequest) (_ connect.AnyRequest, err error) { +func executeTargetsForRequest(ctx context.Context, targets []target_domain.Target, fullMethod string, req connect.AnyRequest, alg crypto.EncryptionAlgorithm) (_ connect.AnyRequest, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -54,14 +55,14 @@ func executeTargetsForRequest(ctx context.Context, targets []execution.Target, f Request: Message{req.Any().(proto.Message)}, } - _, err = execution.CallTargets(ctx, targets, info) + _, err = execution.CallTargets(ctx, targets, info, alg) if err != nil { return nil, err } return req, nil } -func executeTargetsForResponse(ctx context.Context, targets []execution.Target, fullMethod string, req connect.AnyRequest, resp connect.AnyResponse) (_ connect.AnyResponse, err error) { +func executeTargetsForResponse(ctx context.Context, targets []target_domain.Target, fullMethod string, req connect.AnyRequest, resp connect.AnyResponse, alg crypto.EncryptionAlgorithm) (_ connect.AnyResponse, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -81,7 +82,7 @@ func executeTargetsForResponse(ctx context.Context, targets []execution.Target, Response: Message{resp.Any().(proto.Message)}, } - _, err = execution.CallTargets(ctx, targets, info) + _, err = execution.CallTargets(ctx, targets, info, alg) if err != nil { return nil, err } diff --git a/internal/api/grpc/server/connect_middleware/execution_interceptor_test.go b/internal/api/grpc/server/connect_middleware/execution_interceptor_test.go index d910824f21f..d6a765afb1e 100644 --- a/internal/api/grpc/server/connect_middleware/execution_interceptor_test.go +++ b/internal/api/grpc/server/connect_middleware/execution_interceptor_test.go @@ -16,48 +16,10 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" - "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/execution" + target_domain "github.com/zitadel/zitadel/internal/execution/target" ) -var _ execution.Target = &mockExecutionTarget{} - -type mockExecutionTarget struct { - InstanceID string - ExecutionID string - TargetID string - TargetType domain.TargetType - Endpoint string - Timeout time.Duration - InterruptOnError bool - SigningKey string -} - -func (e *mockExecutionTarget) SetEndpoint(endpoint string) { - e.Endpoint = endpoint -} -func (e *mockExecutionTarget) IsInterruptOnError() bool { - return e.InterruptOnError -} -func (e *mockExecutionTarget) GetEndpoint() string { - return e.Endpoint -} -func (e *mockExecutionTarget) GetTargetType() domain.TargetType { - return e.TargetType -} -func (e *mockExecutionTarget) GetTimeout() time.Duration { - return e.Timeout -} -func (e *mockExecutionTarget) GetTargetID() string { - return e.TargetID -} -func (e *mockExecutionTarget) GetExecutionID() string { - return e.ExecutionID -} -func (e *mockExecutionTarget) GetSigningKey() string { - return e.SigningKey -} - func newMockContentRequest(content string) *connect.Request[structpb.Struct] { return connect.NewRequest(&structpb.Struct{ Fields: map[string]*structpb.Value{ @@ -103,7 +65,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { type args struct { ctx context.Context - executionTargets []execution.Target + executionTargets []target_domain.Target targets []target fullMethod string req connect.AnyRequest @@ -134,7 +96,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{}, + executionTargets: []target_domain.Target{}, req: newMockContentRequest("request"), }, res{ @@ -146,12 +108,11 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, }, @@ -168,14 +129,12 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, - SigningKey: "signingkey", }, }, targets: []target{ @@ -197,15 +156,13 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, }, @@ -228,15 +185,13 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Second, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -258,15 +213,13 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Second, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -283,15 +236,13 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -313,14 +264,12 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeAsync, + TargetType: target_domain.TargetTypeAsync, Timeout: time.Second, - SigningKey: "signingkey", }, }, targets: []target{ @@ -342,14 +291,12 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeAsync, + TargetType: target_domain.TargetTypeAsync, Timeout: time.Minute, - SigningKey: "signingkey", }, }, targets: []target{ @@ -371,15 +318,13 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -400,15 +345,13 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Timeout: time.Second, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -430,15 +373,13 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -460,33 +401,27 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target1", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, - &mockExecutionTarget{ - InstanceID: "instance", + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target2", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, - &mockExecutionTarget{ - InstanceID: "instance", + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target3", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, }, @@ -521,33 +456,27 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target1", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, - &mockExecutionTarget{ - InstanceID: "instance", + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target2", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Second, InterruptOnError: true, - SigningKey: "signingkey", }, - &mockExecutionTarget{ - InstanceID: "instance", + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target3", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Second, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -588,8 +517,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { target.respBody, ) - et := tt.args.executionTargets[i].(*mockExecutionTarget) - et.SetEndpoint(url) + tt.args.executionTargets[i].Endpoint = url closeFuncs[i] = closeF } @@ -598,6 +526,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { tt.args.executionTargets, tt.args.fullMethod, tt.args.req, + nil, ) if tt.res.wantErr { @@ -672,7 +601,7 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { type args struct { ctx context.Context - executionTargets []execution.Target + executionTargets []target_domain.Target targets []target fullMethod string req connect.AnyRequest @@ -705,7 +634,7 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{}, + executionTargets: []target_domain.Target{}, req: newMockContentRequest("request"), resp: newMockContentResponse("response"), }, @@ -718,15 +647,13 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -749,15 +676,13 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "response./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -787,8 +712,7 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { target.respBody, ) - et := tt.args.executionTargets[i].(*mockExecutionTarget) - et.SetEndpoint(url) + tt.args.executionTargets[i].Endpoint = url closeFuncs[i] = closeF } @@ -798,6 +722,7 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { tt.args.fullMethod, tt.args.req, tt.args.resp, + nil, ) if tt.res.wantErr { diff --git a/internal/api/grpc/server/middleware/execution_interceptor.go b/internal/api/grpc/server/middleware/execution_interceptor.go index 4aeea6c4dae..c55f868d6a1 100644 --- a/internal/api/grpc/server/middleware/execution_interceptor.go +++ b/internal/api/grpc/server/middleware/execution_interceptor.go @@ -9,17 +9,17 @@ import ( "google.golang.org/protobuf/proto" "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/execution" - "github.com/zitadel/zitadel/internal/query" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/telemetry/tracing" ) -func ExecutionHandler(queries *query.Queries) grpc.UnaryServerInterceptor { +func ExecutionHandler(alg crypto.EncryptionAlgorithm) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - requestTargets, responseTargets := execution.QueryExecutionTargetsForRequestAndResponse(ctx, queries, info.FullMethod) - + requestTargets := execution.QueryExecutionTargetsForRequest(ctx, info.FullMethod) // call targets otherwise return req - handledReq, err := executeTargetsForRequest(ctx, requestTargets, info.FullMethod, req) + handledReq, err := executeTargetsForRequest(ctx, requestTargets, info.FullMethod, req, alg) if err != nil { return nil, err } @@ -29,11 +29,12 @@ func ExecutionHandler(queries *query.Queries) grpc.UnaryServerInterceptor { return nil, err } - return executeTargetsForResponse(ctx, responseTargets, info.FullMethod, handledReq, response) + responseTargets := execution.QueryExecutionTargetsForResponse(ctx, info.FullMethod) + return executeTargetsForResponse(ctx, responseTargets, info.FullMethod, handledReq, response, alg) } } -func executeTargetsForRequest(ctx context.Context, targets []execution.Target, fullMethod string, req interface{}) (_ interface{}, err error) { +func executeTargetsForRequest(ctx context.Context, targets []target_domain.Target, fullMethod string, req interface{}, alg crypto.EncryptionAlgorithm) (_ interface{}, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -52,10 +53,10 @@ func executeTargetsForRequest(ctx context.Context, targets []execution.Target, f Request: Message{req.(proto.Message)}, } - return execution.CallTargets(ctx, targets, info) + return execution.CallTargets(ctx, targets, info, alg) } -func executeTargetsForResponse(ctx context.Context, targets []execution.Target, fullMethod string, req, resp interface{}) (_ interface{}, err error) { +func executeTargetsForResponse(ctx context.Context, targets []target_domain.Target, fullMethod string, req, resp interface{}, alg crypto.EncryptionAlgorithm) (_ interface{}, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -75,7 +76,7 @@ func executeTargetsForResponse(ctx context.Context, targets []execution.Target, Response: Message{resp.(proto.Message)}, } - return execution.CallTargets(ctx, targets, info) + return execution.CallTargets(ctx, targets, info, alg) } var _ execution.ContextInfo = &ContextInfoRequest{} diff --git a/internal/api/grpc/server/middleware/execution_interceptor_test.go b/internal/api/grpc/server/middleware/execution_interceptor_test.go index 281db4617af..63e4872fffd 100644 --- a/internal/api/grpc/server/middleware/execution_interceptor_test.go +++ b/internal/api/grpc/server/middleware/execution_interceptor_test.go @@ -15,48 +15,10 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" - "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/execution" + target_domain "github.com/zitadel/zitadel/internal/execution/target" ) -var _ execution.Target = &mockExecutionTarget{} - -type mockExecutionTarget struct { - InstanceID string - ExecutionID string - TargetID string - TargetType domain.TargetType - Endpoint string - Timeout time.Duration - InterruptOnError bool - SigningKey string -} - -func (e *mockExecutionTarget) SetEndpoint(endpoint string) { - e.Endpoint = endpoint -} -func (e *mockExecutionTarget) IsInterruptOnError() bool { - return e.InterruptOnError -} -func (e *mockExecutionTarget) GetEndpoint() string { - return e.Endpoint -} -func (e *mockExecutionTarget) GetTargetType() domain.TargetType { - return e.TargetType -} -func (e *mockExecutionTarget) GetTimeout() time.Duration { - return e.Timeout -} -func (e *mockExecutionTarget) GetTargetID() string { - return e.TargetID -} -func (e *mockExecutionTarget) GetExecutionID() string { - return e.ExecutionID -} -func (e *mockExecutionTarget) GetSigningKey() string { - return e.SigningKey -} - func newMockContentRequest(content string) proto.Message { return &structpb.Struct{ Fields: map[string]*structpb.Value{ @@ -92,7 +54,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { type args struct { ctx context.Context - executionTargets []execution.Target + executionTargets []target_domain.Target targets []target fullMethod string req interface{} @@ -123,7 +85,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{}, + executionTargets: []target_domain.Target{}, req: newMockContentRequest("request"), }, res{ @@ -135,12 +97,11 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, }, @@ -157,14 +118,12 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, - SigningKey: "signingkey", }, }, targets: []target{ @@ -186,15 +145,13 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, }, @@ -217,15 +174,13 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Second, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -247,15 +202,13 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Second, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -272,15 +225,13 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -302,14 +253,12 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeAsync, + TargetType: target_domain.TargetTypeAsync, Timeout: time.Second, - SigningKey: "signingkey", }, }, targets: []target{ @@ -331,14 +280,12 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeAsync, + TargetType: target_domain.TargetTypeAsync, Timeout: time.Minute, - SigningKey: "signingkey", }, }, targets: []target{ @@ -360,15 +307,13 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -389,15 +334,13 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Timeout: time.Second, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -419,15 +362,13 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -449,33 +390,27 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target1", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, - &mockExecutionTarget{ - InstanceID: "instance", + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target2", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, - &mockExecutionTarget{ - InstanceID: "instance", + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target3", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, }, @@ -510,33 +445,27 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target1", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, - &mockExecutionTarget{ - InstanceID: "instance", + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target2", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Second, InterruptOnError: true, - SigningKey: "signingkey", }, - &mockExecutionTarget{ - InstanceID: "instance", + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target3", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Second, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -577,8 +506,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { target.respBody, ) - et := tt.args.executionTargets[i].(*mockExecutionTarget) - et.SetEndpoint(url) + tt.args.executionTargets[i].Endpoint = url closeFuncs[i] = closeF } @@ -587,6 +515,7 @@ func Test_executeTargetsForGRPCFullMethod_request(t *testing.T) { tt.args.executionTargets, tt.args.fullMethod, tt.args.req, + nil, ) if tt.res.wantErr { @@ -640,7 +569,7 @@ func testServerCall( http.Error(w, "error", http.StatusInternalServerError) return } - if _, err := io.WriteString(w, string(resp)); err != nil { + if _, err := w.Write(resp); err != nil { http.Error(w, "error", http.StatusInternalServerError) return } @@ -661,7 +590,7 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { type args struct { ctx context.Context - executionTargets []execution.Target + executionTargets []target_domain.Target targets []target fullMethod string req interface{} @@ -694,7 +623,7 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{}, + executionTargets: []target_domain.Target{}, req: newMockContentRequest("request"), resp: newMockContentRequest("response"), }, @@ -707,15 +636,13 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "request./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -738,15 +665,13 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { args{ ctx: context.Background(), fullMethod: "/service/method", - executionTargets: []execution.Target{ - &mockExecutionTarget{ - InstanceID: "instance", + executionTargets: []target_domain.Target{ + { ExecutionID: "response./zitadel.session.v2.SessionService/SetSession", TargetID: "target", - TargetType: domain.TargetTypeCall, + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, InterruptOnError: true, - SigningKey: "signingkey", }, }, targets: []target{ @@ -776,8 +701,7 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { target.respBody, ) - et := tt.args.executionTargets[i].(*mockExecutionTarget) - et.SetEndpoint(url) + tt.args.executionTargets[i].Endpoint = url closeFuncs[i] = closeF } @@ -787,6 +711,7 @@ func Test_executeTargetsForGRPCFullMethod_response(t *testing.T) { tt.args.fullMethod, tt.args.req, tt.args.resp, + nil, ) if tt.res.wantErr { diff --git a/internal/api/grpc/server/middleware/instance_interceptor_test.go b/internal/api/grpc/server/middleware/instance_interceptor_test.go index cc71de75f77..60f4ab9748e 100644 --- a/internal/api/grpc/server/middleware/instance_interceptor_test.go +++ b/internal/api/grpc/server/middleware/instance_interceptor_test.go @@ -12,6 +12,7 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" http_util "github.com/zitadel/zitadel/internal/api/http" + "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/feature" object_v3 "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha" ) @@ -295,3 +296,7 @@ func (m *mockInstance) EnableImpersonation() bool { func (m *mockInstance) Features() feature.Features { return feature.Features{} } + +func (m *mockInstance) ExecutionRouter() target.Router { + return target.NewRouter(nil) +} diff --git a/internal/api/grpc/server/server.go b/internal/api/grpc/server/server.go index 0c02087c89d..8f2353e480d 100644 --- a/internal/api/grpc/server/server.go +++ b/internal/api/grpc/server/server.go @@ -9,11 +9,12 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials" healthpb "google.golang.org/grpc/health/grpc_health_v1" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoreflect" "github.com/zitadel/zitadel/internal/api/authz" grpc_api "github.com/zitadel/zitadel/internal/api/grpc" "github.com/zitadel/zitadel/internal/api/grpc/server/middleware" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/logstore" "github.com/zitadel/zitadel/internal/logstore/record" "github.com/zitadel/zitadel/internal/query" @@ -60,6 +61,7 @@ func CreateServer( externalDomain string, tlsConfig *tls.Config, accessSvc *logstore.Service[*record.AccessLog], + targetEncAlg crypto.EncryptionAlgorithm, ) *grpc.Server { metricTypes := []metrics.MetricType{metrics.MetricTypeTotalCount, metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode} serverOptions := []grpc.ServerOption{ @@ -75,7 +77,7 @@ func CreateServer( middleware.AuthorizationInterceptor(verifier, systemAuthz, authConfig), middleware.TranslationHandler(), middleware.QuotaExhaustedInterceptor(accessSvc, system_pb.SystemService_ServiceDesc.ServiceName), - middleware.ExecutionHandler(queries), + middleware.ExecutionHandler(targetEncAlg), middleware.ValidationHandler(), middleware.ServiceHandler(), middleware.ActivityInterceptor(), diff --git a/internal/api/http/middleware/instance_interceptor_test.go b/internal/api/http/middleware/instance_interceptor_test.go index da831dff654..55e2da5d3fd 100644 --- a/internal/api/http/middleware/instance_interceptor_test.go +++ b/internal/api/http/middleware/instance_interceptor_test.go @@ -14,6 +14,7 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" zitadel_http "github.com/zitadel/zitadel/internal/api/http" + "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/feature" ) @@ -351,3 +352,7 @@ func (m *mockInstance) EnableImpersonation() bool { func (m *mockInstance) Features() feature.Features { return feature.Features{} } + +func (m *mockInstance) ExecutionRouter() target.Router { + return target.NewRouter(nil) +} diff --git a/internal/api/oidc/op.go b/internal/api/oidc/op.go index 6f59ce35252..dcfb0e3d720 100644 --- a/internal/api/oidc/op.go +++ b/internal/api/oidc/op.go @@ -108,6 +108,7 @@ func NewServer( query *query.Queries, repo repository.Repository, encryptionAlg crypto.EncryptionAlgorithm, + targetEncryptionAlgorithm crypto.EncryptionAlgorithm, cryptoKey []byte, es *eventstore.Eventstore, userAgentCookie, instanceHandler func(http.Handler) http.Handler, @@ -162,6 +163,7 @@ func NewServer( fallbackLogger: fallbackLogger, hasher: hasher, encAlg: encryptionAlg, + targetEncryptionAlgorithm: targetEncryptionAlgorithm, opCrypto: op.NewAESCrypto(opConfig.CryptoKey), assetAPIPrefix: assets.AssetAPI(), } diff --git a/internal/api/oidc/server.go b/internal/api/oidc/server.go index df7127443fd..1a01124dd74 100644 --- a/internal/api/oidc/server.go +++ b/internal/api/oidc/server.go @@ -36,11 +36,12 @@ type Server struct { defaultIdTokenLifetime time.Duration jwksCacheControlMaxAge time.Duration - fallbackLogger *slog.Logger - hasher *crypto.Hasher - signingKeyAlgorithm string - encAlg crypto.EncryptionAlgorithm - opCrypto op.Crypto + fallbackLogger *slog.Logger + hasher *crypto.Hasher + signingKeyAlgorithm string + encAlg crypto.EncryptionAlgorithm + targetEncryptionAlgorithm crypto.EncryptionAlgorithm + opCrypto op.Crypto assetAPIPrefix func(ctx context.Context) string } diff --git a/internal/api/oidc/userinfo.go b/internal/api/oidc/userinfo.go index 6f6e1ddc64b..08ee4da3eb0 100644 --- a/internal/api/oidc/userinfo.go +++ b/internal/api/oidc/userinfo.go @@ -429,10 +429,8 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user if function == "" { return nil } - executionTargets, err := execution.QueryExecutionTargetsForFunction(ctx, s.query, function) - if err != nil { - return err - } + + executionTargets := execution.QueryExecutionTargetsForFunction(ctx, function) info := &ContextInfo{ Function: function, UserInfo: userInfo, @@ -443,7 +441,7 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user UserGrants: qu.UserGrants, } - resp, err := execution.CallTargets(ctx, executionTargets, info) + resp, err := execution.CallTargets(ctx, executionTargets, info, s.targetEncryptionAlgorithm) if err != nil { return err } diff --git a/internal/api/saml/provider.go b/internal/api/saml/provider.go index 428fc35ed9f..7de0a07eb9c 100644 --- a/internal/api/saml/provider.go +++ b/internal/api/saml/provider.go @@ -42,6 +42,7 @@ func NewProvider( repo repository.Repository, encAlg crypto.EncryptionAlgorithm, certEncAlg crypto.EncryptionAlgorithm, + targetEncAlg crypto.EncryptionAlgorithm, es *eventstore.Eventstore, projections *database.DB, instanceHandler, @@ -56,6 +57,7 @@ func NewProvider( repo, encAlg, certEncAlg, + targetEncAlg, es, projections, fmt.Sprintf("%s%s?%s=", login.HandlerPrefix, login.EndpointLogin, login.QueryAuthRequestID), @@ -114,6 +116,7 @@ func newStorage( repo repository.Repository, encAlg crypto.EncryptionAlgorithm, certEncAlg crypto.EncryptionAlgorithm, + targetEncAlg crypto.EncryptionAlgorithm, es *eventstore.Eventstore, db *database.DB, defaultLoginURL string, @@ -123,6 +126,7 @@ func newStorage( return &Storage{ encAlg: encAlg, certEncAlg: certEncAlg, + targetEncAlg: targetEncAlg, locker: crdb.NewLocker(db.DB, locksTable, signingKey), eventstore: es, repo: repo, diff --git a/internal/api/saml/storage.go b/internal/api/saml/storage.go index 5d1ae31b529..b89fb69f9bc 100644 --- a/internal/api/saml/storage.go +++ b/internal/api/saml/storage.go @@ -56,6 +56,7 @@ type Storage struct { certificateAlgorithm string encAlg crypto.EncryptionAlgorithm certEncAlg crypto.EncryptionAlgorithm + targetEncAlg crypto.EncryptionAlgorithm eventstore *eventstore.Eventstore repo repository.Repository @@ -390,10 +391,7 @@ func (p *Storage) getCustomAttributes(ctx context.Context, user *query.User, use } function := exec_repo.ID(domain.ExecutionTypeFunction, domain.ActionFunctionPreSAMLResponse.LocalizationKey()) - executionTargets, err := execution.QueryExecutionTargetsForFunction(ctx, p.query, function) - if err != nil { - return nil, err - } + executionTargets := execution.QueryExecutionTargetsForFunction(ctx, function) // correct time for utc user.CreationDate = user.CreationDate.UTC() @@ -405,7 +403,7 @@ func (p *Storage) getCustomAttributes(ctx context.Context, user *query.User, use UserGrants: userGrants.UserGrants, } - resp, err := execution.CallTargets(ctx, executionTargets, info) + resp, err := execution.CallTargets(ctx, executionTargets, info, p.targetEncAlg) if err != nil { return nil, err } diff --git a/internal/command/action_v2_execution_test.go b/internal/command/action_v2_execution_test.go index d41ea3f2d52..2bd05ee65ce 100644 --- a/internal/command/action_v2_execution_test.go +++ b/internal/command/action_v2_execution_test.go @@ -10,6 +10,7 @@ import ( "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/repository/execution" "github.com/zitadel/zitadel/internal/repository/target" "github.com/zitadel/zitadel/internal/zerrors" @@ -170,7 +171,7 @@ func TestCommands_SetExecutionRequest(t *testing.T) { target.NewAddedEvent(context.Background(), target.NewAggregate("target", "instance"), "name", - domain.TargetTypeWebhook, + target_domain.TargetTypeWebhook, "https://example.com", time.Second, true, @@ -225,7 +226,7 @@ func TestCommands_SetExecutionRequest(t *testing.T) { target.NewAddedEvent(context.Background(), target.NewAggregate("target", "instance"), "name", - domain.TargetTypeWebhook, + target_domain.TargetTypeWebhook, "https://example.com", time.Second, true, @@ -280,7 +281,7 @@ func TestCommands_SetExecutionRequest(t *testing.T) { target.NewAddedEvent(context.Background(), target.NewAggregate("target", "instance"), "name", - domain.TargetTypeWebhook, + target_domain.TargetTypeWebhook, "https://example.com", time.Second, true, @@ -852,7 +853,7 @@ func TestCommands_SetExecutionResponse(t *testing.T) { target.NewAddedEvent(context.Background(), target.NewAggregate("target", "instance"), "name", - domain.TargetTypeWebhook, + target_domain.TargetTypeWebhook, "https://example.com", time.Second, true, @@ -952,7 +953,7 @@ func TestCommands_SetExecutionResponse(t *testing.T) { target.NewAddedEvent(context.Background(), target.NewAggregate("target", "instance"), "name", - domain.TargetTypeWebhook, + target_domain.TargetTypeWebhook, "https://example.com", time.Second, true, diff --git a/internal/command/action_v2_target.go b/internal/command/action_v2_target.go index 3fb374b36f0..1c61203cc45 100644 --- a/internal/command/action_v2_target.go +++ b/internal/command/action_v2_target.go @@ -9,6 +9,7 @@ import ( "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore/v1/models" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/repository/target" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -17,7 +18,7 @@ type AddTarget struct { models.ObjectRoot Name string - TargetType domain.TargetType + TargetType target_domain.TargetType Endpoint string Timeout time.Duration InterruptOnError bool @@ -90,7 +91,7 @@ type ChangeTarget struct { models.ObjectRoot Name *string - TargetType *domain.TargetType + TargetType *target_domain.TargetType Endpoint *string Timeout *time.Duration InterruptOnError *bool diff --git a/internal/command/action_v2_target_model.go b/internal/command/action_v2_target_model.go index cf20c9923d8..75532aa064c 100644 --- a/internal/command/action_v2_target_model.go +++ b/internal/command/action_v2_target_model.go @@ -8,6 +8,7 @@ import ( "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/repository/target" ) @@ -15,7 +16,7 @@ type TargetWriteModel struct { eventstore.WriteModel Name string - TargetType domain.TargetType + TargetType target_domain.TargetType Endpoint string Timeout time.Duration InterruptOnError bool @@ -86,7 +87,7 @@ func (wm *TargetWriteModel) NewChangedEvent( ctx context.Context, agg *eventstore.Aggregate, name *string, - targetType *domain.TargetType, + targetType *target_domain.TargetType, endpoint *string, timeout *time.Duration, interruptOnError *bool, diff --git a/internal/command/action_v2_target_model_test.go b/internal/command/action_v2_target_model_test.go index e8c40c04c85..547577f9441 100644 --- a/internal/command/action_v2_target_model_test.go +++ b/internal/command/action_v2_target_model_test.go @@ -8,8 +8,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/zitadel/zitadel/internal/crypto" - "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/repository/target" ) @@ -17,7 +17,7 @@ func targetAddEvent(aggID, resourceOwner string) *target.AddedEvent { return target.NewAddedEvent(context.Background(), target.NewAggregate(aggID, resourceOwner), "name", - domain.TargetTypeWebhook, + target_domain.TargetTypeWebhook, "https://example.com", time.Second, false, diff --git a/internal/command/action_v2_target_test.go b/internal/command/action_v2_target_test.go index 32ecbff93a0..2f2f3650c94 100644 --- a/internal/command/action_v2_target_test.go +++ b/internal/command/action_v2_target_test.go @@ -9,9 +9,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/zitadel/zitadel/internal/crypto" - "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/v1/models" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/id" "github.com/zitadel/zitadel/internal/id/mock" "github.com/zitadel/zitadel/internal/repository/target" @@ -130,7 +130,7 @@ func TestCommands_AddTarget(t *testing.T) { target.NewAddedEvent(context.Background(), target.NewAggregate("id1", "instance"), "name", - domain.TargetTypeWebhook, + target_domain.TargetTypeWebhook, "https://example.com", time.Second, false, @@ -153,7 +153,7 @@ func TestCommands_AddTarget(t *testing.T) { Name: "name", Endpoint: "https://example.com", Timeout: time.Second, - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, }, resourceOwner: "instance", }, @@ -177,7 +177,7 @@ func TestCommands_AddTarget(t *testing.T) { ctx: context.Background(), add: &AddTarget{ Name: "name", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Timeout: time.Second, Endpoint: "https://example.com", }, @@ -204,7 +204,7 @@ func TestCommands_AddTarget(t *testing.T) { ctx: context.Background(), add: &AddTarget{ Name: "name", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Timeout: time.Second, Endpoint: "https://example.com", }, @@ -235,7 +235,7 @@ func TestCommands_AddTarget(t *testing.T) { ctx: context.Background(), add: &AddTarget{ Name: "name", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Endpoint: "https://example.com", Timeout: time.Second, InterruptOnError: true, @@ -419,7 +419,7 @@ func TestCommands_ChangeTarget(t *testing.T) { ObjectRoot: models.ObjectRoot{ AggregateID: "id1", }, - TargetType: gu.Ptr(domain.TargetTypeWebhook), + TargetType: gu.Ptr(target_domain.TargetTypeWebhook), }, resourceOwner: "instance", }, @@ -505,7 +505,7 @@ func TestCommands_ChangeTarget(t *testing.T) { []target.Changes{ target.ChangeName("name", "name2"), target.ChangeEndpoint("https://example2.com"), - target.ChangeTargetType(domain.TargetTypeCall), + target.ChangeTargetType(target_domain.TargetTypeCall), target.ChangeTimeout(10 * time.Second), target.ChangeInterruptOnError(true), target.ChangeSigningKey(&crypto.CryptoValue{ @@ -529,7 +529,7 @@ func TestCommands_ChangeTarget(t *testing.T) { }, Name: gu.Ptr("name2"), Endpoint: gu.Ptr("https://example2.com"), - TargetType: gu.Ptr(domain.TargetTypeCall), + TargetType: gu.Ptr(target_domain.TargetTypeCall), Timeout: gu.Ptr(10 * time.Second), InterruptOnError: gu.Ptr(true), ExpirationSigningKey: true, diff --git a/internal/command/main_test.go b/internal/command/main_test.go index 61d1abf6fda..6effedefa70 100644 --- a/internal/command/main_test.go +++ b/internal/command/main_test.go @@ -17,6 +17,7 @@ import ( "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/repository" "github.com/zitadel/zitadel/internal/eventstore/repository/mock" + "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/feature" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -220,6 +221,10 @@ func (m *mockInstance) Features() feature.Features { return feature.Features{} } +func (m *mockInstance) ExecutionRouter() target.Router { + return target.NewRouter(nil) +} + func newMockPermissionCheckAllowed() domain.PermissionCheck { return func(ctx context.Context, permission, orgID, resourceID string) (err error) { return nil diff --git a/internal/domain/target.go b/internal/domain/target.go index 1e08f91cf04..925432d25bc 100644 --- a/internal/domain/target.go +++ b/internal/domain/target.go @@ -1,13 +1,5 @@ package domain -type TargetType uint - -const ( - TargetTypeWebhook TargetType = iota - TargetTypeCall - TargetTypeAsync -) - type TargetState int32 const ( diff --git a/internal/eventstore/config.go b/internal/eventstore/config.go index 6fc3f79f2cc..2e71dedf771 100644 --- a/internal/eventstore/config.go +++ b/internal/eventstore/config.go @@ -11,4 +11,5 @@ type Config struct { Pusher Pusher Querier Querier Searcher Searcher + Queue ExecutionQueue } diff --git a/internal/eventstore/mock/queue.mock.go b/internal/eventstore/mock/queue.mock.go new file mode 100644 index 00000000000..403a60ad47e --- /dev/null +++ b/internal/eventstore/mock/queue.mock.go @@ -0,0 +1,62 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/zitadel/zitadel/internal/eventstore (interfaces: ExecutionQueue) +// +// Generated by this command: +// +// mockgen -package mock -destination ./mock/queue.mock.go github.com/zitadel/zitadel/internal/eventstore ExecutionQueue +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + sql "database/sql" + reflect "reflect" + + river "github.com/riverqueue/river" + queue "github.com/zitadel/zitadel/internal/queue" + gomock "go.uber.org/mock/gomock" +) + +// MockExecutionQueue is a mock of ExecutionQueue interface. +type MockExecutionQueue struct { + ctrl *gomock.Controller + recorder *MockExecutionQueueMockRecorder +} + +// MockExecutionQueueMockRecorder is the mock recorder for MockExecutionQueue. +type MockExecutionQueueMockRecorder struct { + mock *MockExecutionQueue +} + +// NewMockExecutionQueue creates a new mock instance. +func NewMockExecutionQueue(ctrl *gomock.Controller) *MockExecutionQueue { + mock := &MockExecutionQueue{ctrl: ctrl} + mock.recorder = &MockExecutionQueueMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockExecutionQueue) EXPECT() *MockExecutionQueueMockRecorder { + return m.recorder +} + +// InsertManyFastTx mocks base method. +func (m *MockExecutionQueue) InsertManyFastTx(arg0 context.Context, arg1 *sql.Tx, arg2 []river.JobArgs, arg3 ...queue.InsertOpt) error { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1, arg2} + for _, a := range arg3 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "InsertManyFastTx", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// InsertManyFastTx indicates an expected call of InsertManyFastTx. +func (mr *MockExecutionQueueMockRecorder) InsertManyFastTx(arg0, arg1, arg2 any, arg3 ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{arg0, arg1, arg2}, arg3...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertManyFastTx", reflect.TypeOf((*MockExecutionQueue)(nil).InsertManyFastTx), varargs...) +} diff --git a/internal/eventstore/queue.go b/internal/eventstore/queue.go new file mode 100644 index 00000000000..3cbfddcca7b --- /dev/null +++ b/internal/eventstore/queue.go @@ -0,0 +1,20 @@ +package eventstore + +import ( + "context" + "database/sql" + + "github.com/riverqueue/river" + + "github.com/zitadel/zitadel/internal/queue" +) + +//go:generate mockgen -package mock -destination ./mock/queue.mock.go github.com/zitadel/zitadel/internal/eventstore ExecutionQueue + +type ExecutionQueue interface { + // InsertManyFastTx wraps [river.Client.InsertManyFastTx] to insert all jobs in + // a single `COPY FROM` execution, within an existing transaction. + // + // Opts are applied to each job before sending them to river. + InsertManyFastTx(ctx context.Context, tx *sql.Tx, args []river.JobArgs, opts ...queue.InsertOpt) error +} diff --git a/internal/eventstore/v3/eventstore.go b/internal/eventstore/v3/eventstore.go index 424805c882f..26400c03713 100644 --- a/internal/eventstore/v3/eventstore.go +++ b/internal/eventstore/v3/eventstore.go @@ -33,6 +33,7 @@ var ( type Eventstore struct { client *database.DB + queue eventstore.ExecutionQueue } var ( @@ -157,8 +158,14 @@ func (es *Eventstore) Client() *database.DB { return es.client } -func NewEventstore(client *database.DB) *Eventstore { - return &Eventstore{client: client} +func NewEventstore(client *database.DB, opts ...EventstoreOption) *Eventstore { + es := &Eventstore{ + client: client, + } + for _, opt := range opts { + opt(es) + } + return es } func (es *Eventstore) Health(ctx context.Context) error { @@ -200,3 +207,11 @@ func (es *Eventstore) pushTx(ctx context.Context, client database.ContextQueryEx } return tx, func(err error) error { return database.CloseTransaction(tx, err) }, nil } + +type EventstoreOption func(*Eventstore) + +func WithExecutionQueueOption(queue eventstore.ExecutionQueue) EventstoreOption { + return func(es *Eventstore) { + es.queue = queue + } +} diff --git a/internal/eventstore/v3/mock_test.go b/internal/eventstore/v3/mock_test.go index 89a50f8fd4f..fce586c4ca3 100644 --- a/internal/eventstore/v3/mock_test.go +++ b/internal/eventstore/v3/mock_test.go @@ -47,6 +47,10 @@ func (e *mockCommand) Fields() []*eventstore.FieldOperation { } func mockEvent(aggregate *eventstore.Aggregate, sequence uint64, payload Payload) eventstore.Event { + return mockEventType(aggregate, sequence, payload, "event.type") +} + +func mockEventType(aggregate *eventstore.Aggregate, sequence uint64, payload Payload, typ string) eventstore.Event { return &event{ command: &command{ InstanceID: aggregate.InstanceID, @@ -55,7 +59,7 @@ func mockEvent(aggregate *eventstore.Aggregate, sequence uint64, payload Payload Owner: aggregate.ResourceOwner, Creator: "creator", Revision: 1, - CommandType: "event.type", + CommandType: typ, Payload: payload, }, sequence: sequence, diff --git a/internal/eventstore/v3/push.go b/internal/eventstore/v3/push.go index 6497b96ed85..862e0c44048 100644 --- a/internal/eventstore/v3/push.go +++ b/internal/eventstore/v3/push.go @@ -6,11 +6,14 @@ import ( _ "embed" "fmt" + "github.com/riverqueue/river" "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/queue" + exec_repo "github.com/zitadel/zitadel/internal/repository/execution" "github.com/zitadel/zitadel/internal/telemetry/tracing" ) @@ -76,6 +79,11 @@ func (es *Eventstore) writeCommands(ctx context.Context, client database.Context return nil, err } + err = es.queueExecutions(ctx, tx, events) + if err != nil { + return nil, err + } + return events, nil } @@ -106,3 +114,54 @@ func writeEvents(ctx context.Context, tx database.Tx, commands []eventstore.Comm } return events, nil } + +func (es *Eventstore) queueExecutions(ctx context.Context, tx database.Tx, events []eventstore.Event) error { + if es.queue == nil { + return nil + } + + sqlTx, ok := tx.(*sql.Tx) + if !ok { + types := make([]string, len(events)) + for i, event := range events { + types[i] = string(event.Type()) + } + logging.WithFields("event_types", types).Warningf("event executions skipped: wrong type of transaction %T", tx) + return nil + } + jobArgs, err := eventsToJobArgs(ctx, events) + if err != nil { + return err + } + if len(jobArgs) == 0 { + return nil + } + return es.queue.InsertManyFastTx( + ctx, sqlTx, jobArgs, + queue.WithQueueName(exec_repo.QueueName), + ) +} + +func eventsToJobArgs(ctx context.Context, events []eventstore.Event) ([]river.JobArgs, error) { + if len(events) == 0 { + return nil, nil + } + router := authz.GetInstance(ctx).ExecutionRouter() + if router.IsZero() { + return nil, nil + } + + jobArgs := make([]river.JobArgs, 0, len(events)) + for _, event := range events { + targets, ok := router.GetEventBestMatch(fmt.Sprintf("event/%s", event.Type())) + if !ok { + continue + } + req, err := exec_repo.NewRequest(event, targets) + if err != nil { + return nil, err + } + jobArgs = append(jobArgs, req) + } + return jobArgs, nil +} diff --git a/internal/eventstore/v3/push_test.go b/internal/eventstore/v3/push_test.go index da583891e9f..50f771b5498 100644 --- a/internal/eventstore/v3/push_test.go +++ b/internal/eventstore/v3/push_test.go @@ -1,15 +1,23 @@ package eventstore import ( + "context" + "database/sql" _ "embed" "testing" + "github.com/riverqueue/river" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database/postgres" "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/eventstore/mock" + "github.com/zitadel/zitadel/internal/execution/target" + exec_repo "github.com/zitadel/zitadel/internal/repository/execution" ) func Test_mapCommands(t *testing.T) { @@ -251,3 +259,159 @@ func Test_mapCommands(t *testing.T) { }) } } + +func TestEventstore_queueExecutions(t *testing.T) { + events := []eventstore.Event{ + mockEventType(mockAggregate("TEST"), 1, nil, "ex.foo.bar"), + mockEventType(mockAggregate("TEST"), 2, nil, "ex.bar.foo"), + mockEventType(mockAggregate("TEST"), 3, nil, "ex.removed"), + } + type args struct { + ctx context.Context + tx database.Tx + events []eventstore.Event + } + tests := []struct { + name string + queue func(t *testing.T) eventstore.ExecutionQueue + args args + wantErr bool + }{ + { + name: "incorrect Tx type, noop", + queue: func(t *testing.T) eventstore.ExecutionQueue { + mQueue := mock.NewMockExecutionQueue(gomock.NewController(t)) + return mQueue + }, + args: args{ + ctx: context.Background(), + tx: nil, + events: events, + }, + wantErr: false, + }, + { + name: "no events", + queue: func(t *testing.T) eventstore.ExecutionQueue { + mQueue := mock.NewMockExecutionQueue(gomock.NewController(t)) + return mQueue + }, + args: args{ + ctx: context.Background(), + tx: &sql.Tx{}, + events: []eventstore.Event{}, + }, + wantErr: false, + }, + { + name: "no router in Ctx", + queue: func(t *testing.T) eventstore.ExecutionQueue { + mQueue := mock.NewMockExecutionQueue(gomock.NewController(t)) + return mQueue + }, + args: args{ + ctx: context.Background(), + tx: &sql.Tx{}, + events: events, + }, + wantErr: false, + }, + { + name: "not found in router", + queue: func(t *testing.T) eventstore.ExecutionQueue { + mQueue := mock.NewMockExecutionQueue(gomock.NewController(t)) + return mQueue + }, + args: args{ + ctx: authz.WithExecutionRouter( + context.Background(), + target.NewRouter([]target.Target{ + { + ExecutionID: "function/fooBar", + }, + }), + ), + tx: &sql.Tx{}, + events: events, + }, + wantErr: false, + }, + { + name: "event prefix", + queue: func(t *testing.T) eventstore.ExecutionQueue { + mQueue := mock.NewMockExecutionQueue(gomock.NewController(t)) + mQueue.EXPECT().InsertManyFastTx( + gomock.Any(), + gomock.Any(), + []river.JobArgs{ + mustNewRequest(t, events[0], []target.Target{{ExecutionID: "event"}}), + mustNewRequest(t, events[1], []target.Target{{ExecutionID: "event"}}), + mustNewRequest(t, events[2], []target.Target{{ExecutionID: "event"}}), + }, + gomock.Any(), + ) + return mQueue + }, + args: args{ + ctx: authz.WithExecutionRouter( + context.Background(), + target.NewRouter([]target.Target{ + {ExecutionID: "function/fooBar"}, + {ExecutionID: "event"}, + }), + ), + tx: &sql.Tx{}, + events: events, + }, + wantErr: false, + }, + { + name: "event wildcard and exact match", + queue: func(t *testing.T) eventstore.ExecutionQueue { + mQueue := mock.NewMockExecutionQueue(gomock.NewController(t)) + mQueue.EXPECT().InsertManyFastTx( + gomock.Any(), + gomock.Any(), + []river.JobArgs{ + mustNewRequest(t, events[0], []target.Target{{ExecutionID: "event/ex.foo.*"}}), + mustNewRequest(t, events[2], []target.Target{{ExecutionID: "event/ex.removed"}}), + }, + gomock.Any(), + ) + return mQueue + }, + args: args{ + ctx: authz.WithExecutionRouter( + context.Background(), + target.NewRouter([]target.Target{ + {ExecutionID: "function/fooBar"}, + {ExecutionID: "event/ex.foo.*"}, + {ExecutionID: "event/ex.removed"}, + }), + ), + tx: &sql.Tx{}, + events: events, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + es := &Eventstore{ + queue: tt.queue(t), + } + err := es.queueExecutions(tt.args.ctx, tt.args.tx, tt.args.events) + if tt.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + }) + } +} + +func mustNewRequest(t *testing.T, e eventstore.Event, targets []target.Target) *exec_repo.Request { + req, err := exec_repo.NewRequest(e, targets) + require.NoError(t, err, "exec_repo.NewRequest") + return req +} diff --git a/internal/execution/execution.go b/internal/execution/execution.go index b885858d948..df23465fb51 100644 --- a/internal/execution/execution.go +++ b/internal/execution/execution.go @@ -6,14 +6,15 @@ import ( "encoding/json" "io" "net/http" - "strings" "time" "github.com/zitadel/logging" + "github.com/zitadel/zitadel/internal/api/authz" zhttp "github.com/zitadel/zitadel/internal/api/http" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" - "github.com/zitadel/zitadel/internal/query" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/repository/execution" "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" @@ -26,27 +27,19 @@ type ContextInfo interface { SetHTTPResponseBody([]byte) error } -type Target interface { - GetTargetID() string - IsInterruptOnError() bool - GetEndpoint() string - GetTargetType() domain.TargetType - GetTimeout() time.Duration - GetSigningKey() string -} - // CallTargets call a list of targets in order with handling of error and responses func CallTargets( ctx context.Context, - targets []Target, + targets []target_domain.Target, info ContextInfo, + alg crypto.EncryptionAlgorithm, ) (_ interface{}, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() for _, target := range targets { // call the type of target - resp, err := CallTarget(ctx, target, info) + resp, err := CallTarget(ctx, target, info, alg) // handle error if interrupt is set if err != nil && target.IsInterruptOnError() { return nil, err @@ -68,22 +61,28 @@ type ContextInfoRequest interface { // CallTarget call the desired type of target with handling of responses func CallTarget( ctx context.Context, - target Target, + target target_domain.Target, info ContextInfoRequest, + alg crypto.EncryptionAlgorithm, ) (res []byte, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() + signingKey, err := target.GetSigningKey(alg) + if err != nil { + return nil, zerrors.ThrowInternal(err, "EXEC-thiiCh5b", "Errors.Internal") + } + switch target.GetTargetType() { // get request, ignore response and return request and error for handling in list of targets - case domain.TargetTypeWebhook: - return nil, webhook(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody(), target.GetSigningKey()) + case target_domain.TargetTypeWebhook: + return nil, webhook(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody(), signingKey) // get request, return response and error - case domain.TargetTypeCall: - return Call(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody(), target.GetSigningKey()) - case domain.TargetTypeAsync: - go func(ctx context.Context, target Target, info []byte) { - if _, err := Call(ctx, target.GetEndpoint(), target.GetTimeout(), info, target.GetSigningKey()); err != nil { + case target_domain.TargetTypeCall: + return Call(ctx, target.GetEndpoint(), target.GetTimeout(), info.GetHTTPRequestBody(), signingKey) + case target_domain.TargetTypeAsync: + go func(ctx context.Context, target target_domain.Target, info []byte) { + if _, err := Call(ctx, target.GetEndpoint(), target.GetTimeout(), info, signingKey); err != nil { logging.WithFields("target", target.GetTargetID()).OnError(err).Info(err) } }(context.WithoutCancel(ctx), target, info.GetHTTPRequestBody()) @@ -157,58 +156,29 @@ type ErrorBody struct { ForwardedErrorMessage string `json:"forwardedErrorMessage,omitempty"` } -type ExecutionTargetsQueries interface { - TargetsByExecutionID(ctx context.Context, ids []string) (execution []*query.ExecutionTarget, err error) - TargetsByExecutionIDs(ctx context.Context, ids1, ids2 []string) (execution []*query.ExecutionTarget, err error) -} - -func QueryExecutionTargetsForRequestAndResponse( +func QueryExecutionTargetsForRequest( ctx context.Context, - queries ExecutionTargetsQueries, fullMethod string, -) ([]Target, []Target) { +) []target_domain.Target { ctx, span := tracing.NewSpan(ctx) defer span.End() - targets, err := queries.TargetsByExecutionIDs(ctx, - idsForFullMethod(fullMethod, domain.ExecutionTypeRequest), - idsForFullMethod(fullMethod, domain.ExecutionTypeResponse), - ) - requestTargets := make([]Target, 0, len(targets)) - responseTargets := make([]Target, 0, len(targets)) - if err != nil { - logging.WithFields("fullMethod", fullMethod).WithError(err).Info("unable to query targets") - return requestTargets, responseTargets - } - - for _, target := range targets { - if strings.HasPrefix(target.GetExecutionID(), execution.IDAll(domain.ExecutionTypeRequest)) { - requestTargets = append(requestTargets, target) - } else if strings.HasPrefix(target.GetExecutionID(), execution.IDAll(domain.ExecutionTypeResponse)) { - responseTargets = append(responseTargets, target) - } - } - - return requestTargets, responseTargets + requestTargets, _ := authz.GetInstance(ctx).ExecutionRouter().GetEventBestMatch(execution.ID(domain.ExecutionTypeRequest, fullMethod)) + return requestTargets } -func idsForFullMethod(fullMethod string, executionType domain.ExecutionType) []string { - return []string{execution.ID(executionType, fullMethod), execution.ID(executionType, serviceFromFullMethod(fullMethod)), execution.IDAll(executionType)} +func QueryExecutionTargetsForResponse( + ctx context.Context, + fullMethod string, +) []target_domain.Target { + ctx, span := tracing.NewSpan(ctx) + defer span.End() + + responseTargets, _ := authz.GetInstance(ctx).ExecutionRouter().GetEventBestMatch(execution.ID(domain.ExecutionTypeResponse, fullMethod)) + return responseTargets } -func serviceFromFullMethod(s string) string { - parts := strings.Split(s, "/") - return parts[1] -} - -func QueryExecutionTargetsForFunction(ctx context.Context, query ExecutionTargetsQueries, function string) ([]Target, error) { - queriedActionsV2, err := query.TargetsByExecutionID(ctx, []string{function}) - if err != nil { - return nil, err - } - executionTargets := make([]Target, len(queriedActionsV2)) - for i, action := range queriedActionsV2 { - executionTargets[i] = action - } - return executionTargets, nil +func QueryExecutionTargetsForFunction(ctx context.Context, function string) []target_domain.Target { + executionTargets, _ := authz.GetInstance(ctx).ExecutionRouter().GetEventBestMatch(function) + return executionTargets } diff --git a/internal/execution/execution_test.go b/internal/execution/execution_test.go index 036b160ab74..df3c551a4e2 100644 --- a/internal/execution/execution_test.go +++ b/internal/execution/execution_test.go @@ -13,11 +13,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" "google.golang.org/protobuf/types/known/structpb" "github.com/zitadel/zitadel/internal/api/grpc/server/middleware" - "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/execution" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/pkg/actions" ) @@ -132,7 +134,7 @@ func Test_CallTarget(t *testing.T) { ctx context.Context info *middleware.ContextInfoRequest server *callTestServer - target *mockTarget + target target_domain.Target } type res struct { body []byte @@ -155,7 +157,7 @@ func Test_CallTarget(t *testing.T) { timeout: time.Second, statusCode: http.StatusInternalServerError, }, - target: &mockTarget{ + target: target_domain.Target{ TargetType: 4, }, }, @@ -175,8 +177,8 @@ func Test_CallTarget(t *testing.T) { respondBody: []byte("{\"content\":\"request2\"}"), statusCode: http.StatusInternalServerError, }, - target: &mockTarget{ - TargetType: domain.TargetTypeWebhook, + target: target_domain.Target{ + TargetType: target_domain.TargetTypeWebhook, Timeout: time.Minute, }, }, @@ -196,8 +198,8 @@ func Test_CallTarget(t *testing.T) { respondBody: []byte("{\"content\":\"request2\"}"), statusCode: http.StatusOK, }, - target: &mockTarget{ - TargetType: domain.TargetTypeWebhook, + target: target_domain.Target{ + TargetType: target_domain.TargetTypeWebhook, Timeout: time.Minute, }, }, @@ -218,10 +220,14 @@ func Test_CallTarget(t *testing.T) { statusCode: http.StatusOK, signingKey: "signingkey", }, - target: &mockTarget{ - TargetType: domain.TargetTypeWebhook, + target: target_domain.Target{ + TargetType: target_domain.TargetTypeWebhook, Timeout: time.Minute, - SigningKey: "signingkey", + SigningKey: &crypto.CryptoValue{ + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("signingkey"), + }, }, }, res{ @@ -240,8 +246,8 @@ func Test_CallTarget(t *testing.T) { respondBody: []byte("{\"content\":\"request2\"}"), statusCode: http.StatusInternalServerError, }, - target: &mockTarget{ - TargetType: domain.TargetTypeCall, + target: target_domain.Target{ + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, }, }, @@ -261,8 +267,8 @@ func Test_CallTarget(t *testing.T) { respondBody: []byte("{\"content\":\"request2\"}"), statusCode: http.StatusOK, }, - target: &mockTarget{ - TargetType: domain.TargetTypeCall, + target: target_domain.Target{ + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, }, }, @@ -283,10 +289,14 @@ func Test_CallTarget(t *testing.T) { statusCode: http.StatusOK, signingKey: "signingkey", }, - target: &mockTarget{ - TargetType: domain.TargetTypeCall, + target: target_domain.Target{ + TargetType: target_domain.TargetTypeCall, Timeout: time.Minute, - SigningKey: "signingkey", + SigningKey: &crypto.CryptoValue{ + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("signingkey"), + }, }, }, res{ @@ -296,7 +306,7 @@ func Test_CallTarget(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - respBody, err := testServer(t, tt.args.server, testCallTarget(tt.args.ctx, tt.args.info, tt.args.target)) + respBody, err := testServer(t, tt.args.server, testCallTarget(tt.args.ctx, tt.args.info, tt.args.target, crypto.CreateMockEncryptionAlg(gomock.NewController(t)))) if tt.res.wantErr { assert.Error(t, err) } else { @@ -312,7 +322,7 @@ func Test_CallTargets(t *testing.T) { ctx context.Context info *middleware.ContextInfoRequest servers []*callTestServer - targets []*mockTarget + targets []target_domain.Target } type res struct { ret interface{} @@ -341,7 +351,7 @@ func Test_CallTargets(t *testing.T) { respondBody: requestContextInfoBody2, statusCode: http.StatusInternalServerError, }}, - targets: []*mockTarget{ + targets: []target_domain.Target{ {InterruptOnError: false}, {InterruptOnError: true}, }, @@ -368,7 +378,7 @@ func Test_CallTargets(t *testing.T) { respondBody: requestContextInfoBody2, statusCode: http.StatusInternalServerError, }}, - targets: []*mockTarget{ + targets: []target_domain.Target{ {InterruptOnError: false}, {InterruptOnError: false}, }, @@ -395,7 +405,7 @@ func Test_CallTargets(t *testing.T) { respondBody: []byte("just a string, not json"), statusCode: http.StatusOK, }}, - targets: []*mockTarget{ + targets: []target_domain.Target{ {InterruptOnError: false}, {InterruptOnError: true}, }, @@ -422,7 +432,7 @@ func Test_CallTargets(t *testing.T) { respondBody: []byte("just a string, not json"), statusCode: http.StatusOK, }}, - targets: []*mockTarget{ + targets: []target_domain.Target{ {InterruptOnError: false}, {InterruptOnError: false}, }}, @@ -435,7 +445,7 @@ func Test_CallTargets(t *testing.T) { t.Run(tt.name, func(t *testing.T) { respBody, err := testServers(t, tt.args.servers, - testCallTargets(tt.args.ctx, tt.args.info, tt.args.targets), + testCallTargets(tt.args.ctx, tt.args.info, tt.args.targets, crypto.CreateMockEncryptionAlg(gomock.NewController(t))), ) if tt.res.wantErr { assert.Error(t, err) @@ -447,38 +457,6 @@ func Test_CallTargets(t *testing.T) { } } -var _ execution.Target = &mockTarget{} - -type mockTarget struct { - InstanceID string - ExecutionID string - TargetID string - TargetType domain.TargetType - Endpoint string - Timeout time.Duration - InterruptOnError bool - SigningKey string -} - -func (e *mockTarget) GetTargetID() string { - return e.TargetID -} -func (e *mockTarget) IsInterruptOnError() bool { - return e.InterruptOnError -} -func (e *mockTarget) GetEndpoint() string { - return e.Endpoint -} -func (e *mockTarget) GetTargetType() domain.TargetType { - return e.TargetType -} -func (e *mockTarget) GetTimeout() time.Duration { - return e.Timeout -} -func (e *mockTarget) GetSigningKey() string { - return e.SigningKey -} - type callTestServer struct { method string expectBody []byte @@ -527,7 +505,7 @@ func listen( time.Sleep(c.timeout) w.Header().Set("Content-Type", "application/json") - if _, err := io.WriteString(w, string(c.respondBody)); err != nil { + if _, err := w.Write(c.respondBody); err != nil { http.Error(w, "error", http.StatusInternalServerError) return } @@ -554,25 +532,27 @@ func testCall(ctx context.Context, timeout time.Duration, body []byte, signingKe func testCallTarget(ctx context.Context, info *middleware.ContextInfoRequest, - target *mockTarget, + target target_domain.Target, + alg crypto.EncryptionAlgorithm, ) func(string) ([]byte, error) { return func(url string) (r []byte, err error) { target.Endpoint = url - return execution.CallTarget(ctx, target, info) + return execution.CallTarget(ctx, target, info, alg) } } func testCallTargets(ctx context.Context, info *middleware.ContextInfoRequest, - target []*mockTarget, + target []target_domain.Target, + alg crypto.EncryptionAlgorithm, ) func([]string) (interface{}, error) { return func(urls []string) (interface{}, error) { - targets := make([]execution.Target, len(target)) + targets := make([]target_domain.Target, len(target)) for i, t := range target { t.Endpoint = urls[i] targets[i] = t } - return execution.CallTargets(ctx, targets, info) + return execution.CallTargets(ctx, targets, info, alg) } } diff --git a/internal/execution/gen_mock.go b/internal/execution/gen_mock.go index 93eebfbb02d..80f25fb216c 100644 --- a/internal/execution/gen_mock.go +++ b/internal/execution/gen_mock.go @@ -1,4 +1,3 @@ package execution -//go:generate mockgen -package mock -destination ./mock/queries.mock.go github.com/zitadel/zitadel/internal/execution Queries //go:generate mockgen -package mock -destination ./mock/queue.mock.go github.com/zitadel/zitadel/internal/execution Queue diff --git a/internal/execution/handlers.go b/internal/execution/handlers.go deleted file mode 100644 index 1c27cb5920d..00000000000 --- a/internal/execution/handlers.go +++ /dev/null @@ -1,159 +0,0 @@ -package execution - -import ( - "context" - "encoding/json" - "slices" - "strings" - - "github.com/riverqueue/river" - - "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/domain" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/eventstore/handler/v2" - "github.com/zitadel/zitadel/internal/query" - "github.com/zitadel/zitadel/internal/queue" - exec_repo "github.com/zitadel/zitadel/internal/repository/execution" -) - -const ( - HandlerTable = "projections.execution_handler" -) - -type Queue interface { - Insert(ctx context.Context, args river.JobArgs, opts ...queue.InsertOpt) error -} - -type Queries interface { - TargetsByExecutionID(ctx context.Context, ids []string) (execution []*query.ExecutionTarget, err error) - InstanceByID(ctx context.Context, id string) (instance authz.Instance, err error) -} - -type eventHandler struct { - eventTypes []string - aggregateTypeFromEventType func(typ eventstore.EventType) eventstore.AggregateType - query Queries - queue Queue -} - -func NewEventHandler( - ctx context.Context, - config handler.Config, - eventTypes []string, - aggregateTypeFromEventType func(typ eventstore.EventType) eventstore.AggregateType, - query Queries, - queue Queue, -) *handler.Handler { - return handler.NewHandler(ctx, &config, &eventHandler{ - eventTypes: eventTypes, - aggregateTypeFromEventType: aggregateTypeFromEventType, - query: query, - queue: queue, - }) -} - -func (u *eventHandler) Name() string { - return HandlerTable -} - -func (u *eventHandler) Reducers() []handler.AggregateReducer { - aggList := make(map[eventstore.AggregateType][]eventstore.EventType) - for _, eventType := range u.eventTypes { - aggType := u.aggregateTypeFromEventType(eventstore.EventType(eventType)) - aggEventTypes := aggList[aggType] - if !slices.Contains(aggEventTypes, eventstore.EventType(eventType)) { - aggList[aggType] = append(aggList[aggType], eventstore.EventType(eventType)) - } - } - - aggReducers := make([]handler.AggregateReducer, 0, len(aggList)) - for aggType, aggEventTypes := range aggList { - eventReducers := make([]handler.EventReducer, len(aggEventTypes)) - for j, eventType := range aggEventTypes { - eventReducers[j] = handler.EventReducer{ - Event: eventType, - Reduce: u.reduce, - } - } - aggReducers = append(aggReducers, handler.AggregateReducer{ - Aggregate: aggType, - EventReducers: eventReducers, - }) - } - return aggReducers -} - -// FilterGlobalEvents implements [handler.GlobalProjection] -func (u *eventHandler) FilterGlobalEvents() {} - -func groupsFromEventType(s string) []string { - parts := strings.Split(s, ".") - groups := make([]string, len(parts)) - for i := range parts { - groups[i] = strings.Join(parts[:i+1], ".") - if i < len(parts)-1 { - groups[i] += ".*" - } - } - slices.Reverse(groups) - return groups -} - -func idsForEventType(eventType string) []string { - ids := make([]string, 0) - for _, group := range groupsFromEventType(eventType) { - ids = append(ids, - exec_repo.ID(domain.ExecutionTypeEvent, group), - ) - } - return append(ids, - exec_repo.IDAll(domain.ExecutionTypeEvent), - ) -} - -func (u *eventHandler) reduce(e eventstore.Event) (*handler.Statement, error) { - ctx := HandlerContext(context.Background(), e.Aggregate()) - - targets, err := u.query.TargetsByExecutionID(ctx, idsForEventType(string(e.Type()))) - if err != nil { - return nil, err - } - - // no execution from worker necessary - if len(targets) == 0 { - return handler.NewNoOpStatement(e), nil - } - - return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error { - ctx = HandlerContext(ctx, e.Aggregate()) - req, err := NewRequest(e, targets) - if err != nil { - return err - } - return u.queue.Insert(ctx, - req, - queue.WithQueueName(exec_repo.QueueName), - ) - }), nil -} - -func NewRequest(e eventstore.Event, targets []*query.ExecutionTarget) (*exec_repo.Request, error) { - targetsData, err := json.Marshal(targets) - if err != nil { - return nil, err - } - eventData, err := json.Marshal(e) - if err != nil { - return nil, err - } - return &exec_repo.Request{ - Aggregate: e.Aggregate(), - Sequence: e.Sequence(), - EventType: e.Type(), - CreatedAt: e.CreatedAt(), - UserID: e.Creator(), - EventData: eventData, - TargetsData: targetsData, - }, nil -} diff --git a/internal/execution/handlers_test.go b/internal/execution/handlers_test.go deleted file mode 100644 index a1239471606..00000000000 --- a/internal/execution/handlers_test.go +++ /dev/null @@ -1,487 +0,0 @@ -package execution - -import ( - "database/sql" - "encoding/json" - "errors" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "go.uber.org/mock/gomock" - - "github.com/zitadel/zitadel/internal/domain" - "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/eventstore/repository" - "github.com/zitadel/zitadel/internal/execution/mock" - "github.com/zitadel/zitadel/internal/query" - "github.com/zitadel/zitadel/internal/repository/action" - execution_rp "github.com/zitadel/zitadel/internal/repository/execution" - "github.com/zitadel/zitadel/internal/repository/session" - "github.com/zitadel/zitadel/internal/repository/user" - "github.com/zitadel/zitadel/internal/zerrors" -) - -func Test_EventExecution(t *testing.T) { - type args struct { - event eventstore.Event - targets []*query.ExecutionTarget - } - type res struct { - targets []Target - contextInfo *execution_rp.ContextInfoEvent - wantErr bool - } - tests := []struct { - name string - args args - res res - }{ - { - "session added, ok", - args{ - event: &eventstore.BaseEvent{ - Agg: &eventstore.Aggregate{ - ID: "aggID", - Type: session.AggregateType, - ResourceOwner: "resourceOwner", - InstanceID: "instanceID", - Version: session.AggregateVersion, - }, - EventType: session.AddedType, - Seq: 1, - Creation: time.Date(2024, 1, 1, 1, 1, 1, 1, time.UTC), - User: userID, - Data: []byte(`{"ID":"","Seq":1,"Pos":0,"Creation":"2024-01-01T01:01:01.000000001Z"}`), - }, - targets: []*query.ExecutionTarget{{ - InstanceID: instanceID, - ExecutionID: "executionID", - TargetID: "targetID", - TargetType: domain.TargetTypeWebhook, - Endpoint: "endpoint", - Timeout: time.Minute, - InterruptOnError: true, - SigningKey: "key", - }}, - }, - res{ - targets: []Target{ - &query.ExecutionTarget{ - InstanceID: instanceID, - ExecutionID: "executionID", - TargetID: "targetID", - TargetType: domain.TargetTypeWebhook, - Endpoint: "endpoint", - Timeout: time.Minute, - InterruptOnError: true, - SigningKey: "key", - }, - }, - contextInfo: &execution_rp.ContextInfoEvent{ - AggregateID: "aggID", - AggregateType: "session", - ResourceOwner: "resourceOwner", - InstanceID: "instanceID", - Version: "v1", - Sequence: 1, - EventType: "session.added", - CreatedAt: time.Date(2024, 1, 1, 1, 1, 1, 1, time.UTC).Format(time.RFC3339Nano), - UserID: userID, - EventPayload: []byte(`{"ID":"","Seq":1,"Pos":0,"Creation":"2024-01-01T01:01:01.000000001Z"}`), - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - request, err := NewRequest(tt.args.event, tt.args.targets) - if tt.res.wantErr { - assert.Error(t, err) - assert.Nil(t, request) - return - } - assert.NoError(t, err) - targets, err := TargetsFromRequest(request) - assert.NoError(t, err) - assert.Equal(t, tt.res.targets, targets) - assert.Equal(t, tt.res.contextInfo, execution_rp.ContextInfoFromRequest(request)) - }) - } -} - -func Test_groupsFromEventType(t *testing.T) { - type args struct { - eventType eventstore.EventType - } - type res struct { - groups []string - } - tests := []struct { - name string - args args - res res - }{ - { - "user human mfa init skipped, ok", - args{ - eventType: user.HumanMFAInitSkippedType, - }, - res{ - groups: []string{ - "user.human.mfa.init.skipped", - "user.human.mfa.init.*", - "user.human.mfa.*", - "user.human.*", - "user.*", - }, - }, - }, - { - "session added, ok", - args{ - eventType: session.AddedType, - }, - res{ - groups: []string{ - "session.added", - "session.*", - }, - }, - }, - { - "user added, ok", - args{ - eventType: user.HumanAddedType, - }, - res{ - groups: []string{ - "user.human.added", - "user.human.*", - "user.*", - }, - }, - }, - { - "execution set, ok", - args{ - eventType: execution_rp.SetEventV2Type, - }, - res{ - groups: []string{ - "execution.v2.set", - "execution.v2.*", - "execution.*", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.res.groups, groupsFromEventType(string(tt.args.eventType))) - }) - } -} - -func Test_idsForEventType(t *testing.T) { - type args struct { - eventType eventstore.EventType - } - type res struct { - groups []string - } - tests := []struct { - name string - args args - res res - }{ - { - "session added, ok", - args{ - eventType: session.AddedType, - }, - res{ - groups: []string{ - "event/session.added", - "event/session.*", - "event", - }, - }, - }, - { - "user added, ok", - args{ - eventType: user.HumanAddedType, - }, - res{ - groups: []string{ - "event/user.human.added", - "event/user.human.*", - "event/user.*", - "event", - }, - }, - }, - { - "execution set, ok", - args{ - eventType: execution_rp.SetEventV2Type, - }, - res{ - groups: []string{ - "event/execution.v2.set", - "event/execution.v2.*", - "event/execution.*", - "event", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.res.groups, idsForEventType(string(tt.args.eventType))) - }) - } -} - -func TestActionProjection_reduces(t *testing.T) { - tests := []struct { - name string - test func(*gomock.Controller, *mock.MockQueries, *mock.MockQueue) (fields, args, want) - }{ - { - name: "reduce, action, error", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, q *mock.MockQueue) (f fields, a args, w want) { - queries.EXPECT().TargetsByExecutionID(gomock.Any(), gomock.Any()).Return(nil, zerrors.ThrowInternal(nil, "QUERY-37ardr0pki", "Errors.Query.CloseRows")) - return fields{ - queries: queries, - queue: q, - }, args{ - event: &action.AddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - InstanceID: instanceID, - AggregateID: eventID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - Typ: action.AddedEventType, - Data: []byte(eventData), - EditorUser: userID, - Seq: 1, - AggregateType: action.AggregateType, - Version: action.AggregateVersion, - }), - Name: "name", - Script: "name(){}", - Timeout: 3 * time.Second, - AllowedToFail: true, - }, - mapper: action.AddedEventMapper, - }, want{ - err: func(tt assert.TestingT, err error, i ...interface{}) bool { - return errors.Is(err, zerrors.ThrowInternal(nil, "QUERY-37ardr0pki", "Errors.Query.CloseRows")) - }, - } - }, - }, - - { - name: "reduce, action, none", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, q *mock.MockQueue) (f fields, a args, w want) { - queries.EXPECT().TargetsByExecutionID(gomock.Any(), gomock.Any()).Return([]*query.ExecutionTarget{}, nil) - return fields{ - queries: queries, - queue: q, - }, args{ - event: &action.AddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - InstanceID: instanceID, - AggregateID: eventID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: time.Now().UTC(), - Typ: action.AddedEventType, - Data: []byte(eventData), - EditorUser: userID, - Seq: 1, - AggregateType: action.AggregateType, - Version: action.AggregateVersion, - }), - Name: "name", - Script: "name(){}", - Timeout: 3 * time.Second, - AllowedToFail: true, - }, - mapper: action.AddedEventMapper, - }, want{ - noOperation: true, - } - }, - }, - { - name: "reduce, action, single", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, q *mock.MockQueue) (f fields, a args, w want) { - targets := mockTargets(1) - queries.EXPECT().TargetsByExecutionID(gomock.Any(), gomock.Any()).Return(targets, nil) - createdAt := time.Now().UTC() - q.EXPECT().Insert( - gomock.Any(), - &execution_rp.Request{ - Aggregate: &eventstore.Aggregate{ - InstanceID: instanceID, - Type: action.AggregateType, - Version: action.AggregateVersion, - ID: eventID, - ResourceOwner: orgID, - }, - Sequence: 1, - CreatedAt: createdAt, - EventType: action.AddedEventType, - UserID: userID, - EventData: []byte(eventData), - TargetsData: mockTargetsToBytes(targets), - }, - gomock.Any(), - ).Return(nil) - return fields{ - queries: queries, - queue: q, - }, args{ - event: &action.AddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - InstanceID: instanceID, - AggregateID: eventID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: createdAt, - Typ: action.AddedEventType, - Data: []byte(eventData), - EditorUser: userID, - Seq: 1, - AggregateType: action.AggregateType, - Version: action.AggregateVersion, - }), - Name: "name", - Script: "name(){}", - Timeout: 3 * time.Second, - AllowedToFail: true, - }, - mapper: action.AddedEventMapper, - }, w - }, - }, - { - name: "reduce, action, multiple", - test: func(ctrl *gomock.Controller, queries *mock.MockQueries, q *mock.MockQueue) (f fields, a args, w want) { - targets := mockTargets(3) - queries.EXPECT().TargetsByExecutionID(gomock.Any(), gomock.Any()).Return(targets, nil) - createdAt := time.Now().UTC() - q.EXPECT().Insert( - gomock.Any(), - &execution_rp.Request{ - Aggregate: &eventstore.Aggregate{ - InstanceID: instanceID, - Type: action.AggregateType, - Version: action.AggregateVersion, - ID: eventID, - ResourceOwner: orgID, - }, - Sequence: 1, - CreatedAt: createdAt, - EventType: action.AddedEventType, - UserID: userID, - EventData: []byte(eventData), - TargetsData: mockTargetsToBytes(targets), - }, - gomock.Any(), - ).Return(nil) - return fields{ - queries: queries, - queue: q, - }, args{ - event: &action.AddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{ - InstanceID: instanceID, - AggregateID: eventID, - ResourceOwner: sql.NullString{String: orgID}, - CreationDate: createdAt, - Typ: action.AddedEventType, - Data: []byte(eventData), - EditorUser: userID, - Seq: 1, - AggregateType: action.AggregateType, - Version: action.AggregateVersion, - }), - Name: "name", - Script: "name(){}", - Timeout: 3 * time.Second, - AllowedToFail: true, - }, - mapper: action.AddedEventMapper, - }, w - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - queries := mock.NewMockQueries(ctrl) - queue := mock.NewMockQueue(ctrl) - f, a, w := tt.test(ctrl, queries, queue) - - event, err := a.mapper(a.event) - assert.NoError(t, err) - - stmt, err := newEventExecutionsHandler(queries, f).reduce(event) - if w.err != nil { - w.err(t, err) - return - } - assert.NoError(t, err) - - if w.noOperation { - assert.Nil(t, stmt.Execute) - return - } - err = stmt.Execute(t.Context(), nil, "") - if w.stmtErr != nil { - w.stmtErr(t, err) - return - } - assert.NoError(t, err) - }) - } -} - -func mockTarget() *query.ExecutionTarget { - return &query.ExecutionTarget{ - InstanceID: "instanceID", - ExecutionID: "executionID", - TargetID: "targetID", - TargetType: domain.TargetTypeWebhook, - Endpoint: "endpoint", - Timeout: time.Minute, - InterruptOnError: true, - SigningKey: "key", - } -} - -func mockTargets(count int) []*query.ExecutionTarget { - var targets []*query.ExecutionTarget - if count > 0 { - targets = make([]*query.ExecutionTarget, count) - for i := range targets { - targets[i] = mockTarget() - } - } - return targets -} - -func mockTargetsToBytes(targets []*query.ExecutionTarget) []byte { - data, _ := json.Marshal(targets) - return data -} - -func newEventExecutionsHandler(queries *mock.MockQueries, f fields) *eventHandler { - return &eventHandler{ - queue: f.queue, - query: queries, - } -} diff --git a/internal/execution/mock/queries.mock.go b/internal/execution/mock/queries.mock.go deleted file mode 100644 index ab7cf38a32c..00000000000 --- a/internal/execution/mock/queries.mock.go +++ /dev/null @@ -1,72 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/zitadel/zitadel/internal/execution (interfaces: Queries) -// -// Generated by this command: -// -// mockgen -package mock -destination ./mock/queries.mock.go github.com/zitadel/zitadel/internal/execution Queries -// - -// Package mock is a generated GoMock package. -package mock - -import ( - context "context" - reflect "reflect" - - authz "github.com/zitadel/zitadel/internal/api/authz" - query "github.com/zitadel/zitadel/internal/query" - gomock "go.uber.org/mock/gomock" -) - -// MockQueries is a mock of Queries interface. -type MockQueries struct { - ctrl *gomock.Controller - recorder *MockQueriesMockRecorder -} - -// MockQueriesMockRecorder is the mock recorder for MockQueries. -type MockQueriesMockRecorder struct { - mock *MockQueries -} - -// NewMockQueries creates a new mock instance. -func NewMockQueries(ctrl *gomock.Controller) *MockQueries { - mock := &MockQueries{ctrl: ctrl} - mock.recorder = &MockQueriesMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockQueries) EXPECT() *MockQueriesMockRecorder { - return m.recorder -} - -// InstanceByID mocks base method. -func (m *MockQueries) InstanceByID(arg0 context.Context, arg1 string) (authz.Instance, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "InstanceByID", arg0, arg1) - ret0, _ := ret[0].(authz.Instance) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// InstanceByID indicates an expected call of InstanceByID. -func (mr *MockQueriesMockRecorder) InstanceByID(arg0, arg1 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstanceByID", reflect.TypeOf((*MockQueries)(nil).InstanceByID), arg0, arg1) -} - -// TargetsByExecutionID mocks base method. -func (m *MockQueries) TargetsByExecutionID(arg0 context.Context, arg1 []string) ([]*query.ExecutionTarget, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "TargetsByExecutionID", arg0, arg1) - ret0, _ := ret[0].([]*query.ExecutionTarget) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// TargetsByExecutionID indicates an expected call of TargetsByExecutionID. -func (mr *MockQueriesMockRecorder) TargetsByExecutionID(arg0, arg1 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TargetsByExecutionID", reflect.TypeOf((*MockQueries)(nil).TargetsByExecutionID), arg0, arg1) -} diff --git a/internal/execution/mock/queue.mock.go b/internal/execution/mock/queue.mock.go deleted file mode 100644 index c0e8d5fc7b7..00000000000 --- a/internal/execution/mock/queue.mock.go +++ /dev/null @@ -1,61 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/zitadel/zitadel/internal/execution (interfaces: Queue) -// -// Generated by this command: -// -// mockgen -package mock -destination ./mock/queue.mock.go github.com/zitadel/zitadel/internal/execution Queue -// - -// Package mock is a generated GoMock package. -package mock - -import ( - context "context" - reflect "reflect" - - river "github.com/riverqueue/river" - queue "github.com/zitadel/zitadel/internal/queue" - gomock "go.uber.org/mock/gomock" -) - -// MockQueue is a mock of Queue interface. -type MockQueue struct { - ctrl *gomock.Controller - recorder *MockQueueMockRecorder -} - -// MockQueueMockRecorder is the mock recorder for MockQueue. -type MockQueueMockRecorder struct { - mock *MockQueue -} - -// NewMockQueue creates a new mock instance. -func NewMockQueue(ctrl *gomock.Controller) *MockQueue { - mock := &MockQueue{ctrl: ctrl} - mock.recorder = &MockQueueMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockQueue) EXPECT() *MockQueueMockRecorder { - return m.recorder -} - -// Insert mocks base method. -func (m *MockQueue) Insert(arg0 context.Context, arg1 river.JobArgs, arg2 ...queue.InsertOpt) error { - m.ctrl.T.Helper() - varargs := []any{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Insert", varargs...) - ret0, _ := ret[0].(error) - return ret0 -} - -// Insert indicates an expected call of Insert. -func (mr *MockQueueMockRecorder) Insert(arg0, arg1 any, arg2 ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockQueue)(nil).Insert), varargs...) -} diff --git a/internal/execution/projections.go b/internal/execution/projections.go index d16d7c6fcab..88fbf7c7bba 100644 --- a/internal/execution/projections.go +++ b/internal/execution/projections.go @@ -3,10 +3,8 @@ package execution import ( "context" - "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" - "github.com/zitadel/zitadel/internal/query" - "github.com/zitadel/zitadel/internal/query/projection" "github.com/zitadel/zitadel/internal/queue" ) @@ -15,18 +13,12 @@ var ( ) func Register( - ctx context.Context, - executionsCustomConfig projection.CustomConfig, workerConfig WorkerConfig, - queries *query.Queries, - eventTypes []string, queue *queue.Queue, + targetEncAlg crypto.EncryptionAlgorithm, ) { queue.ShouldStart() - projections = []*handler.Handler{ - NewEventHandler(ctx, projection.ApplyCustomConfig(executionsCustomConfig), eventTypes, eventstore.AggregateTypeFromEventType, queries, queue), - } - queue.AddWorkers(NewWorker(workerConfig)) + queue.AddWorkers(NewWorker(workerConfig, targetEncAlg)) } func Start(ctx context.Context) { diff --git a/internal/execution/target/router.go b/internal/execution/target/router.go new file mode 100644 index 00000000000..7ca5667c5b9 --- /dev/null +++ b/internal/execution/target/router.go @@ -0,0 +1,69 @@ +package target + +import ( + "slices" + "strings" +) + +type element struct { + ID string `json:"id"` + Targets []Target `json:"targets,omitempty"` +} + +type Router []element + +func NewRouter(targets []Target) Router { + m := make(map[string][]Target) + for _, t := range targets { + m[t.GetExecutionID()] = append(m[t.GetExecutionID()], t) + } + router := make(Router, 0, len(m)) + for id, targets := range m { + router = append(router, element{ + ID: id, + Targets: targets, + }) + } + slices.SortFunc(router, func(a, b element) int { + return strings.Compare(a.ID, b.ID) + }) + return router +} + +// Get execution targets by exact match of the executionID +func (r Router) Get(executionID string) ([]Target, bool) { + i, ok := slices.BinarySearchFunc(r, executionID, func(a element, b string) int { + return strings.Compare(a.ID, b) + }) + if ok { + return r[i].Targets, true + } + return nil, false +} + +// GetEventBestMatch returns the best matching execution targets for an event. +// The following match priority is used: +// 1. Exact match +// 2. Wildcard match +// 3. Prefix match ("event") +func (r Router) GetEventBestMatch(executionID string) ([]Target, bool) { + t, ok := r.Get(executionID) + if ok { + return t, true + } + var bestMatch element + for _, e := range r { + if e.ID == "event" && strings.HasPrefix(executionID, e.ID) { + bestMatch, ok = e, true + } + cut, has := strings.CutSuffix(e.ID, ".*") + if has && strings.HasPrefix(executionID, cut) { + bestMatch, ok = e, true + } + } + return bestMatch.Targets, ok +} + +func (r Router) IsZero() bool { + return len(r) == 0 +} diff --git a/internal/execution/target/router_test.go b/internal/execution/target/router_test.go new file mode 100644 index 00000000000..e0923a97483 --- /dev/null +++ b/internal/execution/target/router_test.go @@ -0,0 +1,129 @@ +package target + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + eventGlobalTarget = Target{ExecutionID: "event", TargetID: "event_global"} + eventGroupTarget = Target{ExecutionID: "event/foo.*", TargetID: "event_group"} + eventMatchTarget = Target{ExecutionID: "event/foo.bar", TargetID: "event_specific"} + functionCallTarget1 = Target{ExecutionID: "function/Call", TargetID: "function_call_1"} + functionCallTarget2 = Target{ExecutionID: "function/Call", TargetID: "function_call_2"} + + testTargets = []Target{eventGlobalTarget, eventGroupTarget, eventMatchTarget, functionCallTarget1, functionCallTarget2} +) + +func TestBinarySearchRouter_Get(t *testing.T) { + r := NewRouter(testTargets) + type args struct { + id string + } + tests := []struct { + name string + args args + wantTargets []Target + wantOk bool + }{ + { + name: "event global does not match exactly", + args: args{ + id: "event/bar.foo", + }, + wantTargets: nil, + wantOk: false, + }, + { + name: "event group does not match exactly", + args: args{ + id: "event/foo.bar.baz", + }, + wantTargets: nil, + wantOk: false, + }, + { + name: "event match", + args: args{ + id: "event/foo.bar", + }, + wantTargets: []Target{eventMatchTarget}, + wantOk: true, + }, + { + name: "function match", + args: args{ + id: "function/Call", + }, + wantTargets: []Target{functionCallTarget1, functionCallTarget2}, + wantOk: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, ok := r.Get(tt.args.id) + assert.Equal(t, tt.wantTargets, got) + assert.Equal(t, tt.wantOk, ok) + }) + } +} + +func TestBinarySearchRouter_GetEventBestMatch(t *testing.T) { + type args struct { + id string + } + tests := []struct { + name string + targets []Target + args args + wantTargets []Target + wantOk bool + }{ + + { + name: "event global match", + targets: testTargets, + args: args{ + id: "event/bar.foo", + }, + wantTargets: []Target{eventGlobalTarget}, + wantOk: true, + }, + { + name: "event group match", + targets: testTargets[1:], + args: args{ + id: "event/foo.bar.baz", + }, + wantTargets: []Target{eventGroupTarget}, + wantOk: true, + }, + { + name: "event match", + targets: testTargets, + args: args{ + id: "event/foo.bar", + }, + wantTargets: []Target{eventMatchTarget}, + wantOk: true, + }, + { + name: "function match", + targets: testTargets, + args: args{ + id: "function/Call", + }, + wantTargets: []Target{functionCallTarget1, functionCallTarget2}, + wantOk: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := NewRouter(tt.targets) + got, ok := r.GetEventBestMatch(tt.args.id) + assert.Equal(t, tt.wantTargets, got) + assert.Equal(t, tt.wantOk, ok) + }) + } +} diff --git a/internal/execution/target/target.go b/internal/execution/target/target.go new file mode 100644 index 00000000000..108d4ecd766 --- /dev/null +++ b/internal/execution/target/target.go @@ -0,0 +1,50 @@ +package target + +import ( + "time" + + "github.com/zitadel/zitadel/internal/crypto" +) + +type TargetType uint + +const ( + TargetTypeWebhook TargetType = iota + TargetTypeCall + TargetTypeAsync +) + +type Target struct { + ExecutionID string `json:"execution_id,omitempty"` + TargetID string `json:"target_id,omitempty"` + TargetType TargetType `json:"target_type,omitempty"` + Endpoint string `json:"endpoint,omitempty"` + Timeout time.Duration `json:"timeout,omitempty"` + InterruptOnError bool `json:"interrupt_on_error,omitempty"` + SigningKey *crypto.CryptoValue `json:"signing_key,omitempty"` +} + +func (e *Target) GetExecutionID() string { + return e.ExecutionID +} +func (e *Target) GetTargetID() string { + return e.TargetID +} +func (e *Target) IsInterruptOnError() bool { + return e.InterruptOnError +} +func (e *Target) GetEndpoint() string { + return e.Endpoint +} +func (e *Target) GetTargetType() TargetType { + return e.TargetType +} +func (e *Target) GetTimeout() time.Duration { + return e.Timeout +} +func (e *Target) GetSigningKey(alg crypto.EncryptionAlgorithm) (string, error) { + if e.SigningKey == nil { + return "", nil + } + return crypto.DecryptString(e.SigningKey, alg) +} diff --git a/internal/execution/worker.go b/internal/execution/worker.go index 105fa7d46e0..3da069d477d 100644 --- a/internal/execution/worker.go +++ b/internal/execution/worker.go @@ -9,7 +9,8 @@ import ( "github.com/riverqueue/river" - "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/crypto" + target_domain "github.com/zitadel/zitadel/internal/execution/target" exec_repo "github.com/zitadel/zitadel/internal/repository/execution" ) @@ -18,6 +19,8 @@ type Worker struct { config WorkerConfig now nowFunc + + targetEncAlg crypto.EncryptionAlgorithm } // Timeout implements the Timeout-function of [river.Worker]. @@ -42,7 +45,7 @@ func (w *Worker) Work(ctx context.Context, job *river.Job[*exec_repo.Request]) e return river.JobCancel(fmt.Errorf("unable to unmarshal targets because %w", err)) } - _, err = CallTargets(ctx, targets, exec_repo.ContextInfoFromRequest(job.Args)) + _, err = CallTargets(ctx, targets, exec_repo.ContextInfoFromRequest(job.Args), w.targetEncAlg) if err != nil { // If there is an error returned from the targets, it means that the execution was interrupted return river.JobCancel(fmt.Errorf("interruption during call of targets because %w", err)) @@ -61,10 +64,12 @@ type WorkerConfig struct { func NewWorker( config WorkerConfig, + targetEncAlg crypto.EncryptionAlgorithm, ) *Worker { return &Worker{ - config: config, - now: time.Now, + config: config, + now: time.Now, + targetEncAlg: targetEncAlg, } } @@ -77,14 +82,10 @@ func (w *Worker) Register(workers *river.Workers, queues map[string]river.QueueC } } -func TargetsFromRequest(e *exec_repo.Request) ([]Target, error) { - var execTargets []*query.ExecutionTarget - if err := json.Unmarshal(e.TargetsData, &execTargets); err != nil { +func TargetsFromRequest(e *exec_repo.Request) ([]target_domain.Target, error) { + var targets []target_domain.Target + if err := json.Unmarshal(e.TargetsData, &targets); err != nil { return nil, err } - targets := make([]Target, len(execTargets)) - for i, target := range execTargets { - targets[i] = target - } return targets, nil } diff --git a/internal/execution/worker_test.go b/internal/execution/worker_test.go index 32f7879477c..1f8aac90bb0 100644 --- a/internal/execution/worker_test.go +++ b/internal/execution/worker_test.go @@ -15,35 +15,21 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/execution/mock" - "github.com/zitadel/zitadel/internal/query" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/repository/action" exec_repo "github.com/zitadel/zitadel/internal/repository/execution" "github.com/zitadel/zitadel/internal/repository/user" "github.com/zitadel/zitadel/internal/zerrors" ) -type fields struct { - queries *mock.MockQueries - queue *mock.MockQueue -} type fieldsWorker struct { now nowFunc } -type args struct { - event eventstore.Event - mapper func(event eventstore.Event) (eventstore.Event, error) -} type argsWorker struct { job *river.Job[*exec_repo.Request] } -type want struct { - noOperation bool - err assert.ErrorAssertionFunc - stmtErr assert.ErrorAssertionFunc -} type wantWorker struct { - targets []*query.ExecutionTarget + targets []target_domain.Target sendStatusCode int err assert.ErrorAssertionFunc } @@ -286,3 +272,25 @@ func Test_handleEventExecution(t *testing.T) { }) } } + +func mockTarget() target_domain.Target { + return target_domain.Target{ + ExecutionID: "executionID", + TargetID: "targetID", + TargetType: target_domain.TargetTypeWebhook, + Endpoint: "endpoint", + Timeout: time.Minute, + InterruptOnError: true, + } +} + +func mockTargets(count int) []target_domain.Target { + var targets []target_domain.Target + if count > 0 { + targets = make([]target_domain.Target, count) + for i := range targets { + targets[i] = mockTarget() + } + } + return targets +} diff --git a/internal/integration/client.go b/internal/integration/client.go index 6d7737ff1c0..a5db09d9682 100644 --- a/internal/integration/client.go +++ b/internal/integration/client.go @@ -20,6 +20,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/integration/scim" "github.com/zitadel/zitadel/pkg/grpc/action/v2" action_v2beta "github.com/zitadel/zitadel/pkg/grpc/action/v2beta" @@ -1043,7 +1044,7 @@ func (i *Instance) DeleteProjectGrantMembership(t *testing.T, ctx context.Contex require.NoError(t, err) } -func (i *Instance) CreateTarget(ctx context.Context, t *testing.T, name, endpoint string, ty domain.TargetType, interrupt bool) *action.CreateTargetResponse { +func (i *Instance) CreateTarget(ctx context.Context, t *testing.T, name, endpoint string, ty target_domain.TargetType, interrupt bool) *action.CreateTargetResponse { if name == "" { name = gofakeit.Name() } @@ -1053,19 +1054,19 @@ func (i *Instance) CreateTarget(ctx context.Context, t *testing.T, name, endpoin Timeout: durationpb.New(5 * time.Second), } switch ty { - case domain.TargetTypeWebhook: + case target_domain.TargetTypeWebhook: req.TargetType = &action.CreateTargetRequest_RestWebhook{ RestWebhook: &action.RESTWebhook{ InterruptOnError: interrupt, }, } - case domain.TargetTypeCall: + case target_domain.TargetTypeCall: req.TargetType = &action.CreateTargetRequest_RestCall{ RestCall: &action.RESTCall{ InterruptOnError: interrupt, }, } - case domain.TargetTypeAsync: + case target_domain.TargetTypeAsync: req.TargetType = &action.CreateTargetRequest_RestAsync{ RestAsync: &action.RESTAsync{}, } diff --git a/internal/query/execution.go b/internal/query/execution.go index 4739a5839ec..6617c42a4e0 100644 --- a/internal/query/execution.go +++ b/internal/query/execution.go @@ -8,17 +8,13 @@ import ( "encoding/json" "errors" "slices" - "time" sq "github.com/Masterminds/squirrel" "github.com/zitadel/zitadel/internal/api/authz" - "github.com/zitadel/zitadel/internal/crypto" - "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query/projection" exec "github.com/zitadel/zitadel/internal/repository/execution" - "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -65,10 +61,6 @@ var ( var ( //go:embed execution_targets.sql executionTargetsQuery string - //go:embed targets_by_execution_id.sql - TargetsByExecutionIDQuery string - //go:embed targets_by_execution_ids.sql - TargetsByExecutionIDsQuery string ) type Executions struct { @@ -156,71 +148,6 @@ func targetItemJSONB(t domain.ExecutionTargetType, targetItem string) ([]byte, e return json.Marshal([]*executionTarget{target}) } -// TargetsByExecutionID query list of targets for best match of a list of IDs, for example: -// [ "request/zitadel.action.v3alpha.ActionService/GetTargetByID", -// "request/zitadel.action.v3alpha.ActionService", -// "request" ] -func (q *Queries) TargetsByExecutionID(ctx context.Context, ids []string) (execution []*ExecutionTarget, err error) { - ctx, span := tracing.NewSpan(ctx) - defer func() { span.End() }() - - instanceID := authz.GetInstance(ctx).InstanceID() - if instanceID == "" { - return nil, nil - } - - err = q.client.QueryContext(ctx, - func(rows *sql.Rows) error { - execution, err = scanExecutionTargets(rows) - return err - }, - TargetsByExecutionIDQuery, - instanceID, - database.TextArray[string](ids), - ) - for i := range execution { - if err := execution[i].decryptSigningKey(q.targetEncryptionAlgorithm); err != nil { - return nil, err - } - } - return execution, err -} - -// TargetsByExecutionIDs query list of targets for best matches of 2 separate lists of IDs, combined for performance, for example: -// [ "request/zitadel.action.v3alpha.ActionService/GetTargetByID", -// "request/zitadel.action.v3alpha.ActionService", -// "request" ] -// and -// [ "response/zitadel.action.v3alpha.ActionService/GetTargetByID", -// "response/zitadel.action.v3alpha.ActionService", -// "response" ] -func (q *Queries) TargetsByExecutionIDs(ctx context.Context, ids1, ids2 []string) (execution []*ExecutionTarget, err error) { - ctx, span := tracing.NewSpan(ctx) - defer func() { span.End() }() - - instanceID := authz.GetInstance(ctx).InstanceID() - if instanceID == "" { - return nil, nil - } - - err = q.client.QueryContext(ctx, - func(rows *sql.Rows) error { - execution, err = scanExecutionTargets(rows) - return err - }, - TargetsByExecutionIDsQuery, - instanceID, - database.TextArray[string](ids1), - database.TextArray[string](ids2), - ) - for i := range execution { - if err := execution[i].decryptSigningKey(q.targetEncryptionAlgorithm); err != nil { - return nil, err - } - } - return execution, err -} - func prepareExecutionQuery() (sq.SelectBuilder, func(row *sql.Row) (*Execution, error)) { return sq.Select( ExecutionColumnInstanceID.identifier(), @@ -358,99 +285,3 @@ func scanExecutions(rows *sql.Rows) (*Executions, error) { }, }, nil } - -type ExecutionTarget struct { - InstanceID string - ExecutionID string - TargetID string - TargetType domain.TargetType - Endpoint string - Timeout time.Duration - InterruptOnError bool - signingKey *crypto.CryptoValue - SigningKey string -} - -func (e *ExecutionTarget) GetExecutionID() string { - return e.ExecutionID -} -func (e *ExecutionTarget) GetTargetID() string { - return e.TargetID -} -func (e *ExecutionTarget) IsInterruptOnError() bool { - return e.InterruptOnError -} -func (e *ExecutionTarget) GetEndpoint() string { - return e.Endpoint -} -func (e *ExecutionTarget) GetTargetType() domain.TargetType { - return e.TargetType -} -func (e *ExecutionTarget) GetTimeout() time.Duration { - return e.Timeout -} -func (e *ExecutionTarget) GetSigningKey() string { - return e.SigningKey -} - -func (t *ExecutionTarget) decryptSigningKey(alg crypto.EncryptionAlgorithm) error { - if t.signingKey == nil { - return nil - } - keyValue, err := crypto.DecryptString(t.signingKey, alg) - if err != nil { - return zerrors.ThrowInternal(err, "QUERY-bxevy3YXwy", "Errors.Internal") - } - t.SigningKey = keyValue - return nil -} - -func scanExecutionTargets(rows *sql.Rows) ([]*ExecutionTarget, error) { - targets := make([]*ExecutionTarget, 0) - for rows.Next() { - target := new(ExecutionTarget) - - var ( - instanceID = &sql.NullString{} - executionID = &sql.NullString{} - targetID = &sql.NullString{} - targetType = &sql.NullInt32{} - endpoint = &sql.NullString{} - timeout = &sql.NullInt64{} - interruptOnError = &sql.NullBool{} - signingKey = &crypto.CryptoValue{} - ) - - err := rows.Scan( - executionID, - instanceID, - targetID, - targetType, - endpoint, - timeout, - interruptOnError, - signingKey, - ) - - if err != nil { - return nil, err - } - - target.InstanceID = instanceID.String - target.ExecutionID = executionID.String - target.TargetID = targetID.String - target.TargetType = domain.TargetType(targetType.Int32) - target.Endpoint = endpoint.String - target.Timeout = time.Duration(timeout.Int64) - target.InterruptOnError = interruptOnError.Bool - target.signingKey = signingKey - - targets = append(targets, target) - } - - if err := rows.Close(); err != nil { - return nil, zerrors.ThrowInternal(err, "QUERY-37ardr0pki", "Errors.Query.CloseRows") - } - - return targets, nil -} diff --git a/internal/query/execution_targets.sql b/internal/query/execution_targets.sql index a6e6dd6caa3..7c97185bb96 100644 --- a/internal/query/execution_targets.sql +++ b/internal/query/execution_targets.sql @@ -1,10 +1,10 @@ SELECT et.instance_id, et.execution_id, JSONB_AGG( - JSON_OBJECT( - 'position' : et.position, - 'include' : et.include, - 'target' : et.target_id + JSON_BUILD_OBJECT( + 'position', et.position, + 'include', et.include, + 'target', et.target_id ) ) as targets FROM projections.executions1_targets AS et diff --git a/internal/query/execution_test.go b/internal/query/execution_test.go index 64f9a4849f7..a9fc2227795 100644 --- a/internal/query/execution_test.go +++ b/internal/query/execution_test.go @@ -22,7 +22,7 @@ var ( ` COUNT(*) OVER ()` + ` FROM projections.executions1` + ` JOIN (` + - `SELECT et.instance_id, et.execution_id, JSONB_AGG( JSON_OBJECT( 'position' : et.position, 'include' : et.include, 'target' : et.target_id ) ) as targets` + + `SELECT et.instance_id, et.execution_id, JSONB_AGG( JSON_BUILD_OBJECT( 'position', et.position, 'include', et.include, 'target', et.target_id ) ) as targets` + ` FROM projections.executions1_targets AS et` + ` INNER JOIN projections.targets2 AS t ON et.instance_id = t.instance_id AND et.target_id IS NOT NULL AND et.target_id = t.id` + ` GROUP BY et.instance_id, et.execution_id` + @@ -46,7 +46,7 @@ var ( ` execution_targets.targets` + ` FROM projections.executions1` + ` JOIN (` + - `SELECT et.instance_id, et.execution_id, JSONB_AGG( JSON_OBJECT( 'position' : et.position, 'include' : et.include, 'target' : et.target_id ) ) as targets` + + `SELECT et.instance_id, et.execution_id, JSONB_AGG( JSON_BUILD_OBJECT( 'position', et.position, 'include', et.include, 'target', et.target_id ) ) as targets` + ` FROM projections.executions1_targets AS et` + ` INNER JOIN projections.targets2 AS t ON et.instance_id = t.instance_id AND et.target_id IS NOT NULL AND et.target_id = t.id` + ` GROUP BY et.instance_id, et.execution_id` + diff --git a/internal/query/generic.go b/internal/query/generic.go index ea2257c0132..68bc34d82b3 100644 --- a/internal/query/generic.go +++ b/internal/query/generic.go @@ -5,6 +5,7 @@ import ( "database/sql" sq "github.com/Masterminds/squirrel" + "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/database" @@ -24,6 +25,7 @@ func genericRowsQuery[R any]( stmt, args, err := query.ToSql() if err != nil { + logging.OnError(err).Warn("query: invalid request") return rnil, zerrors.ThrowInvalidArgument(err, "QUERY-05wf2q36ji", "Errors.Query.InvalidRequest") } err = client.QueryContext(ctx, func(rows *sql.Rows) error { @@ -31,6 +33,7 @@ func genericRowsQuery[R any]( return err }, stmt, args...) if err != nil { + logging.OnError(err).Error("query: internal error") return rnil, zerrors.ThrowInternal(err, "QUERY-y2u7vctrha", "Errors.Internal") } return resp, err @@ -50,6 +53,7 @@ func genericRowsQueryWithState[R Stateful]( } state, err := latestState(ctx, client, projection) if err != nil { + logging.OnError(err).Error("query: latest state") return rnil, err } resp.SetState(state) diff --git a/internal/query/instance.go b/internal/query/instance.go index bb311cbb853..817f6cfb998 100644 --- a/internal/query/instance.go +++ b/internal/query/instance.go @@ -15,10 +15,12 @@ import ( "github.com/zitadel/logging" "golang.org/x/text/language" + "github.com/zitadel/zitadel/cmd/build" "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/feature" "github.com/zitadel/zitadel/internal/query/projection" "github.com/zitadel/zitadel/internal/telemetry/tracing" @@ -216,7 +218,7 @@ func (q *Queries) InstanceByHost(ctx context.Context, instanceHost, publicHost s publicDomain := strings.Split(publicHost, ":")[0] // remove possible port instance, ok := q.caches.instance.Get(ctx, instanceIndexByHost, instanceDomain) - if ok { + if ok && instance.ZitadelVersion == build.Version() { return instance, instance.checkDomain(instanceDomain, publicDomain) } instance, scan := scanAuthzInstance() @@ -239,14 +241,16 @@ func (q *Queries) InstanceByID(ctx context.Context, id string) (_ authz.Instance }() instance, ok := q.caches.instance.Get(ctx, instanceIndexByID, id) - if ok { + if ok && instance.ZitadelVersion == build.Version() { return instance, nil } instance, scan := scanAuthzInstance() err = q.client.QueryRowContext(ctx, scan, instanceByIDQuery, id) logging.OnError(err).WithField("instance_id", id).Warn("instance by ID") + if err == nil { + instance.ZitadelVersion = build.Version() q.caches.instance.Set(ctx, instance) } return instance, err @@ -460,19 +464,21 @@ func prepareInstanceDomainQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instance, } type authzInstance struct { - ID string `json:"id,omitempty"` - IAMProjectID string `json:"iam_project_id,omitempty"` - ConsoleID string `json:"console_id,omitempty"` - ConsoleAppID string `json:"console_app_id,omitempty"` - DefaultLang language.Tag `json:"default_lang,omitempty"` - DefaultOrgID string `json:"default_org_id,omitempty"` - CSP csp `json:"csp,omitempty"` - Impersonation bool `json:"impersonation,omitempty"` - IsBlocked *bool `json:"is_blocked,omitempty"` - LogRetention *time.Duration `json:"log_retention,omitempty"` - Feature feature.Features `json:"feature,omitempty"` - ExternalDomains database.TextArray[string] `json:"external_domains,omitempty"` - TrustedDomains database.TextArray[string] `json:"trusted_domains,omitempty"` + ID string `json:"id,omitempty"` + IAMProjectID string `json:"iam_project_id,omitempty"` + ConsoleID string `json:"console_id,omitempty"` + ConsoleAppID string `json:"console_app_id,omitempty"` + DefaultLang language.Tag `json:"default_lang,omitempty"` + DefaultOrgID string `json:"default_org_id,omitempty"` + CSP csp `json:"csp,omitempty"` + Impersonation bool `json:"impersonation,omitempty"` + IsBlocked *bool `json:"is_blocked,omitempty"` + LogRetention *time.Duration `json:"log_retention,omitempty"` + Feature feature.Features `json:"feature,omitempty"` + ExternalDomains database.TextArray[string] `json:"external_domains,omitempty"` + TrustedDomains database.TextArray[string] `json:"trusted_domains,omitempty"` + ExecutionTargets target_domain.Router `json:"execution_targets,omitzero"` + ZitadelVersion string `json:"zitadel_version,omitempty"` } type csp struct { @@ -527,6 +533,10 @@ func (i *authzInstance) Features() feature.Features { return i.Feature } +func (i *authzInstance) ExecutionRouter() target_domain.Router { + return i.ExecutionTargets +} + var errPublicDomain = "public domain %q not trusted" func (i *authzInstance) checkDomain(instanceDomain, publicDomain string) error { @@ -562,6 +572,7 @@ func scanAuthzInstance() (*authzInstance, func(row *sql.Row) error) { auditLogRetention database.NullDuration block sql.NullBool features []byte + executionTargetsBytes []byte ) err := row.Scan( &instance.ID, @@ -578,6 +589,7 @@ func scanAuthzInstance() (*authzInstance, func(row *sql.Row) error) { &features, &instance.ExternalDomains, &instance.TrustedDomains, + &executionTargetsBytes, ) if errors.Is(err, sql.ErrNoRows) { return zerrors.ThrowNotFound(nil, "QUERY-1kIjX", "Errors.IAM.NotFound") @@ -600,6 +612,13 @@ func scanAuthzInstance() (*authzInstance, func(row *sql.Row) error) { if err = json.Unmarshal(features, &instance.Feature); err != nil { return zerrors.ThrowInternal(err, "QUERY-Po8ki", "Errors.Internal") } + if len(executionTargetsBytes) > 0 { + var targets []target_domain.Target + if err := json.Unmarshal(executionTargetsBytes, &targets); err != nil { + return zerrors.ThrowInternal(err, "QUERY-aeKa2", "Errors.Internal") + } + instance.ExecutionTargets = target_domain.NewRouter(targets) + } return nil } } @@ -616,6 +635,8 @@ func (c *Caches) registerInstanceInvalidation() { invalidate = cacheInvalidationFunc(c.instance, instanceIndexByID, getResourceOwner) projection.LimitsProjection.RegisterCacheInvalidation(invalidate) projection.RestrictionsProjection.RegisterCacheInvalidation(invalidate) + projection.ExecutionProjection.RegisterCacheInvalidation(invalidate) + projection.TargetProjection.RegisterCacheInvalidation(invalidate) // System feature update should invalidate all instances, so Truncate the cache. projection.SystemFeatureProjection.RegisterCacheInvalidation(func(ctx context.Context, _ []*eventstore.Aggregate) { diff --git a/internal/query/instance_by_domain.sql b/internal/query/instance_by_domain.sql index 60896027c43..12d82040e8e 100644 --- a/internal/query/instance_by_domain.sql +++ b/internal/query/instance_by_domain.sql @@ -26,6 +26,29 @@ with domain as ( from domain d join projections.instance_trusted_domains td on d.instance_id = td.instance_id group by td.instance_id +), execution_targets as ( + select instance_id, json_agg(x.execution_targets) as execution_targets from ( + select e.instance_id, json_build_object( + 'execution_id', et.execution_id, + 'target_id', t.id, + 'target_type', t.target_type, + 'endpoint', t.endpoint, + 'timeout', t.timeout, + 'interrupt_on_error', t.interrupt_on_error, + 'signing_key', t.signing_key + ) as execution_targets + from domain d + join projections.executions1 e + on d.instance_id = e.instance_id + join projections.executions1_targets et + on e.instance_id = et.instance_id + and e.id = et.execution_id + join projections.targets2 t + on et.instance_id = t.instance_id + and et.target_id = t.id + order by et.position asc + ) as x + group by instance_id ) select i.id, @@ -41,11 +64,13 @@ select l.block, f.features, ed.domains as external_domains, - td.domains as trusted_domains + td.domains as trusted_domains, + et.execution_targets from domain d join projections.instances i on i.id = d.instance_id left join projections.security_policies2 s on i.id = s.instance_id left join projections.limits l on i.id = l.instance_id left join features f on i.id = f.instance_id left join external_domains ed on i.id = ed.instance_id -left join trusted_domains td on i.id = td.instance_id; +left join trusted_domains td on i.id = td.instance_id +left join execution_targets et on i.id = et.instance_id; diff --git a/internal/query/instance_by_id.sql b/internal/query/instance_by_id.sql index d4000b8d8e2..58bc92cac62 100644 --- a/internal/query/instance_by_id.sql +++ b/internal/query/instance_by_id.sql @@ -17,6 +17,28 @@ with features as ( from projections.instance_trusted_domains where instance_id = $1 group by instance_id +), execution_targets as ( + select instance_id, json_agg(x.execution_targets) as execution_targets from ( + select e.instance_id, json_build_object( + 'execution_id', et.execution_id, + 'target_id', t.id, + 'target_type', t.target_type, + 'endpoint', t.endpoint, + 'timeout', t.timeout, + 'interrupt_on_error', t.interrupt_on_error, + 'signing_key', t.signing_key + ) as execution_targets + from projections.executions1 e + join projections.executions1_targets et + on e.instance_id = et.instance_id + and e.id = et.execution_id + join projections.targets2 t + on et.instance_id = t.instance_id + and et.target_id = t.id + where e.instance_id = $1 + order by et.position asc + ) as x + group by instance_id ) select i.id, @@ -32,11 +54,13 @@ select l.block, f.features, ed.domains as external_domains, - td.domains as trusted_domains + td.domains as trusted_domains, + et.execution_targets from projections.instances i left join projections.security_policies2 s on i.id = s.instance_id left join projections.limits l on i.id = l.instance_id left join features f on i.id = f.instance_id left join external_domains ed on i.id = ed.instance_id left join trusted_domains td on i.id = td.instance_id +left join execution_targets et on i.id = et.instance_id where i.id = $1; diff --git a/internal/query/projection/target_test.go b/internal/query/projection/target_test.go index 6517e78f046..95c6befd033 100644 --- a/internal/query/projection/target_test.go +++ b/internal/query/projection/target_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" - "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/target" "github.com/zitadel/zitadel/internal/zerrors" @@ -51,7 +51,7 @@ func TestTargetProjection_reduces(t *testing.T) { uint64(15), "name", "https://example.com", - domain.TargetTypeWebhook, + target_domain.TargetTypeWebhook, 3 * time.Second, true, anyArg{}, @@ -86,7 +86,7 @@ func TestTargetProjection_reduces(t *testing.T) { uint64(15), "ro-id", "name2", - domain.TargetTypeWebhook, + target_domain.TargetTypeWebhook, "https://example.com", 3 * time.Second, true, diff --git a/internal/query/target.go b/internal/query/target.go index d9b50f4a14b..d5a464547c6 100644 --- a/internal/query/target.go +++ b/internal/query/target.go @@ -11,6 +11,7 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/query/projection" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -79,7 +80,7 @@ type Target struct { domain.ObjectDetails Name string - TargetType domain.TargetType + TargetType target_domain.TargetType Endpoint string Timeout time.Duration InterruptOnError bool diff --git a/internal/query/target_test.go b/internal/query/target_test.go index ef564bf236a..570b42ba17f 100644 --- a/internal/query/target_test.go +++ b/internal/query/target_test.go @@ -11,6 +11,7 @@ import ( "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" + target_domain "github.com/zitadel/zitadel/internal/execution/target" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -103,7 +104,7 @@ func Test_TargetPrepares(t *testing.T) { testNow, "ro", "target-name", - domain.TargetTypeWebhook, + target_domain.TargetTypeWebhook, 1 * time.Second, "https://example.com", true, @@ -130,7 +131,7 @@ func Test_TargetPrepares(t *testing.T) { ResourceOwner: "ro", }, Name: "target-name", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Timeout: 1 * time.Second, Endpoint: "https://example.com", InterruptOnError: true, @@ -158,7 +159,7 @@ func Test_TargetPrepares(t *testing.T) { testNow, "ro", "target-name1", - domain.TargetTypeWebhook, + target_domain.TargetTypeWebhook, 1 * time.Second, "https://example.com", true, @@ -175,7 +176,7 @@ func Test_TargetPrepares(t *testing.T) { testNow, "ro", "target-name2", - domain.TargetTypeWebhook, + target_domain.TargetTypeWebhook, 1 * time.Second, "https://example.com", false, @@ -192,7 +193,7 @@ func Test_TargetPrepares(t *testing.T) { testNow, "ro", "target-name3", - domain.TargetTypeAsync, + target_domain.TargetTypeAsync, 1 * time.Second, "https://example.com", false, @@ -219,7 +220,7 @@ func Test_TargetPrepares(t *testing.T) { ResourceOwner: "ro", }, Name: "target-name1", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Timeout: 1 * time.Second, Endpoint: "https://example.com", InterruptOnError: true, @@ -238,7 +239,7 @@ func Test_TargetPrepares(t *testing.T) { ResourceOwner: "ro", }, Name: "target-name2", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Timeout: 1 * time.Second, Endpoint: "https://example.com", InterruptOnError: false, @@ -257,7 +258,7 @@ func Test_TargetPrepares(t *testing.T) { ResourceOwner: "ro", }, Name: "target-name3", - TargetType: domain.TargetTypeAsync, + TargetType: target_domain.TargetTypeAsync, Timeout: 1 * time.Second, Endpoint: "https://example.com", InterruptOnError: false, @@ -319,7 +320,7 @@ func Test_TargetPrepares(t *testing.T) { testNow, "ro", "target-name", - domain.TargetTypeWebhook, + target_domain.TargetTypeWebhook, 1 * time.Second, "https://example.com", true, @@ -340,7 +341,7 @@ func Test_TargetPrepares(t *testing.T) { ResourceOwner: "ro", }, Name: "target-name", - TargetType: domain.TargetTypeWebhook, + TargetType: target_domain.TargetTypeWebhook, Timeout: 1 * time.Second, Endpoint: "https://example.com", InterruptOnError: true, diff --git a/internal/query/targets_by_execution_id.sql b/internal/query/targets_by_execution_id.sql deleted file mode 100644 index f3ee25d6757..00000000000 --- a/internal/query/targets_by_execution_id.sql +++ /dev/null @@ -1,40 +0,0 @@ -WITH RECURSIVE - matched AS (SELECT * - FROM projections.executions1 - WHERE instance_id = $1 - AND id = ANY($2) - ORDER BY id DESC - LIMIT 1), - matched_targets_and_includes AS (SELECT pos.* - FROM matched m - JOIN - projections.executions1_targets pos - ON m.id = pos.execution_id - AND m.instance_id = pos.instance_id - ORDER BY execution_id, - position), - dissolved_execution_targets(execution_id, instance_id, position, "include", "target_id") - AS (SELECT execution_id - , instance_id - , ARRAY [position] - , "include" - , "target_id" - FROM matched_targets_and_includes - UNION ALL - SELECT e.execution_id - , p.instance_id - , e.position || p.position - , p."include" - , p."target_id" - FROM dissolved_execution_targets e - JOIN projections.executions1_targets p - ON e.instance_id = p.instance_id - AND e.include IS NOT NULL - AND e.include = p.execution_id) -select e.execution_id, e.instance_id, e.target_id, t.target_type, t.endpoint, t.timeout, t.interrupt_on_error, t.signing_key -FROM dissolved_execution_targets e - JOIN projections.targets2 t - ON e.instance_id = t.instance_id - AND e.target_id = t.id -WHERE "include" = '' -ORDER BY position DESC; diff --git a/internal/query/targets_by_execution_ids.sql b/internal/query/targets_by_execution_ids.sql deleted file mode 100644 index 277826a81bf..00000000000 --- a/internal/query/targets_by_execution_ids.sql +++ /dev/null @@ -1,47 +0,0 @@ -WITH RECURSIVE - matched AS ((SELECT * - FROM projections.executions1 - WHERE instance_id = $1 - AND id = ANY($2) - ORDER BY id DESC - LIMIT 1) - UNION ALL - (SELECT * - FROM projections.executions1 - WHERE instance_id = $1 - AND id = ANY($3) - ORDER BY id DESC - LIMIT 1)), - matched_targets_and_includes AS (SELECT pos.* - FROM matched m - JOIN - projections.executions1_targets pos - ON m.id = pos.execution_id - AND m.instance_id = pos.instance_id - ORDER BY execution_id, - position), - dissolved_execution_targets(execution_id, instance_id, position, "include", "target_id") - AS (SELECT execution_id - , instance_id - , ARRAY [position] - , "include" - , "target_id" - FROM matched_targets_and_includes - UNION ALL - SELECT e.execution_id - , p.instance_id - , e.position || p.position - , p."include" - , p."target_id" - FROM dissolved_execution_targets e - JOIN projections.executions1_targets p - ON e.instance_id = p.instance_id - AND e.include IS NOT NULL - AND e.include = p.execution_id) -select e.execution_id, e.instance_id, e.target_id, t.target_type, t.endpoint, t.timeout, t.interrupt_on_error, t.signing_key -FROM dissolved_execution_targets e - JOIN projections.targets2 t - ON e.instance_id = t.instance_id - AND e.target_id = t.id -WHERE "include" = '' -ORDER BY position DESC; diff --git a/internal/queue/migrate.go b/internal/queue/migrate.go index 9af294cbe1a..8f77550847c 100644 --- a/internal/queue/migrate.go +++ b/internal/queue/migrate.go @@ -2,27 +2,27 @@ package queue import ( "context" + "database/sql" - "github.com/jackc/pgx/v5" "github.com/riverqueue/river/riverdriver" - "github.com/riverqueue/river/riverdriver/riverpgxv5" + "github.com/riverqueue/river/riverdriver/riverdatabasesql" "github.com/riverqueue/river/rivermigrate" "github.com/zitadel/zitadel/internal/database" ) type Migrator struct { - driver riverdriver.Driver[pgx.Tx] + driver riverdriver.Driver[*sql.Tx] } func NewMigrator(client *database.DB) *Migrator { return &Migrator{ - driver: riverpgxv5.New(client.Pool), + driver: riverdatabasesql.New(client.DB), } } func (m *Migrator) Execute(ctx context.Context) error { - _, err := m.driver.GetExecutor().Exec(ctx, "CREATE SCHEMA IF NOT EXISTS "+schema) + err := m.driver.GetExecutor().Exec(ctx, "CREATE SCHEMA IF NOT EXISTS "+schema) if err != nil { return err } diff --git a/internal/queue/queue.go b/internal/queue/queue.go index 6db5e0ec2a4..3fde54aa3c1 100644 --- a/internal/queue/queue.go +++ b/internal/queue/queue.go @@ -2,11 +2,11 @@ package queue import ( "context" + "database/sql" - "github.com/jackc/pgx/v5" "github.com/riverqueue/river" "github.com/riverqueue/river/riverdriver" - "github.com/riverqueue/river/riverdriver/riverpgxv5" + "github.com/riverqueue/river/riverdriver/riverdatabasesql" "github.com/riverqueue/river/rivertype" "github.com/riverqueue/rivercontrib/otelriver" "github.com/robfig/cron/v3" @@ -19,8 +19,8 @@ import ( // Queue abstracts the underlying queuing library // For more information see github.com/riverqueue/river type Queue struct { - driver riverdriver.Driver[pgx.Tx] - client *river.Client[pgx.Tx] + driver riverdriver.Driver[*sql.Tx] + client *river.Client[*sql.Tx] config *river.Config shouldStart bool @@ -35,7 +35,7 @@ func NewQueue(config *Config) (_ *Queue, err error) { MeterProvider: metrics.GetMetricsProvider(), })} return &Queue{ - driver: riverpgxv5.New(config.Client.Pool), + driver: riverdatabasesql.New(config.Client.DB), config: &river.Config{ Workers: river.NewWorkers(), Queues: make(map[string]river.QueueConfig), @@ -111,12 +111,33 @@ func WithQueueName(name string) InsertOpt { } func (q *Queue) Insert(ctx context.Context, args river.JobArgs, opts ...InsertOpt) error { + _, err := q.client.Insert(ctx, args, applyInsertOpts(opts)) + return err +} + +// InsertManyFastTx wraps [river.Client.InsertManyFastTx] to insert all jobs in +// a single `COPY FROM` execution, within the existing transaction. +// +// Opts are applied to each job before sending them to river. +func (q *Queue) InsertManyFastTx(ctx context.Context, tx *sql.Tx, args []river.JobArgs, opts ...InsertOpt) error { + params := make([]river.InsertManyParams, len(args)) + for i, arg := range args { + params[i] = river.InsertManyParams{ + Args: arg, + InsertOpts: applyInsertOpts(opts), + } + } + + _, err := q.client.InsertManyFastTx(ctx, tx, params) + return err +} + +func applyInsertOpts(opts []InsertOpt) *river.InsertOpts { options := new(river.InsertOpts) for _, opt := range opts { opt(options) } - _, err := q.client.Insert(ctx, args, options) - return err + return options } type Worker interface { diff --git a/internal/repository/execution/queue.go b/internal/repository/execution/queue.go index ed3a6ce4a08..7cce61b0613 100644 --- a/internal/repository/execution/queue.go +++ b/internal/repository/execution/queue.go @@ -5,6 +5,7 @@ import ( "time" "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/execution/target" ) const ( @@ -21,6 +22,26 @@ type Request struct { TargetsData []byte `json:"targetsData"` } +func NewRequest(e eventstore.Event, targets []target.Target) (*Request, error) { + targetsData, err := json.Marshal(targets) + if err != nil { + return nil, err + } + eventData, err := json.Marshal(e) + if err != nil { + return nil, err + } + return &Request{ + Aggregate: e.Aggregate(), + Sequence: e.Sequence(), + EventType: e.Type(), + CreatedAt: e.CreatedAt(), + UserID: e.Creator(), + EventData: eventData, + TargetsData: targetsData, + }, nil +} + func (e *Request) Kind() string { return "execution_request" } diff --git a/internal/repository/target/target.go b/internal/repository/target/target.go index 3df1b314808..63c72728686 100644 --- a/internal/repository/target/target.go +++ b/internal/repository/target/target.go @@ -5,8 +5,8 @@ import ( "time" "github.com/zitadel/zitadel/internal/crypto" - "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" + target_domain "github.com/zitadel/zitadel/internal/execution/target" ) const ( @@ -19,12 +19,12 @@ const ( type AddedEvent struct { eventstore.BaseEvent `json:"-"` - Name string `json:"name"` - TargetType domain.TargetType `json:"targetType"` - Endpoint string `json:"endpoint"` - Timeout time.Duration `json:"timeout"` - InterruptOnError bool `json:"interruptOnError"` - SigningKey *crypto.CryptoValue `json:"signingKey"` + Name string `json:"name"` + TargetType target_domain.TargetType `json:"targetType"` + Endpoint string `json:"endpoint"` + Timeout time.Duration `json:"timeout"` + InterruptOnError bool `json:"interruptOnError"` + SigningKey *crypto.CryptoValue `json:"signingKey"` } func (e *AddedEvent) SetBaseEvent(b *eventstore.BaseEvent) { @@ -43,7 +43,7 @@ func NewAddedEvent( ctx context.Context, aggregate *eventstore.Aggregate, name string, - targetType domain.TargetType, + targetType target_domain.TargetType, endpoint string, timeout time.Duration, interruptOnError bool, @@ -59,12 +59,12 @@ func NewAddedEvent( type ChangedEvent struct { eventstore.BaseEvent `json:"-"` - Name *string `json:"name,omitempty"` - TargetType *domain.TargetType `json:"targetType,omitempty"` - Endpoint *string `json:"endpoint,omitempty"` - Timeout *time.Duration `json:"timeout,omitempty"` - InterruptOnError *bool `json:"interruptOnError,omitempty"` - SigningKey *crypto.CryptoValue `json:"signingKey,omitempty"` + Name *string `json:"name,omitempty"` + TargetType *target_domain.TargetType `json:"targetType,omitempty"` + Endpoint *string `json:"endpoint,omitempty"` + Timeout *time.Duration `json:"timeout,omitempty"` + InterruptOnError *bool `json:"interruptOnError,omitempty"` + SigningKey *crypto.CryptoValue `json:"signingKey,omitempty"` oldName string } @@ -114,7 +114,7 @@ func ChangeName(oldName, name string) func(event *ChangedEvent) { } } -func ChangeTargetType(targetType domain.TargetType) func(event *ChangedEvent) { +func ChangeTargetType(targetType target_domain.TargetType) func(event *ChangedEvent) { return func(e *ChangedEvent) { e.TargetType = &targetType }