From 39c35c94555d8edc54bd300c20df76edab91dbb6 Mon Sep 17 00:00:00 2001 From: Silvan Date: Wed, 29 Sep 2021 13:20:57 +0200 Subject: [PATCH] feat(queries): use org projection (#2342) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * job queue * wg improvements * start handler * statement * statements * imporve handler * improve statement * statement in seperate file * move handlers * move query/old to query * handler * read models * bulk works * cleanup * contrib * rename readmodel to projection * rename read_models schema to projections * rename read_models schema to projections * search query as func, bulk iterates as long as new events * add event sequence less query * update checks for events between current sequence and sequence of first statement if it has previous sequence 0 * cleanup crdb projection * refactor projection handler * start with testing * tests for handler * remove todo * refactor statement: remove table name, add tests * improve projection handler shutdown, no savepoint if noop stmt, tests for stmt handler * tests * start failed events * seperate branch for contrib * move statement constructors to crdb pkg * correct import * Subscribe for eventtypes (#1800) * fix: is default (#1737) * fix: use email as username on global org (#1738) * fix: use email as username on global org * Update user_human.go * Update register_handler.go * chore(deps): update docusaurus (#1739) * chore: remove PAT and use GH Token (#1716) * chore: remove PAT and use GH Token * fix env * fix env * fix env * md lint * trigger ci * change user * fix GH bug * replace login part * chore: add GH Token to sem rel (#1746) * chore: add GH Token to sem rel * try branch * add GH Token * remove test branch again * docs: changes acme to acme-caos (#1744) * changes acme to acme-caos * Apply suggestions from code review Co-authored-by: Florian Forster Co-authored-by: Maximilian Panne Co-authored-by: Florian Forster * feat: add additional origins on applications (#1691) * feat: add additional origins on applications * app additional redirects * chore(deps-dev): bump @angular/cli from 11.2.8 to 11.2.11 in /console (#1706) * fix: show org with regex (#1688) * fix: flag mapping (#1699) * chore(deps-dev): bump @angular/cli from 11.2.8 to 11.2.11 in /console Bumps [@angular/cli](https://github.com/angular/angular-cli) from 11.2.8 to 11.2.11. - [Release notes](https://github.com/angular/angular-cli/releases) - [Commits](https://github.com/angular/angular-cli/compare/v11.2.8...v11.2.11) Signed-off-by: dependabot[bot] Co-authored-by: Max Peintner Co-authored-by: Silvan Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump stylelint from 13.10.0 to 13.13.1 in /console (#1703) * fix: show org with regex (#1688) * fix: flag mapping (#1699) * chore(deps-dev): bump stylelint from 13.10.0 to 13.13.1 in /console Bumps [stylelint](https://github.com/stylelint/stylelint) from 13.10.0 to 13.13.1. - [Release notes](https://github.com/stylelint/stylelint/releases) - [Changelog](https://github.com/stylelint/stylelint/blob/master/CHANGELOG.md) - [Commits](https://github.com/stylelint/stylelint/compare/13.10.0...13.13.1) Signed-off-by: dependabot[bot] Co-authored-by: Max Peintner Co-authored-by: Silvan Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @types/node from 14.14.37 to 15.0.1 in /console (#1702) * fix: show org with regex (#1688) * fix: flag mapping (#1699) * chore(deps-dev): bump @types/node from 14.14.37 to 15.0.1 in /console Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.37 to 15.0.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Signed-off-by: dependabot[bot] Co-authored-by: Max Peintner Co-authored-by: Silvan Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump ts-protoc-gen from 0.14.0 to 0.15.0 in /console (#1701) * fix: show org with regex (#1688) * fix: flag mapping (#1699) * chore(deps): bump ts-protoc-gen from 0.14.0 to 0.15.0 in /console Bumps [ts-protoc-gen](https://github.com/improbable-eng/ts-protoc-gen) from 0.14.0 to 0.15.0. - [Release notes](https://github.com/improbable-eng/ts-protoc-gen/releases) - [Changelog](https://github.com/improbable-eng/ts-protoc-gen/blob/master/CHANGELOG.md) - [Commits](https://github.com/improbable-eng/ts-protoc-gen/compare/0.14.0...0.15.0) Signed-off-by: dependabot[bot] Co-authored-by: Max Peintner Co-authored-by: Silvan Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @types/jasmine from 3.6.9 to 3.6.10 in /console (#1682) Bumps [@types/jasmine](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jasmine) from 3.6.9 to 3.6.10. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jasmine) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump @types/google-protobuf in /console (#1681) Bumps [@types/google-protobuf](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/google-protobuf) from 3.7.4 to 3.15.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/google-protobuf) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump grpc from 1.24.5 to 1.24.7 in /console (#1666) Bumps [grpc](https://github.com/grpc/grpc-node) from 1.24.5 to 1.24.7. - [Release notes](https://github.com/grpc/grpc-node/releases) - [Commits](https://github.com/grpc/grpc-node/compare/grpc@1.24.5...grpc@1.24.7) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * lock * chore(deps-dev): bump @angular/language-service from 11.2.9 to 11.2.12 in /console (#1704) * fix: show org with regex (#1688) * fix: flag mapping (#1699) * chore(deps-dev): bump @angular/language-service in /console Bumps [@angular/language-service](https://github.com/angular/angular/tree/HEAD/packages/language-service) from 11.2.9 to 11.2.12. - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/11.2.12/packages/language-service) Signed-off-by: dependabot[bot] Co-authored-by: Max Peintner Co-authored-by: Silvan Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * package lock * downgrade grpc * downgrade protobuf types * revert npm packs 🥸 Co-authored-by: Max Peintner Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Silvan * docs: update run and start section texts (#1745) * update run and start section texts * adds showcase Co-authored-by: Maximilian Panne * fix: additional origin list (#1753) * fix: handle api configs in authz handler (#1755) * fix(console): add model for api keys, fix toast, binding (#1757) * fix: add model for api keys, fix toast, binding * show api clientid * fix: missing patchvalue (#1758) * feat: refresh token (#1728) * begin refresh tokens * refresh tokens * list and revoke refresh tokens * handle remove * tests for refresh tokens * uniqueness and default expiration * rename oidc token methods * cleanup * migration version * Update internal/static/i18n/en.yaml Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> * fixes * feat: update oidc pkg for refresh tokens Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> * fix: correct json name of clientId in key.json (#1760) * fix: migration version (#1767) * start subscription * eventtypes * fix(login): links (#1778) * fix(login): href for help * fix(login): correct link to tos * fix: access tokens for service users and refresh token infos (#1779) * fix: access token for service user * handle info from refresh request * uniqueness * postpone access token uniqueness change * chore(coc): recommend code of conduct (#1782) * subscribe for events * feat(console): refresh toggle out of granttype context (#1785) * refresh toggle * disable if not code flow, lint * lint * fix: change oidc config order * accept refresh option within flow Co-authored-by: Livio Amstutz * fix: refresh token activation (#1795) * fix: oidc grant type check * docs: add offline_access scope * docs: update refresh token status in supported grant types * fix: update oidc pkg * fix: check refresh token grant type (#1796) * configuration structs * org admins * failed events * fixes Co-authored-by: Max Peintner Co-authored-by: Livio Amstutz Co-authored-by: Florian Forster Co-authored-by: mffap Co-authored-by: Maximilian Panne Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> * remove comment * aggregate reducer * remove eventtypes * add protoc-get-validate to mod * fix transaltion * upsert * add gender on org admins, allow to retry failed stmts after configurable time * remove if * sub queries * fix: tests * add builder to tests * new search query * rename searchquerybuilder to builder * remove comment from code * test with multiple queries * add filters test * current sequences * make org and org_admins work again * add aggregate type to current sequence * fix(contibute): listing * add validate module * fix: search queries * feat(eventstore): previous aggregate root sequence (#1810) * feat(eventstore): previous aggregate root sequence * fix tests * fix: eventstore v1 test * add col to all mocked rows * next try * fix mig * rename aggregate root to aggregate type * update comment Co-authored-by: Livio Amstutz Co-authored-by: Livio Amstutz * small refactorings * allow update multiple current sequences * unique log id * fix migrations * rename org admin to org owner * improve error handling and logging * fix(migration): optimize prev agg root seq * fix: projection handler test * fix: sub queries * small fixes * additional event types * correct org owner projection * fix primary key * feat(eventstore): jobs for projections (#2026) * fix: template names in login (#1974) * fix: template names in login * fix: error.html * fix: check for features on mgmt only (#1976) * fix: add sentry in ui, http and projection handlers (#1977) * fix: add sentry in ui, http and projection handlers * fix test * fix(eventstore): sub queries (#1805) * sub queries * fix: tests * add builder to tests * new search query * rename searchquerybuilder to builder * remove comment from code * test with multiple queries * add filters test * fix(contibute): listing * add validate module * fix: search queries * remove unused event type in query * ignore query if error in marshal * go mod tidy * update privacy policy query * update queries Co-authored-by: Livio Amstutz * feat: Extend oidc idp with oauth endpoints (#1980) * feat: add oauth attributes to oidc idp configuration * feat: return idpconfig id on create idp * feat: tests * feat: descriptions * feat: docs * feat: tests * docs: update to beta 3 (#1984) * fix: role assertion (#1986) * fix: enum to display access token role assertion * improve assertion descriptions * fix nil pointer * docs: eventstore (#1982) * docs: eventstore * Apply suggestions from code review Co-authored-by: Florian Forster Co-authored-by: Florian Forster * fix(sentry): trigger sentry release (#1989) * feat(send sentry release): send sentry release * fix(moved step and added releasetag): moved step and added releasetag * fix: set version for sentry release (#1990) * feat(send sentry release): send sentry release * fix(moved step and added releasetag): moved step and added releasetag * fix(corrected var name): corrected var name Co-authored-by: Livio Amstutz * fix: log error reason on terminate session (#1973) * fix: return default language file, if requested lang does not exist for default login texts (#1988) * fix: return default language file, if requested lang doesnt exists * feat: read default translation file * feat: docs * fix: race condition in auth request unmarshalling (#1993) * feat: handle ui_locales in login (#1994) * fix: handle ui_locales in login * move supportedlanguage func into i18n package * update oidc pkg * fix: handle closed channels on unsubscribe (#1995) * fix: give restore more time (#1997) * fix: translation file read (#2009) * feat: translation file read * feat: readme * fix: enable idp add button for iam users (#2010) * fix: filter event_data (#2011) * feat: Custom message files (#1992) * feat: add get custom message text to admin api * feat: read custom message texts from files * feat: get languages in apis * feat: get languages in apis * feat: get languages in apis * feat: pr feedback * feat: docs * feat: merge main * fix: sms notification (#2013) * fix: phone verifications * feat: fix password reset as sms * fix: phone verification * fix: grpc status in sentry and validation interceptors (#2012) * fix: remove oauth endpoints from oidc config proto (#2014) * try with view * fix(console): disable sw (#2021) * fix: disable sw * angular.json disable sw * project projections * fix typos * customize projections * customizable projections, add change date to projects Co-authored-by: Livio Amstutz Co-authored-by: Max Peintner Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Florian Forster Co-authored-by: mffap Co-authored-by: Christian Jakob <47860090+thesephirot@users.noreply.github.com> Co-authored-by: Elio Bischof * env file * typo * correct users * correct migration * fix: merge fail * fix test * fix(tests): unordered matcher * improve currentSequenceMatcher * correct certs * correct certs * add zitadel database on database list * refctor switch in match * enable all handlers * Delete io.env * cleanup * add handlers * rename view to projection * rename view to projection * fix type typo * remove unnecessary logs * refactor stmts * simplify interval calculation * fix tests * fix unlock test * fix migration * migs * fix(operator): update cockroach and flyway versions (#2138) * chore(deps): bump k8s.io/apiextensions-apiserver from 0.19.2 to 0.21.3 Bumps [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) from 0.19.2 to 0.21.3. - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.19.2...v0.21.3) --- updated-dependencies: - dependency-name: k8s.io/apiextensions-apiserver dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump google.golang.org/api from 0.34.0 to 0.52.0 Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.34.0 to 0.52.0. - [Release notes](https://github.com/googleapis/google-api-go-client/releases) - [Changelog](https://github.com/googleapis/google-api-go-client/blob/master/CHANGES.md) - [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.34.0...v0.52.0) --- updated-dependencies: - dependency-name: google.golang.org/api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * start update dependencies * update mods and otlp * fix(build): update to go 1.16 * old version for k8s mods * update k8s versions * update orbos * fix(operator): update cockroach and flyway version * Update images.go Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stefan Benz * fix import * fix typo * fix(migration): add org projection * fix(projection): correct table for org events in org owners * better insert stmt * fix typo * fix typo * set max connection lifetime * set max conns and conn lifetime in eventstore v1 * configure sql connection settings * add mig for agg type index * fix replace tab in yaml * handler interfaces * subscription * first try * handler * move sql client initialization * first part implemented * removed all occurencies of org by id and search orgs * fix merge issues * cleanup code * fix: queries implements orgviewprovider * cleanup * refactor text comparison * remove unused file * remove unused code * log * remove unused code * remove unused field * remove unused file * refactor * tests for search query * remove try * simplify state change mappers * projection tests * query functions * move reusable objects to separate files * rename domain column to primar_domain * fix tests * add current sequence * remove log prints * fix tests * fix: verifier * fix test * rename domain col migrations * simplify search response * add custom column constructors * fix: org projection table const * fix: full column name * feat: text query extension * fix: tests for query * number query * add deprection message * column in a single place (#2416) * column in a single place * use projection for columns * query column with aliases * rename methods * remove unused code * column for current sequences * global counter column * fix is org unique * fix: merge main and change actions / flow projections to new query side (#2434) * feat: actions (#2377) * feat(actions): begin api * feat(actions): begin api * api and projections * fix: handle multiple statements for a single event in projections * export func type * fix test * update to new reduce interface * flows in login * feat: jwt idp * feat: command side * feat: add tests * actions and flows * fill idp views with jwt idps and return apis * add jwtEndpoint to jwt idp * begin jwt request handling * add feature * merge * merge * handle jwt idp * cleanup * bug fixes * autoregister * get token from specific header name * fix: proto * fixes * i18n * begin tests * fix and log http proxy * remove docker cache * fixes * usergrants in actions api * tests adn cleanup * cleanup * fix add user grant * set login context * i18n Co-authored-by: fabi * change actions / flow projections to new query side * fixes * enable org projection Co-authored-by: fabi * fixes * cleanup * add tests Co-authored-by: Max Peintner Co-authored-by: Livio Amstutz Co-authored-by: Florian Forster Co-authored-by: mffap Co-authored-by: Maximilian Panne Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Christian Jakob <47860090+thesephirot@users.noreply.github.com> Co-authored-by: Elio Bischof Co-authored-by: Stefan Benz Co-authored-by: fabi --- build/local/docker-compose-local.yml | 6 +- cmd/zitadel/main.go | 12 +- cmd/zitadel/startup.yaml | 1 + docs/docs/apis/proto/admin.md | 24 +- .../eventsourcing/eventstore/org.go | 102 +-- .../eventsourcing/handler/handler.go | 9 +- .../repository/eventsourcing/handler/org.go | 103 --- .../repository/eventsourcing/repository.go | 4 +- .../repository/eventsourcing/view/org.go | 49 -- internal/admin/repository/org.go | 6 - internal/api/api.go | 14 +- internal/api/grpc/admin/org.go | 22 +- internal/api/grpc/admin/org_converter.go | 25 +- internal/api/grpc/management/actions.go | 10 +- .../api/grpc/management/actions_converter.go | 4 +- internal/api/grpc/management/flow.go | 2 +- internal/api/grpc/management/org.go | 5 +- internal/api/grpc/object/converter.go | 65 +- internal/api/grpc/org/converter.go | 33 +- internal/api/oidc/client.go | 12 +- .../eventsourcing/eventstore/auth_request.go | 15 +- .../eventstore/auth_request_test.go | 82 +- .../eventsourcing/eventstore/org.go | 35 - .../eventsourcing/eventstore/user.go | 15 +- .../eventsourcing/eventstore/user_grant.go | 33 +- .../eventsourcing/handler/handler.go | 2 - .../repository/eventsourcing/handler/org.go | 111 --- .../repository/eventsourcing/repository.go | 3 +- .../auth/repository/eventsourcing/view/org.go | 53 -- internal/auth/repository/org.go | 3 +- internal/auth/repository/user.go | 1 - .../eventstore/token_verifier.go | 5 - .../eventsourcing/handler/handler.go | 2 - .../repository/eventsourcing/handler/org.go | 103 --- .../repository/eventsourcing/view/org.go | 49 -- internal/command/command.go | 18 +- internal/command/main_test.go | 6 +- internal/command/org_policy_label.go | 3 +- internal/command/org_policy_label_test.go | 5 +- internal/command/org_policy_login.go | 3 +- internal/command/org_policy_login_test.go | 5 +- internal/eventstore/handler/crdb/statement.go | 24 +- .../eventstore/handler/crdb/statement_test.go | 43 + internal/eventstore/handler/statement.go | 23 +- .../eventsourcing/eventstore/org.go | 109 ++- .../eventsourcing/eventstore/user.go | 35 +- .../eventsourcing/handler/handler.go | 2 - .../repository/eventsourcing/handler/org.go | 99 --- .../repository/eventsourcing/view/org.go | 44 - internal/management/repository/org.go | 2 - internal/org/repository/view/org_view.go | 50 -- internal/query/action.go | 244 ++++-- internal/query/action_flow.go | 297 ++++--- internal/query/current_sequence.go | 75 ++ internal/query/org.go | 252 ++++++ internal/query/projection/action.go | 102 +-- internal/query/projection/action_test.go | 178 ++++ internal/query/projection/config.go | 1 + internal/query/projection/flow.go | 93 +++ internal/query/projection/flow_test.go | 93 +++ internal/query/projection/org.go | 89 +- .../query/projection/org/owner/projection.go | 20 +- internal/query/projection/org_test.go | 196 +++++ internal/query/projection/project.go | 2 +- internal/query/projection/projection.go | 36 +- internal/query/projection/test_event.go | 87 ++ internal/query/projection/test_executer.go | 48 ++ internal/query/query.go | 25 +- internal/query/search_query.go | 254 ++++-- internal/query/search_query_test.go | 767 ++++++++++++++++++ internal/repository/flow/flow.go | 14 +- internal/repository/org/org.go | 20 +- internal/static/i18n/de.yaml | 4 + internal/static/i18n/en.yaml | 4 + internal/ui/login/handler/custom_action.go | 36 +- .../login/handler/external_login_handler.go | 6 +- internal/ui/login/handler/jwt_handler.go | 7 +- internal/ui/login/handler/login.go | 7 +- .../cockroach/V1.73__fix_projections.sql | 9 + proto/zitadel/admin.proto | 52 +- 80 files changed, 2960 insertions(+), 1549 deletions(-) delete mode 100644 internal/admin/repository/eventsourcing/handler/org.go delete mode 100644 internal/admin/repository/eventsourcing/view/org.go delete mode 100644 internal/auth/repository/eventsourcing/handler/org.go delete mode 100644 internal/auth/repository/eventsourcing/view/org.go delete mode 100644 internal/authz/repository/eventsourcing/handler/org.go delete mode 100644 internal/authz/repository/eventsourcing/view/org.go delete mode 100644 internal/management/repository/eventsourcing/handler/org.go delete mode 100644 internal/management/repository/eventsourcing/view/org.go delete mode 100644 internal/org/repository/view/org_view.go create mode 100644 internal/query/current_sequence.go create mode 100644 internal/query/org.go create mode 100644 internal/query/projection/action_test.go create mode 100644 internal/query/projection/flow.go create mode 100644 internal/query/projection/flow_test.go create mode 100644 internal/query/projection/org_test.go create mode 100644 internal/query/projection/test_event.go create mode 100644 internal/query/projection/test_executer.go create mode 100644 internal/query/search_query_test.go create mode 100644 migrations/cockroach/V1.73__fix_projections.sql diff --git a/build/local/docker-compose-local.yml b/build/local/docker-compose-local.yml index ea676f1c8f..78fa463406 100644 --- a/build/local/docker-compose-local.yml +++ b/build/local/docker-compose-local.yml @@ -6,7 +6,7 @@ services: restart: always networks: - zitadel - image: cockroachdb/cockroach:v21.1.0 + image: cockroachdb/cockroach:v21.1.7 command: start-single-node --insecure --listen-addr=0.0.0.0 ports: - 8080:8080 @@ -155,7 +155,7 @@ services: ports: - '50000:8080' environment: - - BKD_HOST=backend-run + - BKD_HOST=host.docker.internal - BKD_PORT=50001 frontend-local-run: @@ -187,7 +187,7 @@ services: volumes: - ./environment.json:/environment.json environment: - - HOST=backend-run + - HOST=host.docker.internal - PORT=50002 networks: diff --git a/cmd/zitadel/main.go b/cmd/zitadel/main.go index 4c4aacf444..5c7ab946a9 100644 --- a/cmd/zitadel/main.go +++ b/cmd/zitadel/main.go @@ -179,7 +179,15 @@ func startZitadel(configPaths []string) { logging.Log("MAIN-9oRw6").OnError(err).Fatal("error starting auth repo") } - verifier := internal_authz.Start(authZRepo) + repo := struct { + authz_repo.EsRepository + query.Queries + }{ + *authZRepo, + *queries, + } + + verifier := internal_authz.Start(&repo) startAPI(ctx, conf, verifier, authZRepo, authRepo, commands, queries, store) startUI(ctx, conf, authRepo, commands, queries, store) @@ -213,7 +221,7 @@ func startAPI(ctx context.Context, conf *Config, verifier *internal_authz.TokenV repo, err := admin_es.Start(ctx, conf.Admin, conf.SystemDefaults, command, static, roles, *localDevMode) logging.Log("API-D42tq").OnError(err).Fatal("error starting auth repo") - apis := api.Create(conf.API, conf.InternalAuthZ, authZRepo, authRepo, repo, conf.SystemDefaults) + apis := api.Create(conf.API, conf.InternalAuthZ, query, authZRepo, authRepo, repo, conf.SystemDefaults) if *adminEnabled { apis.RegisterServer(ctx, admin.CreateServer(command, query, repo, conf.SystemDefaults.Domain)) diff --git a/cmd/zitadel/startup.yaml b/cmd/zitadel/startup.yaml index a0e34e0b5b..414122a286 100644 --- a/cmd/zitadel/startup.yaml +++ b/cmd/zitadel/startup.yaml @@ -71,6 +71,7 @@ Projections: RetryFailedAfter: 1s MaxFailureCount: 5 BulkLimit: 200 + MaxIterators: 1 CRDB: Host: $ZITADEL_EVENTSTORE_HOST Port: $ZITADEL_EVENTSTORE_PORT diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md index 3faedaa518..7f2e0355eb 100644 --- a/docs/docs/apis/proto/admin.md +++ b/docs/docs/apis/proto/admin.md @@ -32,18 +32,6 @@ Returns the default languages GET: /languages -### IsOrgUnique - -> **rpc** IsOrgUnique([IsOrgUniqueRequest](#isorguniquerequest)) -[IsOrgUniqueResponse](#isorguniqueresponse) - -Checks whether an organisation exists by the given parameters - - - - GET: /orgs/_is_unique - - ### GetOrgByID > **rpc** GetOrgByID([GetOrgByIDRequest](#getorgbyidrequest)) @@ -56,6 +44,18 @@ Returns an organisation by id GET: /orgs/{id} +### IsOrgUnique + +> **rpc** IsOrgUnique([IsOrgUniqueRequest](#isorguniquerequest)) +[IsOrgUniqueResponse](#isorguniqueresponse) + +Checks whether an organisation exists by the given parameters + + + + GET: /orgs/_is_unique + + ### ListOrgs > **rpc** ListOrgs([ListOrgsRequest](#listorgsrequest)) diff --git a/internal/admin/repository/eventsourcing/eventstore/org.go b/internal/admin/repository/eventsourcing/eventstore/org.go index 944eb6eda2..44a5a5f370 100644 --- a/internal/admin/repository/eventsourcing/eventstore/org.go +++ b/internal/admin/repository/eventsourcing/eventstore/org.go @@ -3,98 +3,19 @@ package eventstore import ( "context" - "github.com/caos/zitadel/internal/errors" - v1 "github.com/caos/zitadel/internal/eventstore/v1" - "github.com/caos/zitadel/internal/eventstore/v1/models" - es_sdk "github.com/caos/zitadel/internal/eventstore/v1/sdk" - iam_model "github.com/caos/zitadel/internal/iam/model" - org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" - "github.com/caos/zitadel/internal/org/repository/view" - "github.com/caos/zitadel/internal/telemetry/tracing" - - "github.com/caos/logging" - admin_view "github.com/caos/zitadel/internal/admin/repository/eventsourcing/view" "github.com/caos/zitadel/internal/config/systemdefaults" + "github.com/caos/zitadel/internal/errors" + iam_model "github.com/caos/zitadel/internal/iam/model" iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model" - org_model "github.com/caos/zitadel/internal/org/model" - "github.com/caos/zitadel/internal/org/repository/view/model" ) type OrgRepo struct { - Eventstore v1.Eventstore - View *admin_view.View - SearchLimit uint64 SystemDefaults systemdefaults.SystemDefaults } -func (repo *OrgRepo) OrgByID(ctx context.Context, id string) (*org_model.OrgView, error) { - org, viewErr := repo.View.OrgByID(id) - if viewErr != nil && !errors.IsNotFound(viewErr) { - return nil, viewErr - } - if errors.IsNotFound(viewErr) { - org = new(model.OrgView) - } - - events, esErr := repo.getOrgEvents(ctx, id, org.Sequence) - if errors.IsNotFound(viewErr) && len(events) == 0 { - return nil, errors.ThrowNotFound(nil, "EVENT-Lsoj7", "Errors.Org.NotFound") - } - if esErr != nil { - logging.Log("EVENT-PSoc3").WithError(esErr).Debug("error retrieving new events") - return model.OrgToModel(org), nil - } - orgCopy := *org - for _, event := range events { - if err := orgCopy.AppendEvent(event); err != nil { - return model.OrgToModel(&orgCopy), nil - } - } - return model.OrgToModel(&orgCopy), nil -} - -func (repo *OrgRepo) SearchOrgs(ctx context.Context, query *org_model.OrgSearchRequest) (*org_model.OrgSearchResult, error) { - err := query.EnsureLimit(repo.SearchLimit) - if err != nil { - return nil, err - } - sequence, err := repo.View.GetLatestOrgSequence() - logging.Log("EVENT-LXo9w").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Warn("could not read latest iam sequence") - orgs, count, err := repo.View.SearchOrgs(query) - if err != nil { - return nil, err - } - result := &org_model.OrgSearchResult{ - Offset: query.Offset, - Limit: query.Limit, - TotalResult: count, - Result: model.OrgsToModel(orgs), - } - if err == nil { - result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.LastSuccessfulSpoolerRun - } - return result, nil -} - -func (repo *OrgRepo) IsOrgUnique(ctx context.Context, name, domain string) (isUnique bool, err error) { - var found bool - err = es_sdk.Filter(ctx, repo.Eventstore.FilterEvents, isUniqueValidation(&found), view.OrgNameUniqueQuery(name)) - if (err != nil && !errors.IsNotFound(err)) || found { - return false, err - } - - err = es_sdk.Filter(ctx, repo.Eventstore.FilterEvents, isUniqueValidation(&found), view.OrgDomainUniqueQuery(domain)) - if err != nil && !errors.IsNotFound(err) { - return false, err - } - - return !found, nil -} - func (repo *OrgRepo) GetOrgIAMPolicyByID(ctx context.Context, id string) (*iam_model.OrgIAMPolicyView, error) { policy, err := repo.View.OrgIAMPolicyByAggregateID(id) if errors.IsNotFound(err) { @@ -114,22 +35,3 @@ func (repo *OrgRepo) GetDefaultOrgIAMPolicy(ctx context.Context) (*iam_model.Org policy.Default = true return iam_es_model.OrgIAMViewToModel(policy), err } - -func (repo *OrgRepo) getOrgEvents(ctx context.Context, orgID string, sequence uint64) ([]*models.Event, error) { - query, err := view.OrgByIDQuery(orgID, sequence) - if err != nil { - return nil, err - } - return repo.Eventstore.FilterEvents(ctx, query) -} - -func isUniqueValidation(unique *bool) func(events ...*models.Event) error { - return func(events ...*models.Event) error { - if len(events) == 0 { - return nil - } - *unique = *unique || events[0].Type == org_es_model.OrgDomainReserved || events[0].Type == org_es_model.OrgNameReserved - - return nil - } -} diff --git a/internal/admin/repository/eventsourcing/handler/handler.go b/internal/admin/repository/eventsourcing/handler/handler.go index 8ec2e3155a..6d21bb66b3 100644 --- a/internal/admin/repository/eventsourcing/handler/handler.go +++ b/internal/admin/repository/eventsourcing/handler/handler.go @@ -3,14 +3,13 @@ package handler import ( "time" - "github.com/caos/zitadel/internal/command" - v1 "github.com/caos/zitadel/internal/eventstore/v1" - "github.com/caos/zitadel/internal/static" - "github.com/caos/zitadel/internal/admin/repository/eventsourcing/view" + "github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/types" + v1 "github.com/caos/zitadel/internal/eventstore/v1" "github.com/caos/zitadel/internal/eventstore/v1/query" + "github.com/caos/zitadel/internal/static" ) type Configs map[string]*Config @@ -34,8 +33,6 @@ func (h *handler) Eventstore() v1.Eventstore { func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, defaults systemdefaults.SystemDefaults, command *command.Commands, static static.Storage, localDevMode bool) []query.Handler { handlers := []query.Handler{ - newOrg( - handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount, es}), newIAMMember( handler{view, bulkLimit, configs.cycleDuration("IamMember"), errorCount, es}), newIDPConfig( diff --git a/internal/admin/repository/eventsourcing/handler/org.go b/internal/admin/repository/eventsourcing/handler/org.go deleted file mode 100644 index be3682a966..0000000000 --- a/internal/admin/repository/eventsourcing/handler/org.go +++ /dev/null @@ -1,103 +0,0 @@ -package handler - -import ( - "github.com/caos/logging" - "github.com/caos/zitadel/internal/eventstore/v1" - "github.com/caos/zitadel/internal/org/repository/view" - - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" - org_model "github.com/caos/zitadel/internal/org/repository/view/model" -) - -const ( - orgTable = "adminapi.orgs" -) - -type Org struct { - handler - subscription *v1.Subscription -} - -func newOrg(handler handler) *Org { - h := &Org{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (o *Org) subscribe() { - o.subscription = o.es.Subscribe(o.AggregateTypes()...) - go func() { - for event := range o.subscription.Events { - query.ReduceEvent(o, event) - } - }() -} - -func (o *Org) ViewModel() string { - return orgTable -} - -func (o *Org) Subscription() *v1.Subscription { - return o.subscription -} - -func (o *Org) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{model.OrgAggregate} -} - -func (o *Org) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := o.view.GetLatestOrgSequence() - if err != nil { - return nil, err - } - return view.OrgQuery(sequence.CurrentSequence), nil -} - -func (o *Org) CurrentSequence() (uint64, error) { - sequence, err := o.view.GetLatestOrgSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (o *Org) Reduce(event *es_models.Event) error { - org := new(org_model.OrgView) - - switch event.Type { - case model.OrgAdded: - err := org.AppendEvent(event) - if err != nil { - return err - } - case model.OrgChanged: - org, err := o.view.OrgByID(event.ResourceOwner) - if err != nil { - return err - } - err = org.AppendEvent(event) - if err != nil { - return err - } - default: - return o.view.ProcessedOrgSequence(event) - } - - return o.view.PutOrg(org, event) -} - -func (o *Org) OnError(event *es_models.Event, spoolerErr error) error { - logging.LogWithFields("SPOOL-ls9ew", "id", event.AggregateID).WithError(spoolerErr).Warn("something went wrong in project app handler") - return spooler.HandleError(event, spoolerErr, o.view.GetLatestOrgFailedEvent, o.view.ProcessedOrgFailedEvent, o.view.ProcessedOrgSequence, o.errorCountUntilSkip) -} - -func (o *Org) OnSuccess() error { - return spooler.HandleSuccess(o.view.UpdateOrgSpoolerRunTimestamp) -} diff --git a/internal/admin/repository/eventsourcing/repository.go b/internal/admin/repository/eventsourcing/repository.go index b196773b91..ebffb31a33 100644 --- a/internal/admin/repository/eventsourcing/repository.go +++ b/internal/admin/repository/eventsourcing/repository.go @@ -12,7 +12,7 @@ import ( "github.com/caos/zitadel/internal/command" sd "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/types" - "github.com/caos/zitadel/internal/eventstore/v1" + v1 "github.com/caos/zitadel/internal/eventstore/v1" es_spol "github.com/caos/zitadel/internal/eventstore/v1/spooler" "github.com/caos/zitadel/internal/static" ) @@ -61,9 +61,7 @@ func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, c return &EsRepository{ spooler: spool, OrgRepo: eventstore.OrgRepo{ - Eventstore: es, View: view, - SearchLimit: conf.SearchLimit, SystemDefaults: systemDefaults, }, IAMRepository: eventstore.IAMRepository{ diff --git a/internal/admin/repository/eventsourcing/view/org.go b/internal/admin/repository/eventsourcing/view/org.go deleted file mode 100644 index 781d2e5a3c..0000000000 --- a/internal/admin/repository/eventsourcing/view/org.go +++ /dev/null @@ -1,49 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/eventstore/v1/models" - org_model "github.com/caos/zitadel/internal/org/model" - org_view "github.com/caos/zitadel/internal/org/repository/view" - "github.com/caos/zitadel/internal/org/repository/view/model" - "github.com/caos/zitadel/internal/view/repository" -) - -const ( - orgTable = "adminapi.orgs" -) - -func (v *View) OrgByID(orgID string) (*model.OrgView, error) { - return org_view.OrgByID(v.Db, orgTable, orgID) -} - -func (v *View) SearchOrgs(query *org_model.OrgSearchRequest) ([]*model.OrgView, uint64, error) { - return org_view.SearchOrgs(v.Db, orgTable, query) -} - -func (v *View) PutOrg(org *model.OrgView, event *models.Event) error { - err := org_view.PutOrg(v.Db, orgTable, org) - if err != nil { - return err - } - return v.ProcessedOrgSequence(event) -} - -func (v *View) GetLatestOrgFailedEvent(sequence uint64) (*repository.FailedEvent, error) { - return v.latestFailedEvent(orgTable, sequence) -} - -func (v *View) ProcessedOrgFailedEvent(failedEvent *repository.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} - -func (v *View) UpdateOrgSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(orgTable) -} - -func (v *View) GetLatestOrgSequence() (*repository.CurrentSequence, error) { - return v.latestSequence(orgTable) -} - -func (v *View) ProcessedOrgSequence(event *models.Event) error { - return v.saveCurrentSequence(orgTable, event) -} diff --git a/internal/admin/repository/org.go b/internal/admin/repository/org.go index e35cd7e5c4..c276550932 100644 --- a/internal/admin/repository/org.go +++ b/internal/admin/repository/org.go @@ -4,14 +4,8 @@ import ( "context" iam_model "github.com/caos/zitadel/internal/iam/model" - - org_model "github.com/caos/zitadel/internal/org/model" ) type OrgRepository interface { - IsOrgUnique(ctx context.Context, name, domain string) (bool, error) - OrgByID(ctx context.Context, id string) (*org_model.OrgView, error) - SearchOrgs(ctx context.Context, query *org_model.OrgSearchRequest) (*org_model.OrgSearchResult, error) - GetOrgIAMPolicyByID(ctx context.Context, id string) (*iam_model.OrgIAMPolicyView, error) } diff --git a/internal/api/api.go b/internal/api/api.go index b3a1b1ec10..630846a92e 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -22,6 +22,7 @@ import ( "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" iam_model "github.com/caos/zitadel/internal/iam/model" + "github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/telemetry/metrics" "github.com/caos/zitadel/internal/telemetry/metrics/otel" "github.com/caos/zitadel/internal/telemetry/tracing" @@ -58,11 +59,20 @@ type admin interface { GetSpoolerDiv(database, viewName string) int64 } -func Create(config Config, authZ authz.Config, authZRepo *authz_es.EsRepository, authRepo *auth_es.EsRepository, adminRepo *admin_es.EsRepository, sd systemdefaults.SystemDefaults) *API { +func Create(config Config, authZ authz.Config, q *query.Queries, authZRepo *authz_es.EsRepository, authRepo *auth_es.EsRepository, adminRepo *admin_es.EsRepository, sd systemdefaults.SystemDefaults) *API { api := &API{ serverPort: config.GRPC.ServerPort, } - api.verifier = authz.Start(authZRepo) + + repo := struct { + authz_es.EsRepository + query.Queries + }{ + *authZRepo, + *q, + } + + api.verifier = authz.Start(&repo) api.health = authZRepo api.auth = authRepo api.admin = adminRepo diff --git a/internal/api/grpc/admin/org.go b/internal/api/grpc/admin/org.go index 7e6fb9f6af..2753ed1938 100644 --- a/internal/api/grpc/admin/org.go +++ b/internal/api/grpc/admin/org.go @@ -5,20 +5,21 @@ import ( "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/grpc/object" + org_grpc "github.com/caos/zitadel/internal/api/grpc/org" "github.com/caos/zitadel/internal/domain" usr_model "github.com/caos/zitadel/internal/user/model" - - org_grpc "github.com/caos/zitadel/internal/api/grpc/org" admin_pb "github.com/caos/zitadel/pkg/grpc/admin" + obj_pb "github.com/caos/zitadel/pkg/grpc/object" + "google.golang.org/protobuf/types/known/timestamppb" ) func (s *Server) IsOrgUnique(ctx context.Context, req *admin_pb.IsOrgUniqueRequest) (*admin_pb.IsOrgUniqueResponse, error) { - isUnique, err := s.org.IsOrgUnique(ctx, req.Name, req.Domain) + isUnique, err := s.query.IsOrgUnique(ctx, req.Name, req.Domain) return &admin_pb.IsOrgUniqueResponse{IsUnique: isUnique}, err } func (s *Server) GetOrgByID(ctx context.Context, req *admin_pb.GetOrgByIDRequest) (*admin_pb.GetOrgByIDResponse, error) { - org, err := s.org.OrgByID(ctx, req.Id) + org, err := s.query.OrgByID(ctx, req.Id) if err != nil { return nil, err } @@ -26,15 +27,22 @@ func (s *Server) GetOrgByID(ctx context.Context, req *admin_pb.GetOrgByIDRequest } func (s *Server) ListOrgs(ctx context.Context, req *admin_pb.ListOrgsRequest) (*admin_pb.ListOrgsResponse, error) { - query, err := listOrgRequestToModel(req) + queries, err := listOrgRequestToModel(req) if err != nil { return nil, err } - orgs, err := s.org.SearchOrgs(ctx, query) + orgs, err := s.query.SearchOrgs(ctx, queries) if err != nil { return nil, err } - return &admin_pb.ListOrgsResponse{Result: org_grpc.OrgViewsToPb(orgs.Result)}, nil + return &admin_pb.ListOrgsResponse{ + Result: org_grpc.OrgViewsToPb(orgs.Orgs), + Details: &obj_pb.ListDetails{ + TotalResult: orgs.Count, + ProcessedSequence: orgs.Sequence, + ViewTimestamp: timestamppb.New(orgs.Timestamp), + }, + }, nil } func (s *Server) SetUpOrg(ctx context.Context, req *admin_pb.SetUpOrgRequest) (*admin_pb.SetUpOrgResponse, error) { diff --git a/internal/api/grpc/admin/org_converter.go b/internal/api/grpc/admin/org_converter.go index 5f649d47a4..a188123a27 100644 --- a/internal/api/grpc/admin/org_converter.go +++ b/internal/api/grpc/admin/org_converter.go @@ -4,24 +4,37 @@ import ( "github.com/caos/zitadel/internal/api/grpc/object" org_grpc "github.com/caos/zitadel/internal/api/grpc/org" "github.com/caos/zitadel/internal/domain" - "github.com/caos/zitadel/internal/org/model" + "github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/pkg/grpc/admin" + "github.com/caos/zitadel/pkg/grpc/org" ) -func listOrgRequestToModel(req *admin.ListOrgsRequest) (*model.OrgSearchRequest, error) { +func listOrgRequestToModel(req *admin.ListOrgsRequest) (*query.OrgSearchQueries, error) { offset, limit, asc := object.ListQueryToModel(req.Query) queries, err := org_grpc.OrgQueriesToModel(req.Queries) if err != nil { return nil, err } - return &model.OrgSearchRequest{ - Offset: offset, - Limit: limit, - Asc: asc, + return &query.OrgSearchQueries{ + SearchRequest: query.SearchRequest{ + Offset: offset, + Limit: limit, + SortingColumn: fieldNameToOrgColumn(req.SortingColumn), + Asc: asc, + }, Queries: queries, }, nil } +func fieldNameToOrgColumn(fieldName org.OrgFieldName) query.Column { + switch fieldName { + case org.OrgFieldName_ORG_FIELD_NAME_NAME: + return query.OrgColumnName + default: + return query.Column{} + } +} + func setUpOrgOrgToDomain(req *admin.SetUpOrgRequest_Org) *domain.Org { org := &domain.Org{ Name: req.Name, diff --git a/internal/api/grpc/management/actions.go b/internal/api/grpc/management/actions.go index 3136fdb8ae..6e3e6548ed 100644 --- a/internal/api/grpc/management/actions.go +++ b/internal/api/grpc/management/actions.go @@ -10,18 +10,22 @@ import ( ) func (s *Server) ListActions(ctx context.Context, req *mgmt_pb.ListActionsRequest) (*mgmt_pb.ListActionsResponse, error) { - query, _ := listActionsToQuery(authz.GetCtxData(ctx).OrgID, req) + query, err := listActionsToQuery(authz.GetCtxData(ctx).OrgID, req) + if err != nil { + return nil, err + } actions, err := s.query.SearchActions(ctx, query) if err != nil { return nil, err } return &mgmt_pb.ListActionsResponse{ - Result: action_grpc.ActionsToPb(actions), + Details: obj_grpc.ToListDetails(actions.Count, actions.Sequence, actions.Timestamp), + Result: action_grpc.ActionsToPb(actions.Actions), }, nil } func (s *Server) GetAction(ctx context.Context, req *mgmt_pb.GetActionRequest) (*mgmt_pb.GetActionResponse, error) { - action, err := s.query.GetAction(ctx, req.Id, authz.GetCtxData(ctx).OrgID) + action, err := s.query.GetActionByID(ctx, req.Id, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/actions_converter.go b/internal/api/grpc/management/actions_converter.go index 22dbd838a0..9a7537efdd 100644 --- a/internal/api/grpc/management/actions_converter.go +++ b/internal/api/grpc/management/actions_converter.go @@ -30,10 +30,10 @@ func updateActionRequestToDomain(req *mgmt_pb.UpdateActionRequest) *domain.Actio } } -func listActionsToQuery(id string, req *mgmt_pb.ListActionsRequest) (_ *query.ActionSearchQueries, err error) { +func listActionsToQuery(orgID string, req *mgmt_pb.ListActionsRequest) (_ *query.ActionSearchQueries, err error) { offset, limit, asc := object.ListQueryToModel(req.Query) queries := make([]query.SearchQuery, len(req.Queries)+1) - queries[0], err = query.NewActionResourceOwnerQuery(id) + queries[0], err = query.NewActionResourceOwnerQuery(orgID) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/flow.go b/internal/api/grpc/management/flow.go index 22fb627be8..5d4156f3d5 100644 --- a/internal/api/grpc/management/flow.go +++ b/internal/api/grpc/management/flow.go @@ -10,7 +10,7 @@ import ( ) func (s *Server) GetFlow(ctx context.Context, req *mgmt_pb.GetFlowRequest) (*mgmt_pb.GetFlowResponse, error) { - flow, err := s.query.GetFlow(ctx, action_grpc.FlowTypeToDomain(req.Type)) + flow, err := s.query.GetFlow(ctx, action_grpc.FlowTypeToDomain(req.Type), authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/org.go b/internal/api/grpc/management/org.go index 82411f230b..8db51dedac 100644 --- a/internal/api/grpc/management/org.go +++ b/internal/api/grpc/management/org.go @@ -17,7 +17,7 @@ import ( ) func (s *Server) GetMyOrg(ctx context.Context, req *mgmt_pb.GetMyOrgRequest) (*mgmt_pb.GetMyOrgResponse, error) { - org, err := s.org.OrgByID(ctx, authz.GetCtxData(ctx).OrgID) + org, err := s.query.OrgByID(ctx, authz.GetCtxData(ctx).OrgID) if err != nil { return nil, err } @@ -25,10 +25,11 @@ func (s *Server) GetMyOrg(ctx context.Context, req *mgmt_pb.GetMyOrgRequest) (*m } func (s *Server) GetOrgByDomainGlobal(ctx context.Context, req *mgmt_pb.GetOrgByDomainGlobalRequest) (*mgmt_pb.GetOrgByDomainGlobalResponse, error) { - org, err := s.org.OrgByDomainGlobal(ctx, req.Domain) + org, err := s.query.OrgByDomainGlobal(ctx, req.Domain) if err != nil { return nil, err } + return &mgmt_pb.GetOrgByDomainGlobalResponse{Org: org_grpc.OrgViewToPb(org)}, nil } diff --git a/internal/api/grpc/object/converter.go b/internal/api/grpc/object/converter.go index ab907da26d..fc91963bab 100644 --- a/internal/api/grpc/object/converter.go +++ b/internal/api/grpc/object/converter.go @@ -7,7 +7,6 @@ import ( "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/query" - "github.com/caos/zitadel/pkg/grpc/object" object_pb "github.com/caos/zitadel/pkg/grpc/object" ) @@ -69,7 +68,7 @@ func ToListDetails( totalResult, processedSequence uint64, viewTimestamp time.Time, -) *object.ListDetails { +) *object_pb.ListDetails { return &object_pb.ListDetails{ TotalResult: totalResult, ProcessedSequence: processedSequence, @@ -79,53 +78,53 @@ func ToListDetails( func TextMethodToModel(method object_pb.TextQueryMethod) domain.SearchMethod { switch method { - case object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS: + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS: return domain.SearchMethodEquals - case object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE: + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE: return domain.SearchMethodEqualsIgnoreCase - case object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH: + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH: return domain.SearchMethodStartsWith - case object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH_IGNORE_CASE: + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH_IGNORE_CASE: return domain.SearchMethodStartsWithIgnoreCase - case object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS: + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS: return domain.SearchMethodContains - case object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE: + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE: return domain.SearchMethodContainsIgnoreCase - case object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH: + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH: return domain.SearchMethodEndsWith - case object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH_IGNORE_CASE: + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH_IGNORE_CASE: return domain.SearchMethodEndsWithIgnoreCase default: return -1 } } +func TextMethodToQuery(method object_pb.TextQueryMethod) query.TextComparison { + switch method { + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS: + return query.TextEquals + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE: + return query.TextEqualsIgnoreCase + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH: + return query.TextStartsWith + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH_IGNORE_CASE: + return query.TextStartsWithIgnoreCase + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS: + return query.TextContains + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE: + return query.TextContainsIgnoreCase + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH: + return query.TextEndsWith + case object_pb.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH_IGNORE_CASE: + return query.TextEndsWithIgnoreCase + default: + return -1 + } +} + func ListQueryToModel(query *object_pb.ListQuery) (offset, limit uint64, asc bool) { if query == nil { return } return query.Offset, uint64(query.Limit), query.Asc } - -func TextMethodToQuery(method object_pb.TextQueryMethod) query.TextComparison { - switch method { - case object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS: - return query.TextEquals - case object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE: - return query.TextEqualsIgnore - case object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH: - return query.TextStartsWith - case object.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH_IGNORE_CASE: - return query.TextStartsWithIgnore - case object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS: - return query.TextContains - case object.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE: - return query.TextContainsIgnore - case object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH: - return query.TextEndsWith - case object.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH_IGNORE_CASE: - return query.TextEndsWithIgnore - default: - return -1 - } -} diff --git a/internal/api/grpc/org/converter.go b/internal/api/grpc/org/converter.go index 3beb22eef8..354bf898df 100644 --- a/internal/api/grpc/org/converter.go +++ b/internal/api/grpc/org/converter.go @@ -5,12 +5,13 @@ import ( "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" org_model "github.com/caos/zitadel/internal/org/model" + "github.com/caos/zitadel/internal/query" grant_model "github.com/caos/zitadel/internal/usergrant/model" org_pb "github.com/caos/zitadel/pkg/grpc/org" ) -func OrgQueriesToModel(queries []*org_pb.OrgQuery) (_ []*org_model.OrgSearchQuery, err error) { - q := make([]*org_model.OrgSearchQuery, len(queries)) +func OrgQueriesToModel(queries []*org_pb.OrgQuery) (_ []query.SearchQuery, err error) { + q := make([]query.SearchQuery, len(queries)) for i, query := range queries { q[i], err = OrgQueryToModel(query) if err != nil { @@ -20,22 +21,14 @@ func OrgQueriesToModel(queries []*org_pb.OrgQuery) (_ []*org_model.OrgSearchQuer return q, nil } -func OrgQueryToModel(query *org_pb.OrgQuery) (*org_model.OrgSearchQuery, error) { - switch q := query.Query.(type) { +func OrgQueryToModel(apiQuery *org_pb.OrgQuery) (query.SearchQuery, error) { + switch q := apiQuery.Query.(type) { case *org_pb.OrgQuery_DomainQuery: - return &org_model.OrgSearchQuery{ - Key: org_model.OrgSearchKeyOrgDomain, - Method: object.TextMethodToModel(q.DomainQuery.Method), - Value: q.DomainQuery.Domain, - }, nil + return query.NewOrgDomainSearchQuery(object.TextMethodToQuery(q.DomainQuery.Method), q.DomainQuery.Domain) case *org_pb.OrgQuery_NameQuery: - return &org_model.OrgSearchQuery{ - Key: org_model.OrgSearchKeyOrgName, - Method: object.TextMethodToModel(q.NameQuery.Method), - Value: q.NameQuery.Name, - }, nil + return query.NewOrgNameSearchQuery(object.TextMethodToQuery(q.NameQuery.Method), q.NameQuery.Name) default: - return nil, errors.ThrowInvalidArgument(nil, "ADMIN-vR9nC", "List.Query.Invalid") + return nil, errors.ThrowInvalidArgument(nil, "ORG-vR9nC", "List.Query.Invalid") } } @@ -69,7 +62,7 @@ func OrgQueryToUserGrantQueryModel(query *org_pb.OrgQuery) (*grant_model.UserGra } } -func OrgViewsToPb(orgs []*org_model.OrgView) []*org_pb.Org { +func OrgViewsToPb(orgs []*query.Org) []*org_pb.Org { o := make([]*org_pb.Org, len(orgs)) for i, org := range orgs { o[i] = OrgViewToPb(org) @@ -77,7 +70,7 @@ func OrgViewsToPb(orgs []*org_model.OrgView) []*org_pb.Org { return o } -func OrgViewToPb(org *org_model.OrgView) *org_pb.Org { +func OrgViewToPb(org *query.Org) *org_pb.Org { return &org_pb.Org{ Id: org.ID, State: OrgStateToPb(org.State), @@ -113,11 +106,11 @@ func OrgToPb(org *grant_model.Org) *org_pb.Org { } } -func OrgStateToPb(state org_model.OrgState) org_pb.OrgState { +func OrgStateToPb(state domain.OrgState) org_pb.OrgState { switch state { - case org_model.OrgStateActive: + case domain.OrgStateActive: return org_pb.OrgState_ORG_STATE_ACTIVE - case org_model.OrgStateInactive: + case domain.OrgStateInactive: return org_pb.OrgState_ORG_STATE_INACTIVE default: return org_pb.OrgState_ORG_STATE_UNSPECIFIED diff --git a/internal/api/oidc/client.go b/internal/api/oidc/client.go index 96e31f523b..1511c27892 100644 --- a/internal/api/oidc/client.go +++ b/internal/api/oidc/client.go @@ -88,7 +88,7 @@ func (o *OPStorage) ValidateJWTProfileScopes(ctx context.Context, subject string scope := scopes[i] if strings.HasPrefix(scope, authreq_model.OrgDomainPrimaryScope) { var orgID string - org, err := o.repo.OrgByPrimaryDomain(strings.TrimPrefix(scope, authreq_model.OrgDomainPrimaryScope)) + org, err := o.query.OrgByDomainGlobal(ctx, strings.TrimPrefix(scope, authreq_model.OrgDomainPrimaryScope)) if err == nil { orgID = org.ID } @@ -310,14 +310,18 @@ func (o *OPStorage) assertUserMetaData(ctx context.Context, userID string) (map[ } func (o *OPStorage) assertUserResourceOwner(ctx context.Context, userID string) (map[string]string, error) { - resourceOwner, err := o.repo.OrgByUserID(ctx, userID) + user, err := o.repo.UserByID(ctx, userID) + if err != nil { + return nil, err + } + resourceOwner, err := o.query.OrgByID(ctx, user.ResourceOwner) if err != nil { return nil, err } return map[string]string{ - ClaimResourceOwner + "id": resourceOwner.AggregateID, + ClaimResourceOwner + "id": resourceOwner.ID, ClaimResourceOwner + "name": resourceOwner.Name, - ClaimResourceOwner + "primary_domain": resourceOwner.PrimaryDomain, + ClaimResourceOwner + "primary_domain": resourceOwner.Domain, }, nil } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 0241911db7..504e2fb1e8 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -17,9 +17,8 @@ import ( iam_model "github.com/caos/zitadel/internal/iam/model" iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model" "github.com/caos/zitadel/internal/id" - org_model "github.com/caos/zitadel/internal/org/model" - org_view_model "github.com/caos/zitadel/internal/org/repository/view/model" project_view_model "github.com/caos/zitadel/internal/project/repository/view/model" + "github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/repository/iam" "github.com/caos/zitadel/internal/telemetry/tracing" user_model "github.com/caos/zitadel/internal/user/model" @@ -87,8 +86,8 @@ type userCommandProvider interface { } type orgViewProvider interface { - OrgByID(string) (*org_view_model.OrgView, error) - OrgByPrimaryDomain(string) (*org_view_model.OrgView, error) + OrgByID(context.Context, string) (*query.Org, error) + OrgByDomainGlobal(context.Context, string) (*query.Org, error) } type userGrantProvider interface { @@ -935,7 +934,7 @@ func setOrgID(orgViewProvider orgViewProvider, request *domain.AuthRequest) erro return nil } - org, err := orgViewProvider.OrgByPrimaryDomain(primaryDomain) + org, err := orgViewProvider.OrgByDomainGlobal(context.TODO(), primaryDomain) if err != nil { return err } @@ -1032,7 +1031,7 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve return user_view_model.UserSessionToModel(&sessionCopy, provider.PrefixAvatarURL()), nil } -func activeUserByID(ctx context.Context, userViewProvider userViewProvider, userEventProvider userEventProvider, orgViewProvider orgViewProvider, lockoutPolicyProvider lockoutPolicyViewProvider, userID string) (*user_model.UserView, error) { +func activeUserByID(ctx context.Context, userViewProvider userViewProvider, userEventProvider userEventProvider, queries orgViewProvider, lockoutPolicyProvider lockoutPolicyViewProvider, userID string) (*user_model.UserView, error) { // PLANNED: Check LockoutPolicy user, err := userByID(ctx, userViewProvider, userEventProvider, userID) if err != nil { @@ -1048,11 +1047,11 @@ func activeUserByID(ctx context.Context, userViewProvider userViewProvider, user if !(user.State == user_model.UserStateActive || user.State == user_model.UserStateInitial) { return nil, errors.ThrowPreconditionFailed(nil, "EVENT-FJ262", "Errors.User.NotActive") } - org, err := orgViewProvider.OrgByID(user.ResourceOwner) + org, err := queries.OrgByID(context.TODO(), user.ResourceOwner) if err != nil { return nil, err } - if org.State != int32(org_model.OrgStateActive) { + if org.State != domain.OrgStateActive { return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Zws3s", "Errors.User.NotActive") } return user, nil diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index 741eea5f53..4c0291308d 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -8,19 +8,17 @@ import ( "github.com/stretchr/testify/assert" - "github.com/caos/zitadel/internal/crypto" - "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view" "github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/auth_request/repository/cache" + "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" es_models "github.com/caos/zitadel/internal/eventstore/v1/models" iam_model "github.com/caos/zitadel/internal/iam/model" iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model" - org_model "github.com/caos/zitadel/internal/org/model" - org_view_model "github.com/caos/zitadel/internal/org/repository/view/model" proj_view_model "github.com/caos/zitadel/internal/project/repository/view/model" + "github.com/caos/zitadel/internal/query" user_model "github.com/caos/zitadel/internal/user/model" user_es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" user_view_model "github.com/caos/zitadel/internal/user/repository/view/model" @@ -186,28 +184,28 @@ func (m *mockViewUser) PrefixAvatarURL() string { } type mockViewOrg struct { - State org_model.OrgState + State domain.OrgState } -func (m *mockViewOrg) OrgByID(string) (*org_view_model.OrgView, error) { - return &org_view_model.OrgView{ - State: int32(m.State), +func (m *mockViewOrg) OrgByID(context.Context, string) (*query.Org, error) { + return &query.Org{ + State: m.State, }, nil } -func (m *mockViewOrg) OrgByPrimaryDomain(string) (*org_view_model.OrgView, error) { - return &org_view_model.OrgView{ - State: int32(m.State), +func (m *mockViewOrg) OrgByDomainGlobal(context.Context, string) (*query.Org, error) { + return &query.Org{ + State: m.State, }, nil } type mockViewErrOrg struct{} -func (m *mockViewErrOrg) OrgByID(string) (*org_view_model.OrgView, error) { +func (m *mockViewErrOrg) OrgByID(context.Context, string) (*query.Org, error) { return nil, errors.ThrowInternal(nil, "id", "internal error") } -func (m *mockViewErrOrg) OrgByPrimaryDomain(string) (*org_view_model.OrgView, error) { +func (m *mockViewErrOrg) OrgByDomainGlobal(context.Context, string) (*query.Org, error) { return nil, errors.ThrowInternal(nil, "id", "internal error") } @@ -439,7 +437,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { Type: user_es_model.UserDeactivated, }, }, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -460,7 +458,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { Type: user_es_model.UserLocked, }, }, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -492,7 +490,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { fields{ userViewProvider: &mockViewUser{}, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateInactive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateInactive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -511,7 +509,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordSet: true, }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -528,7 +526,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userSessionViewProvider: &mockViewErrUserSession{}, userViewProvider: &mockViewUser{}, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -548,7 +546,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordSet: true, }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -569,7 +567,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordlessInitRequired: true, }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, MultiFactorCheckLifeTime: 10 * time.Hour, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ @@ -589,7 +587,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordlessTokens: user_view_model.WebAuthNTokens{&user_view_model.WebAuthNView{ID: "id", State: int32(user_model.MFAStateReady)}}, }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -610,7 +608,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordlessTokens: user_view_model.WebAuthNTokens{&user_view_model.WebAuthNView{ID: "id", State: int32(user_model.MFAStateReady)}}, }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -642,7 +640,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowLockOutFailures: true, }, }, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, MultiFactorCheckLifeTime: 10 * time.Hour, }, args{&domain.AuthRequest{ @@ -668,7 +666,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowLockOutFailures: true, }, }, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, }, args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false}, []domain.NextStep{&domain.InitPasswordStep{}}, @@ -690,7 +688,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { ShowLockOutFailures: true, }, }, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, SecondFactorCheckLifeTime: 18 * time.Hour, }, args{&domain.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false}, @@ -709,7 +707,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, userGrantProvider: &mockUserGrants{}, projectProvider: &mockProject{}, loginPolicyProvider: &mockLoginPolicy{ @@ -742,7 +740,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { PasswordSet: true, }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -767,7 +765,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, userGrantProvider: &mockUserGrants{}, projectProvider: &mockProject{}, lockoutPolicyProvider: &mockLockoutPolicy{ @@ -801,7 +799,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelMultiFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -834,7 +832,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -868,7 +866,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -905,7 +903,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -936,7 +934,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -967,7 +965,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -998,7 +996,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, userGrantProvider: &mockUserGrants{}, projectProvider: &mockProject{}, lockoutPolicyProvider: &mockLockoutPolicy{ @@ -1032,7 +1030,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, userGrantProvider: &mockUserGrants{}, projectProvider: &mockProject{}, lockoutPolicyProvider: &mockLockoutPolicy{ @@ -1067,7 +1065,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, userGrantProvider: &mockUserGrants{ roleCheck: true, userGrants: 0, @@ -1105,7 +1103,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, userGrantProvider: &mockUserGrants{ roleCheck: true, userGrants: 2, @@ -1143,7 +1141,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, userGrantProvider: &mockUserGrants{}, projectProvider: &mockProject{ projectCheck: true, @@ -1181,7 +1179,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, userGrantProvider: &mockUserGrants{}, projectProvider: &mockProject{ projectCheck: true, @@ -1223,7 +1221,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { }, }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, SecondFactorCheckLifeTime: 18 * time.Hour, }, args{ @@ -1249,7 +1247,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { MFAMaxSetUp: int32(model.MFALevelSecondFactor), }, userEventProvider: &mockEventUser{}, - orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + orgViewProvider: &mockViewOrg{State: domain.OrgStateActive}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, diff --git a/internal/auth/repository/eventsourcing/eventstore/org.go b/internal/auth/repository/eventsourcing/eventstore/org.go index da1d9ea2a4..fee1f6e647 100644 --- a/internal/auth/repository/eventsourcing/eventstore/org.go +++ b/internal/auth/repository/eventsourcing/eventstore/org.go @@ -14,10 +14,7 @@ import ( "github.com/caos/zitadel/internal/eventstore/v1/models" iam_model "github.com/caos/zitadel/internal/iam/model" iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model" - org_model "github.com/caos/zitadel/internal/org/model" - "github.com/caos/zitadel/internal/org/repository/view/model" "github.com/caos/zitadel/internal/repository/iam" - "github.com/caos/zitadel/internal/telemetry/tracing" ) const ( @@ -32,38 +29,6 @@ type OrgRepository struct { SystemDefaults systemdefaults.SystemDefaults } -func (repo *OrgRepository) SearchOrgs(ctx context.Context, request *org_model.OrgSearchRequest) (*org_model.OrgSearchResult, error) { - err := request.EnsureLimit(repo.SearchLimit) - if err != nil { - return nil, err - } - sequence, err := repo.View.GetLatestOrgSequence() - logging.Log("EVENT-7Udhz").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Warn("could not read latest org sequence") - members, count, err := repo.View.SearchOrgs(request) - if err != nil { - return nil, err - } - result := &org_model.OrgSearchResult{ - Offset: request.Offset, - Limit: request.Limit, - TotalResult: count, - Result: model.OrgsToModel(members), - } - if err == nil { - result.Sequence = sequence.CurrentSequence - result.Timestamp = sequence.LastSuccessfulSpoolerRun - } - return result, nil -} - -func (repo *OrgRepository) OrgByPrimaryDomain(primaryDomain string) (*org_model.OrgView, error) { - org, err := repo.View.OrgByPrimaryDomain(primaryDomain) - if err != nil { - return nil, err - } - return model.OrgToModel(org), nil -} - func (repo *OrgRepository) GetDefaultOrgIAMPolicy(ctx context.Context) (*iam_model.OrgIAMPolicyView, error) { orgPolicy, err := repo.View.OrgIAMPolicyByAggregateID(repo.SystemDefaults.IamID) if err != nil { diff --git a/internal/auth/repository/eventsourcing/eventstore/user.go b/internal/auth/repository/eventsourcing/eventstore/user.go index f3610739ec..4d0ff94cae 100644 --- a/internal/auth/repository/eventsourcing/eventstore/user.go +++ b/internal/auth/repository/eventsourcing/eventstore/user.go @@ -12,12 +12,11 @@ import ( "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/eventstore/v1" + v1 "github.com/caos/zitadel/internal/eventstore/v1" "github.com/caos/zitadel/internal/eventstore/v1/models" iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" key_model "github.com/caos/zitadel/internal/key/model" key_view_model "github.com/caos/zitadel/internal/key/repository/view/model" - org_model "github.com/caos/zitadel/internal/org/repository/view/model" "github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/user/model" usr_view "github.com/caos/zitadel/internal/user/repository/view" @@ -308,18 +307,6 @@ func (repo *UserRepo) GetMyMetadataByKey(ctx context.Context, key string) (*doma return iam_model.MetadataViewToDomain(data), nil } -func (repo *UserRepo) OrgByUserID(ctx context.Context, userID string) (*domain.Org, error) { - user, err := repo.View.UserByID(userID) - if err != nil { - return nil, err - } - org, err := repo.View.OrgByID(user.ResourceOwner) - if err != nil { - return nil, err - } - return org_model.OrgToDomain(org), nil -} - func (repo *UserRepo) SearchUserMetadata(ctx context.Context, userID string) (*domain.MetadataSearchResponse, error) { req := new(domain.MetadataSearchRequest) return repo.searchUserMetadata(userID, "", req) diff --git a/internal/auth/repository/eventsourcing/eventstore/user_grant.go b/internal/auth/repository/eventsourcing/eventstore/user_grant.go index fa2661affc..1132c0bec4 100644 --- a/internal/auth/repository/eventsourcing/eventstore/user_grant.go +++ b/internal/auth/repository/eventsourcing/eventstore/user_grant.go @@ -6,13 +6,12 @@ import ( "github.com/caos/logging" "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view" authz_repo "github.com/caos/zitadel/internal/authz/repository/eventsourcing" caos_errs "github.com/caos/zitadel/internal/errors" - org_model "github.com/caos/zitadel/internal/org/model" - org_view_model "github.com/caos/zitadel/internal/org/repository/view/model" "github.com/caos/zitadel/internal/telemetry/tracing" user_model "github.com/caos/zitadel/internal/user/model" user_view_model "github.com/caos/zitadel/internal/user/repository/view/model" @@ -27,6 +26,8 @@ type UserGrantRepo struct { Auth authz.Config AuthZRepo *authz_repo.EsRepository PrefixAvatarURL string + + Query *query.Queries } func (repo *UserGrantRepo) SearchMyUserGrants(ctx context.Context, request *grant_model.UserGrantSearchRequest) (*grant_model.UserGrantSearchResponse, error) { @@ -198,22 +199,28 @@ func (repo *UserGrantRepo) SearchMyProjectPermissions(ctx context.Context) ([]st } func (repo *UserGrantRepo) SearchAdminOrgs(request *grant_model.UserGrantSearchRequest) (*grant_model.ProjectOrgSearchResponse, error) { - searchRequest := &org_model.OrgSearchRequest{ - SortingColumn: org_model.OrgSearchKeyOrgNameIgnoreCase, - Asc: true, + searchRequest := query.OrgSearchQueries{ + SearchRequest: query.SearchRequest{ + SortingColumn: query.OrgColumnName, + Asc: true, + }, } if len(request.Queries) > 0 { for _, q := range request.Queries { if q.Key == grant_model.UserGrantSearchKeyOrgName { - searchRequest.Queries = append(searchRequest.Queries, &org_model.OrgSearchQuery{Key: org_model.OrgSearchKeyOrgName, Method: q.Method, Value: q.Value}) + nameQuery, err := query.NewOrgNameSearchQuery(query.TextComparisonFromMethod(q.Method), q.Value.(string)) + if err != nil { + return nil, err + } + searchRequest.Queries = append(searchRequest.Queries, nameQuery) } } } - orgs, count, err := repo.View.SearchOrgs(searchRequest) + orgs, err := repo.Query.SearchOrgs(context.TODO(), &searchRequest) if err != nil { return nil, err } - return orgRespToOrgResp(orgs, count), nil + return orgRespToOrgResp(orgs), nil } func (repo *UserGrantRepo) IsIamAdmin(ctx context.Context) (bool, error) { @@ -244,7 +251,7 @@ func (repo *UserGrantRepo) userOrg(ctxData authz.CtxData) (*grant_model.ProjectO if err != nil { return nil, err } - org, err := repo.View.OrgByID(user.ResourceOwner) + org, err := repo.Query.OrgByID(context.TODO(), user.ResourceOwner) if err != nil { return nil, err } @@ -300,12 +307,12 @@ func grantRespToOrgResp(grants *grant_model.UserGrantSearchResponse) *grant_mode return resp } -func orgRespToOrgResp(orgs []*org_view_model.OrgView, count uint64) *grant_model.ProjectOrgSearchResponse { +func orgRespToOrgResp(orgs *query.Orgs) *grant_model.ProjectOrgSearchResponse { resp := &grant_model.ProjectOrgSearchResponse{ - TotalResult: count, + TotalResult: orgs.Count, } - resp.Result = make([]*grant_model.Org, len(orgs)) - for i, o := range orgs { + resp.Result = make([]*grant_model.Org, len(orgs.Orgs)) + for i, o := range orgs.Orgs { resp.Result[i] = &grant_model.Org{OrgID: o.ID, OrgName: o.Name} } return resp diff --git a/internal/auth/repository/eventsourcing/handler/handler.go b/internal/auth/repository/eventsourcing/handler/handler.go index 5c3daac5d8..6a54aa7b85 100644 --- a/internal/auth/repository/eventsourcing/handler/handler.go +++ b/internal/auth/repository/eventsourcing/handler/handler.go @@ -45,8 +45,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es handler{view, bulkLimit, configs.cycleDuration("Key"), errorCount, es}, keyChan), newApplication(handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount, es}), - newOrg( - handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount, es}), newUserGrant( handler{view, bulkLimit, configs.cycleDuration("UserGrant"), errorCount, es}, systemDefaults.IamID), diff --git a/internal/auth/repository/eventsourcing/handler/org.go b/internal/auth/repository/eventsourcing/handler/org.go deleted file mode 100644 index 70990ef9ee..0000000000 --- a/internal/auth/repository/eventsourcing/handler/org.go +++ /dev/null @@ -1,111 +0,0 @@ -package handler - -import ( - "github.com/caos/logging" - "github.com/caos/zitadel/internal/eventstore/v1" - "github.com/caos/zitadel/internal/org/repository/view" - - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" - org_model "github.com/caos/zitadel/internal/org/repository/view/model" -) - -const ( - orgTable = "auth.orgs" -) - -type Org struct { - handler - subscription *v1.Subscription -} - -func newOrg(handler handler) *Org { - h := &Org{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (o *Org) subscribe() { - o.subscription = o.es.Subscribe(o.AggregateTypes()...) - go func() { - for event := range o.subscription.Events { - query.ReduceEvent(o, event) - } - }() -} - -func (o *Org) ViewModel() string { - return orgTable -} - -func (o *Org) Subscription() *v1.Subscription { - return o.subscription -} - -func (_ *Org) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{model.OrgAggregate} -} - -func (o *Org) CurrentSequence() (uint64, error) { - sequence, err := o.view.GetLatestOrgSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (o *Org) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := o.view.GetLatestOrgSequence() - if err != nil { - return nil, err - } - return view.OrgQuery(sequence.CurrentSequence), nil -} - -func (o *Org) Reduce(event *es_models.Event) (err error) { - org := new(org_model.OrgView) - - switch event.Type { - case model.OrgAdded: - err = org.AppendEvent(event) - case model.OrgChanged: - org, err = o.view.OrgByID(event.ResourceOwner) - if err != nil { - return err - } - err = org.AppendEvent(event) - case model.OrgDomainPrimarySet: - domain := new(org_model.OrgDomainView) - err = domain.SetData(event) - if err != nil { - return err - } - org, err = o.view.OrgByID(event.AggregateID) - if err != nil { - return err - } - org.Domain = domain.Domain - default: - return o.view.ProcessedOrgSequence(event) - } - if err != nil { - return err - } - - return o.view.PutOrg(org, event) -} - -func (o *Org) OnError(event *es_models.Event, spoolerErr error) error { - logging.LogWithFields("SPOOL-8siWS", "id", event.AggregateID).WithError(spoolerErr).Warn("something went wrong in org handler") - return spooler.HandleError(event, spoolerErr, o.view.GetLatestOrgFailedEvent, o.view.ProcessedOrgFailedEvent, o.view.ProcessedOrgSequence, o.errorCountUntilSkip) -} - -func (o *Org) OnSuccess() error { - return spooler.HandleSuccess(o.view.UpdateOrgSpoolerRunTimestamp) -} diff --git a/internal/auth/repository/eventsourcing/repository.go b/internal/auth/repository/eventsourcing/repository.go index 6df9f147fc..13f0f7fd36 100644 --- a/internal/auth/repository/eventsourcing/repository.go +++ b/internal/auth/repository/eventsourcing/repository.go @@ -99,6 +99,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co userRepo, eventstore.AuthRequestRepo{ Command: command, + OrgViewProvider: queries, AuthRequests: authReq, View: view, Eventstore: es, @@ -106,7 +107,6 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co UserViewProvider: view, UserCommandProvider: command, UserEventProvider: &userRepo, - OrgViewProvider: view, IDPProviderViewProvider: view, LoginPolicyViewProvider: view, LockoutPolicyViewProvider: view, @@ -154,6 +154,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co IamID: systemDefaults.IamID, Auth: authZ, AuthZRepo: authZRepo, + Query: queries, }, eventstore.OrgRepository{ SearchLimit: conf.SearchLimit, diff --git a/internal/auth/repository/eventsourcing/view/org.go b/internal/auth/repository/eventsourcing/view/org.go deleted file mode 100644 index 3cf4e7f17a..0000000000 --- a/internal/auth/repository/eventsourcing/view/org.go +++ /dev/null @@ -1,53 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/org/model" - org_view "github.com/caos/zitadel/internal/org/repository/view" - org_model "github.com/caos/zitadel/internal/org/repository/view/model" - "github.com/caos/zitadel/internal/view/repository" -) - -const ( - orgTable = "auth.orgs" -) - -func (v *View) OrgByID(orgID string) (*org_model.OrgView, error) { - return org_view.OrgByID(v.Db, orgTable, orgID) -} - -func (v *View) OrgByPrimaryDomain(primaryDomain string) (*org_model.OrgView, error) { - return org_view.OrgByPrimaryDomain(v.Db, orgTable, primaryDomain) -} - -func (v *View) SearchOrgs(req *model.OrgSearchRequest) ([]*org_model.OrgView, uint64, error) { - return org_view.SearchOrgs(v.Db, orgTable, req) -} - -func (v *View) PutOrg(org *org_model.OrgView, event *models.Event) error { - err := org_view.PutOrg(v.Db, orgTable, org) - if err != nil { - return err - } - return v.ProcessedOrgSequence(event) -} - -func (v *View) GetLatestOrgFailedEvent(sequence uint64) (*repository.FailedEvent, error) { - return v.latestFailedEvent(orgTable, sequence) -} - -func (v *View) ProcessedOrgFailedEvent(failedEvent *repository.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} - -func (v *View) UpdateOrgSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(orgTable) -} - -func (v *View) GetLatestOrgSequence() (*repository.CurrentSequence, error) { - return v.latestSequence(orgTable) -} - -func (v *View) ProcessedOrgSequence(event *models.Event) error { - return v.saveCurrentSequence(orgTable, event) -} diff --git a/internal/auth/repository/org.go b/internal/auth/repository/org.go index f420f81371..fce19afe79 100644 --- a/internal/auth/repository/org.go +++ b/internal/auth/repository/org.go @@ -2,13 +2,12 @@ package repository import ( "context" + "github.com/caos/zitadel/internal/domain" iam_model "github.com/caos/zitadel/internal/iam/model" - org_model "github.com/caos/zitadel/internal/org/model" ) type OrgRepository interface { - OrgByPrimaryDomain(primaryDomain string) (*org_model.OrgView, error) GetOrgIAMPolicy(ctx context.Context, orgID string) (*iam_model.OrgIAMPolicyView, error) GetDefaultOrgIAMPolicy(ctx context.Context) (*iam_model.OrgIAMPolicyView, error) GetIDPConfigByID(ctx context.Context, idpConfigID string) (*iam_model.IDPConfigView, error) diff --git a/internal/auth/repository/user.go b/internal/auth/repository/user.go index 679df592cb..0f9c573c81 100644 --- a/internal/auth/repository/user.go +++ b/internal/auth/repository/user.go @@ -23,7 +23,6 @@ type UserRepository interface { SearchUsers(ctx context.Context, request *model.UserSearchRequest) (*model.UserSearchResponse, error) SearchUserMetadata(ctx context.Context, userID string) (*domain.MetadataSearchResponse, error) - OrgByUserID(ctx context.Context, userID string) (*domain.Org, error) } type myUserRepo interface { diff --git a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go index 57210efaa8..977acf5d52 100644 --- a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go +++ b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go @@ -109,11 +109,6 @@ func (repo *TokenVerifierRepo) ProjectIDAndOriginsByClientID(ctx context.Context return app.ProjectID, app.OriginAllowList, nil } -func (repo *TokenVerifierRepo) ExistsOrg(ctx context.Context, orgID string) error { - _, err := repo.View.OrgByID(orgID) - return err -} - func (repo *TokenVerifierRepo) CheckOrgFeatures(ctx context.Context, orgID string, requiredFeatures ...string) error { features, err := repo.View.FeaturesByAggregateID(orgID) if caos_errs.IsNotFound(err) { diff --git a/internal/authz/repository/eventsourcing/handler/handler.go b/internal/authz/repository/eventsourcing/handler/handler.go index 5dd35cc7be..a16b27d485 100644 --- a/internal/authz/repository/eventsourcing/handler/handler.go +++ b/internal/authz/repository/eventsourcing/handler/handler.go @@ -38,8 +38,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es handler{view, bulkLimit, configs.cycleDuration("UserMemberships"), errorCount, es}), newApplication( handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount, es}), - newOrg( - handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount, es}), newFeatures( handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}), } diff --git a/internal/authz/repository/eventsourcing/handler/org.go b/internal/authz/repository/eventsourcing/handler/org.go deleted file mode 100644 index aca287fd40..0000000000 --- a/internal/authz/repository/eventsourcing/handler/org.go +++ /dev/null @@ -1,103 +0,0 @@ -package handler - -import ( - "github.com/caos/logging" - "github.com/caos/zitadel/internal/eventstore/v1" - "github.com/caos/zitadel/internal/org/repository/view" - - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" - org_model "github.com/caos/zitadel/internal/org/repository/view/model" -) - -const ( - orgTable = "authz.orgs" -) - -type Org struct { - handler - subscription *v1.Subscription -} - -func newOrg(handler handler) *Org { - h := &Org{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (k *Org) subscribe() { - k.subscription = k.es.Subscribe(k.AggregateTypes()...) - go func() { - for event := range k.subscription.Events { - query.ReduceEvent(k, event) - } - }() -} - -func (o *Org) ViewModel() string { - return orgTable -} - -func (o *Org) Subscription() *v1.Subscription { - return o.subscription -} - -func (_ *Org) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{model.OrgAggregate} -} - -func (o *Org) CurrentSequence() (uint64, error) { - sequence, err := o.view.GetLatestOrgSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (o *Org) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := o.view.GetLatestOrgSequence() - if err != nil { - return nil, err - } - return view.OrgQuery(sequence.CurrentSequence), nil -} - -func (o *Org) Reduce(event *es_models.Event) error { - org := new(org_model.OrgView) - - switch event.Type { - case model.OrgAdded: - err := org.AppendEvent(event) - if err != nil { - return err - } - case model.OrgChanged: - org, err := o.view.OrgByID(event.ResourceOwner) - if err != nil { - return err - } - err = org.AppendEvent(event) - if err != nil { - return err - } - default: - return o.view.ProcessedOrgSequence(event) - } - - return o.view.PutOrg(org, event) -} - -func (o *Org) OnError(event *es_models.Event, spoolerErr error) error { - logging.LogWithFields("SPOOL-8siWS", "id", event.AggregateID).WithError(spoolerErr).Warn("something went wrong in org handler") - return spooler.HandleError(event, spoolerErr, o.view.GetLatestOrgFailedEvent, o.view.ProcessedOrgFailedEvent, o.view.ProcessedOrgSequence, o.errorCountUntilSkip) -} - -func (o *Org) OnSuccess() error { - return spooler.HandleSuccess(o.view.UpdateOrgSpoolerRunTimestamp) -} diff --git a/internal/authz/repository/eventsourcing/view/org.go b/internal/authz/repository/eventsourcing/view/org.go deleted file mode 100644 index 0b395101d2..0000000000 --- a/internal/authz/repository/eventsourcing/view/org.go +++ /dev/null @@ -1,49 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/org/model" - org_view "github.com/caos/zitadel/internal/org/repository/view" - org_model "github.com/caos/zitadel/internal/org/repository/view/model" - "github.com/caos/zitadel/internal/view/repository" -) - -const ( - orgTable = "authz.orgs" -) - -func (v *View) OrgByID(orgID string) (*org_model.OrgView, error) { - return org_view.OrgByID(v.Db, orgTable, orgID) -} - -func (v *View) SearchOrgs(req *model.OrgSearchRequest) ([]*org_model.OrgView, uint64, error) { - return org_view.SearchOrgs(v.Db, orgTable, req) -} - -func (v *View) PutOrg(org *org_model.OrgView, event *models.Event) error { - err := org_view.PutOrg(v.Db, orgTable, org) - if err != nil { - return err - } - return v.ProcessedOrgSequence(event) -} - -func (v *View) GetLatestOrgFailedEvent(sequence uint64) (*repository.FailedEvent, error) { - return v.latestFailedEvent(orgTable, sequence) -} - -func (v *View) ProcessedOrgFailedEvent(failedEvent *repository.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} - -func (v *View) GetLatestOrgSequence() (*repository.CurrentSequence, error) { - return v.latestSequence(orgTable) -} - -func (v *View) ProcessedOrgSequence(event *models.Event) error { - return v.saveCurrentSequence(orgTable, event) -} - -func (v *View) UpdateOrgSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(orgTable) -} diff --git a/internal/command/command.go b/internal/command/command.go index aeb62967f6..ccb6c85fd6 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -55,16 +55,26 @@ type Commands struct { keyAlgorithm crypto.EncryptionAlgorithm privateKeyLifetime time.Duration publicKeyLifetime time.Duration - tokenVerifier *authz.TokenVerifier + tokenVerifier orgFeatureChecker +} + +type orgFeatureChecker interface { + CheckOrgFeatures(ctx context.Context, orgID string, requiredFeatures ...string) error } type Config struct { Eventstore types.SQLUser } -func StartCommands(eventstore *eventstore.Eventstore, defaults sd.SystemDefaults, authZConfig authz.Config, staticStore static.Storage, authZRepo *authz_repo.EsRepository) (repo *Commands, err error) { +func StartCommands( + es *eventstore.Eventstore, + defaults sd.SystemDefaults, + authZConfig authz.Config, + staticStore static.Storage, + authZRepo *authz_repo.EsRepository, +) (repo *Commands, err error) { repo = &Commands{ - eventstore: eventstore, + eventstore: es, static: staticStore, idGenerator: id.SonyFlakeGenerator, iamDomain: defaults.Domain, @@ -130,7 +140,7 @@ func StartCommands(eventstore *eventstore.Eventstore, defaults sd.SystemDefaults } repo.keyAlgorithm = keyAlgorithm - repo.tokenVerifier = authz.Start(authZRepo) + repo.tokenVerifier = authZRepo return repo, nil } diff --git a/internal/command/main_test.go b/internal/command/main_test.go index 8a2a837f47..4726c34909 100644 --- a/internal/command/main_test.go +++ b/internal/command/main_test.go @@ -182,10 +182,10 @@ func GetMockSecretGenerator(t *testing.T) crypto.Generator { return generator } -func GetMockVerifier(t *testing.T, features ...string) *authz.TokenVerifier { - return authz.Start(&testVerifier{ +func GetMockVerifier(t *testing.T, features ...string) *testVerifier { + return &testVerifier{ features: features, - }) + } } type testVerifier struct { diff --git a/internal/command/org_policy_label.go b/internal/command/org_policy_label.go index fea680b44c..d38e9bc259 100644 --- a/internal/command/org_policy_label.go +++ b/internal/command/org_policy_label.go @@ -3,7 +3,6 @@ package command import ( "context" - "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/domain" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/repository/org" @@ -124,7 +123,7 @@ func (c *Commands) checkLabelPolicyAllowed(ctx context.Context, resourceOwner st if defaultPolicy.DisableWatermark != policy.DisableWatermark { requiredFeatures = append(requiredFeatures, domain.FeatureLabelPolicyWatermark) } - return authz.CheckOrgFeatures(ctx, c.tokenVerifier, resourceOwner, requiredFeatures...) + return c.tokenVerifier.CheckOrgFeatures(ctx, resourceOwner, requiredFeatures...) } func (c *Commands) ActivateLabelPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) { diff --git a/internal/command/org_policy_label_test.go b/internal/command/org_policy_label_test.go index befa74a816..c5710c9dc4 100644 --- a/internal/command/org_policy_label_test.go +++ b/internal/command/org_policy_label_test.go @@ -7,7 +7,6 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" - "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/domain" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore" @@ -23,7 +22,7 @@ import ( func TestCommandSide_AddLabelPolicy(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore - tokenVerifier *authz.TokenVerifier + tokenVerifier orgFeatureChecker } type args struct { ctx context.Context @@ -260,7 +259,7 @@ func TestCommandSide_AddLabelPolicy(t *testing.T) { func TestCommandSide_ChangeLabelPolicy(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore - tokenVerifier *authz.TokenVerifier + tokenVerifier orgFeatureChecker } type args struct { ctx context.Context diff --git a/internal/command/org_policy_login.go b/internal/command/org_policy_login.go index 88e19d486a..bf58acd023 100644 --- a/internal/command/org_policy_login.go +++ b/internal/command/org_policy_login.go @@ -6,7 +6,6 @@ import ( "github.com/caos/logging" - "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/domain" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore" @@ -131,7 +130,7 @@ func (c *Commands) checkLoginPolicyAllowed(ctx context.Context, resourceOwner st if defaultPolicy.HidePasswordReset != policy.HidePasswordReset { requiredFeatures = append(requiredFeatures, domain.FeatureLoginPolicyPasswordReset) } - return authz.CheckOrgFeatures(ctx, c.tokenVerifier, resourceOwner, requiredFeatures...) + return c.tokenVerifier.CheckOrgFeatures(ctx, resourceOwner, requiredFeatures...) } func (c *Commands) RemoveLoginPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) { diff --git a/internal/command/org_policy_login_test.go b/internal/command/org_policy_login_test.go index e5d364554e..343fb3ab7c 100644 --- a/internal/command/org_policy_login_test.go +++ b/internal/command/org_policy_login_test.go @@ -6,7 +6,6 @@ import ( "github.com/stretchr/testify/assert" - "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/domain" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore" @@ -21,7 +20,7 @@ import ( func TestCommandSide_AddLoginPolicy(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore - tokenVerifier *authz.TokenVerifier + tokenVerifier orgFeatureChecker } type args struct { ctx context.Context @@ -217,7 +216,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) { func TestCommandSide_ChangeLoginPolicy(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore - tokenVerifier *authz.TokenVerifier + tokenVerifier orgFeatureChecker } type args struct { ctx context.Context diff --git a/internal/eventstore/handler/crdb/statement.go b/internal/eventstore/handler/crdb/statement.go index 2ecf5b615b..e0a06a776b 100644 --- a/internal/eventstore/handler/crdb/statement.go +++ b/internal/eventstore/handler/crdb/statement.go @@ -181,6 +181,26 @@ func AddDeleteStatement(conditions []handler.Condition, opts ...execOption) func } } +func NewArrayAppendCol(column string, value interface{}) handler.Column { + return handler.Column{ + Name: column, + Value: value, + ParameterOpt: func(placeholder string) string { + return "array_append(" + column + ", " + placeholder + ")" + }, + } +} + +func NewArrayRemoveCol(column string, value interface{}) handler.Column { + return handler.Column{ + Name: column, + Value: value, + ParameterOpt: func(placeholder string) string { + return "array_remove(" + column + ", " + placeholder + ")" + }, + } +} + func columnsToQuery(cols []handler.Column) (names []string, parameters []string, values []interface{}) { names = make([]string, len(cols)) values = make([]interface{}, len(cols)) @@ -189,7 +209,9 @@ func columnsToQuery(cols []handler.Column) (names []string, parameters []string, names[i] = col.Name values[i] = col.Value parameters[i] = "$" + strconv.Itoa(i+1) - + if col.ParameterOpt != nil { + parameters[i] = col.ParameterOpt(parameters[i]) + } } return names, parameters, values } diff --git a/internal/eventstore/handler/crdb/statement_test.go b/internal/eventstore/handler/crdb/statement_test.go index 3558a6a7ef..57b6e07db9 100644 --- a/internal/eventstore/handler/crdb/statement_test.go +++ b/internal/eventstore/handler/crdb/statement_test.go @@ -1015,3 +1015,46 @@ func Test_columnsToWhere(t *testing.T) { }) } } + +func TestParameterOpts(t *testing.T) { + type args struct { + column string + value interface{} + placeholder string + } + tests := []struct { + name string + args args + constructor func(column string, value interface{}) handler.Column + want string + }{ + { + name: "NewArrayAppendCol", + args: args{ + column: "testCol", + value: "val", + placeholder: "$1", + }, + constructor: NewArrayAppendCol, + want: "array_append(testCol, $1)", + }, + { + name: "NewArrayRemoveCol", + args: args{ + column: "testCol", + value: "val", + placeholder: "$1", + }, + constructor: NewArrayRemoveCol, + want: "array_remove(testCol, $1)", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + col := tt.constructor(tt.args.column, tt.args.value) + if param := col.ParameterOpt(tt.args.placeholder); param != tt.want { + t.Errorf("constructor() = %v, want %v", param, tt.want) + } + }) + } +} diff --git a/internal/eventstore/handler/statement.go b/internal/eventstore/handler/statement.go index c2369878b8..887d050eb6 100644 --- a/internal/eventstore/handler/statement.go +++ b/internal/eventstore/handler/statement.go @@ -2,8 +2,11 @@ package handler import ( "database/sql" + "encoding/json" "errors" + "github.com/caos/logging" + "github.com/caos/zitadel/internal/eventstore" ) @@ -14,6 +17,12 @@ var ( ErrSomeStmtsFailed = errors.New("some statements failed") ) +type Statements []Statement + +func (stmts Statements) Len() int { return len(stmts) } +func (stmts Statements) Swap(i, j int) { stmts[i], stmts[j] = stmts[j], stmts[i] } +func (stmts Statements) Less(i, j int) bool { return stmts[i].Sequence < stmts[j].Sequence } + type Statement struct { AggregateType eventstore.AggregateType Sequence uint64 @@ -31,8 +40,9 @@ type Executer interface { } type Column struct { - Name string - Value interface{} + Name string + Value interface{} + ParameterOpt func(string) string } func NewCol(name string, value interface{}) Column { @@ -42,6 +52,15 @@ func NewCol(name string, value interface{}) Column { } } +func NewJSONCol(name string, value interface{}) Column { + marshalled, err := json.Marshal(value) + if err != nil { + logging.LogWithFields("HANDL-oFvsl", "column", name).WithError(err).Panic("unable to marshal column") + } + + return NewCol(name, marshalled) +} + type Condition Column func NewCond(name string, value interface{}) Condition { diff --git a/internal/management/repository/eventsourcing/eventstore/org.go b/internal/management/repository/eventsourcing/eventstore/org.go index d852728b55..f9fbb58c9c 100644 --- a/internal/management/repository/eventsourcing/eventstore/org.go +++ b/internal/management/repository/eventsourcing/eventstore/org.go @@ -25,7 +25,6 @@ import ( "github.com/caos/zitadel/internal/i18n" iam_model "github.com/caos/zitadel/internal/iam/model" iam_view "github.com/caos/zitadel/internal/iam/repository/view" - iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model" iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model" mgmt_view "github.com/caos/zitadel/internal/management/repository/eventsourcing/view" org_model "github.com/caos/zitadel/internal/org/model" @@ -65,22 +64,6 @@ func (repo *OrgRepository) Languages(ctx context.Context) ([]language.Tag, error return repo.supportedLangs, nil } -func (repo *OrgRepository) OrgByID(ctx context.Context, id string) (*org_model.OrgView, error) { - org, err := repo.View.OrgByID(id) - if err != nil { - return nil, err - } - return model.OrgToModel(org), nil -} - -func (repo *OrgRepository) OrgByDomainGlobal(ctx context.Context, domain string) (*org_model.OrgView, error) { - verifiedDomain, err := repo.View.VerifiedOrgDomain(domain) - if err != nil { - return nil, err - } - return repo.OrgByID(ctx, verifiedDomain.OrgID) -} - func (repo *OrgRepository) GetMyOrgIamPolicy(ctx context.Context) (*iam_model.OrgIAMPolicyView, error) { policy, err := repo.View.OrgIAMPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) if errors.IsNotFound(err) { @@ -93,7 +76,7 @@ func (repo *OrgRepository) GetMyOrgIamPolicy(ctx context.Context) (*iam_model.Or if err != nil { return nil, err } - return iam_es_model.OrgIAMViewToModel(policy), err + return iam_view_model.OrgIAMViewToModel(policy), err } func (repo *OrgRepository) SearchMyOrgDomains(ctx context.Context, request *org_model.OrgDomainSearchRequest) (*org_model.OrgDomainSearchResponse, error) { @@ -233,7 +216,7 @@ func (repo *OrgRepository) GetLabelPolicy(ctx context.Context) (*iam_model.Label if err != nil { return nil, err } - return iam_es_model.LabelPolicyViewToModel(policy), err + return iam_view_model.LabelPolicyViewToModel(policy), err } func (repo *OrgRepository) GetPreviewLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) { @@ -248,7 +231,7 @@ func (repo *OrgRepository) GetPreviewLabelPolicy(ctx context.Context) (*iam_mode if err != nil { return nil, err } - return iam_es_model.LabelPolicyViewToModel(policy), err + return iam_view_model.LabelPolicyViewToModel(policy), err } func (repo *OrgRepository) GetDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) { @@ -265,7 +248,7 @@ func (repo *OrgRepository) getDefaultLabelPolicy(ctx context.Context, state doma return nil, viewErr } if errors.IsNotFound(viewErr) { - policy = new(iam_es_model.LabelPolicyView) + policy = new(iam_view_model.LabelPolicyView) } events, esErr := repo.getIAMEvents(ctx, policy.Sequence) if errors.IsNotFound(viewErr) && len(events) == 0 { @@ -273,16 +256,16 @@ func (repo *OrgRepository) getDefaultLabelPolicy(ctx context.Context, state doma } if esErr != nil { logging.Log("EVENT-28uLp").WithError(esErr).Debug("error retrieving new events") - return iam_es_model.LabelPolicyViewToModel(policy), nil + return iam_view_model.LabelPolicyViewToModel(policy), nil } policyCopy := *policy for _, event := range events { if err := policyCopy.AppendEvent(event); err != nil { - return iam_es_model.LabelPolicyViewToModel(policy), nil + return iam_view_model.LabelPolicyViewToModel(policy), nil } } policy.Default = true - return iam_es_model.LabelPolicyViewToModel(policy), nil + return iam_view_model.LabelPolicyViewToModel(policy), nil } func (repo *OrgRepository) GetLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) { @@ -291,7 +274,7 @@ func (repo *OrgRepository) GetLoginPolicy(ctx context.Context) (*iam_model.Login return nil, viewErr } if errors.IsNotFound(viewErr) { - policy = new(iam_es_model.LoginPolicyView) + policy = new(iam_view_model.LoginPolicyView) } events, esErr := repo.getOrgEvents(ctx, repo.SystemDefaults.IamID, policy.Sequence) if errors.IsNotFound(viewErr) && len(events) == 0 { @@ -299,15 +282,15 @@ func (repo *OrgRepository) GetLoginPolicy(ctx context.Context) (*iam_model.Login } if esErr != nil { logging.Log("EVENT-38iTr").WithError(esErr).Debug("error retrieving new events") - return iam_es_model.LoginPolicyViewToModel(policy), nil + return iam_view_model.LoginPolicyViewToModel(policy), nil } policyCopy := *policy for _, event := range events { if err := policyCopy.AppendEvent(event); err != nil { - return iam_es_model.LoginPolicyViewToModel(policy), nil + return iam_view_model.LoginPolicyViewToModel(policy), nil } } - return iam_es_model.LoginPolicyViewToModel(policy), nil + return iam_view_model.LoginPolicyViewToModel(policy), nil } func (repo *OrgRepository) GetIDPProvidersByIDPConfigID(ctx context.Context, aggregateID, idpConfigID string) ([]*iam_model.IDPProviderView, error) { @@ -324,7 +307,7 @@ func (repo *OrgRepository) GetDefaultLoginPolicy(ctx context.Context) (*iam_mode return nil, viewErr } if errors.IsNotFound(viewErr) { - policy = new(iam_es_model.LoginPolicyView) + policy = new(iam_view_model.LoginPolicyView) } events, esErr := repo.getIAMEvents(ctx, policy.Sequence) if errors.IsNotFound(viewErr) && len(events) == 0 { @@ -332,16 +315,16 @@ func (repo *OrgRepository) GetDefaultLoginPolicy(ctx context.Context) (*iam_mode } if esErr != nil { logging.Log("EVENT-28uLp").WithError(esErr).Debug("error retrieving new events") - return iam_es_model.LoginPolicyViewToModel(policy), nil + return iam_view_model.LoginPolicyViewToModel(policy), nil } policyCopy := *policy for _, event := range events { if err := policyCopy.AppendEvent(event); err != nil { - return iam_es_model.LoginPolicyViewToModel(policy), nil + return iam_view_model.LoginPolicyViewToModel(policy), nil } } policy.Default = true - return iam_es_model.LoginPolicyViewToModel(policy), nil + return iam_view_model.LoginPolicyViewToModel(policy), nil } func (repo *OrgRepository) SearchIDPProviders(ctx context.Context, request *iam_model.IDPProviderSearchRequest) (*iam_model.IDPProviderSearchResponse, error) { @@ -368,7 +351,7 @@ func (repo *OrgRepository) SearchIDPProviders(ctx context.Context, request *iam_ Offset: request.Offset, Limit: request.Limit, TotalResult: count, - Result: iam_es_model.IDPProviderViewsToModel(providers), + Result: iam_view_model.IDPProviderViewsToModel(providers), } if sequenceErr == nil { result.Sequence = sequence.CurrentSequence @@ -405,7 +388,7 @@ func (repo *OrgRepository) GetPasswordComplexityPolicy(ctx context.Context) (*ia return nil, viewErr } if errors.IsNotFound(viewErr) { - policy = new(iam_es_model.PasswordComplexityPolicyView) + policy = new(iam_view_model.PasswordComplexityPolicyView) } events, esErr := repo.getOrgEvents(ctx, repo.SystemDefaults.IamID, policy.Sequence) if errors.IsNotFound(viewErr) && len(events) == 0 { @@ -413,15 +396,15 @@ func (repo *OrgRepository) GetPasswordComplexityPolicy(ctx context.Context) (*ia } if esErr != nil { logging.Log("EVENT-1Bx8s").WithError(esErr).Debug("error retrieving new events") - return iam_es_model.PasswordComplexityViewToModel(policy), nil + return iam_view_model.PasswordComplexityViewToModel(policy), nil } policyCopy := *policy for _, event := range events { if err := policyCopy.AppendEvent(event); err != nil { - return iam_es_model.PasswordComplexityViewToModel(policy), nil + return iam_view_model.PasswordComplexityViewToModel(policy), nil } } - return iam_es_model.PasswordComplexityViewToModel(policy), nil + return iam_view_model.PasswordComplexityViewToModel(policy), nil } func (repo *OrgRepository) GetDefaultPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error) { @@ -430,7 +413,7 @@ func (repo *OrgRepository) GetDefaultPasswordComplexityPolicy(ctx context.Contex return nil, viewErr } if errors.IsNotFound(viewErr) { - policy = new(iam_es_model.PasswordComplexityPolicyView) + policy = new(iam_view_model.PasswordComplexityPolicyView) } events, esErr := repo.getIAMEvents(ctx, policy.Sequence) if errors.IsNotFound(viewErr) && len(events) == 0 { @@ -438,16 +421,16 @@ func (repo *OrgRepository) GetDefaultPasswordComplexityPolicy(ctx context.Contex } if esErr != nil { logging.Log("EVENT-pL9sw").WithError(esErr).Debug("error retrieving new events") - return iam_es_model.PasswordComplexityViewToModel(policy), nil + return iam_view_model.PasswordComplexityViewToModel(policy), nil } policyCopy := *policy for _, event := range events { if err := policyCopy.AppendEvent(event); err != nil { - return iam_es_model.PasswordComplexityViewToModel(policy), nil + return iam_view_model.PasswordComplexityViewToModel(policy), nil } } policy.Default = true - return iam_es_model.PasswordComplexityViewToModel(policy), nil + return iam_view_model.PasswordComplexityViewToModel(policy), nil } func (repo *OrgRepository) GetPasswordAgePolicy(ctx context.Context) (*iam_model.PasswordAgePolicyView, error) { @@ -456,7 +439,7 @@ func (repo *OrgRepository) GetPasswordAgePolicy(ctx context.Context) (*iam_model return nil, viewErr } if errors.IsNotFound(viewErr) { - policy = new(iam_es_model.PasswordAgePolicyView) + policy = new(iam_view_model.PasswordAgePolicyView) } events, esErr := repo.getOrgEvents(ctx, repo.SystemDefaults.IamID, policy.Sequence) if errors.IsNotFound(viewErr) && len(events) == 0 { @@ -464,15 +447,15 @@ func (repo *OrgRepository) GetPasswordAgePolicy(ctx context.Context) (*iam_model } if esErr != nil { logging.Log("EVENT-5Mx7s").WithError(esErr).Debug("error retrieving new events") - return iam_es_model.PasswordAgeViewToModel(policy), nil + return iam_view_model.PasswordAgeViewToModel(policy), nil } policyCopy := *policy for _, event := range events { if err := policyCopy.AppendEvent(event); err != nil { - return iam_es_model.PasswordAgeViewToModel(policy), nil + return iam_view_model.PasswordAgeViewToModel(policy), nil } } - return iam_es_model.PasswordAgeViewToModel(policy), nil + return iam_view_model.PasswordAgeViewToModel(policy), nil } func (repo *OrgRepository) GetDefaultPasswordAgePolicy(ctx context.Context) (*iam_model.PasswordAgePolicyView, error) { @@ -481,7 +464,7 @@ func (repo *OrgRepository) GetDefaultPasswordAgePolicy(ctx context.Context) (*ia return nil, viewErr } if errors.IsNotFound(viewErr) { - policy = new(iam_es_model.PasswordAgePolicyView) + policy = new(iam_view_model.PasswordAgePolicyView) } events, esErr := repo.getIAMEvents(ctx, policy.Sequence) if errors.IsNotFound(viewErr) && len(events) == 0 { @@ -489,16 +472,16 @@ func (repo *OrgRepository) GetDefaultPasswordAgePolicy(ctx context.Context) (*ia } if esErr != nil { logging.Log("EVENT-3I90s").WithError(esErr).Debug("error retrieving new events") - return iam_es_model.PasswordAgeViewToModel(policy), nil + return iam_view_model.PasswordAgeViewToModel(policy), nil } policyCopy := *policy for _, event := range events { if err := policyCopy.AppendEvent(event); err != nil { - return iam_es_model.PasswordAgeViewToModel(policy), nil + return iam_view_model.PasswordAgeViewToModel(policy), nil } } policy.Default = true - return iam_es_model.PasswordAgeViewToModel(policy), nil + return iam_view_model.PasswordAgeViewToModel(policy), nil } func (repo *OrgRepository) GetLockoutPolicy(ctx context.Context) (*iam_model.LockoutPolicyView, error) { @@ -507,7 +490,7 @@ func (repo *OrgRepository) GetLockoutPolicy(ctx context.Context) (*iam_model.Loc return nil, viewErr } if errors.IsNotFound(viewErr) { - policy = new(iam_es_model.LockoutPolicyView) + policy = new(iam_view_model.LockoutPolicyView) } events, esErr := repo.getOrgEvents(ctx, repo.SystemDefaults.IamID, policy.Sequence) if errors.IsNotFound(viewErr) && len(events) == 0 { @@ -515,15 +498,15 @@ func (repo *OrgRepository) GetLockoutPolicy(ctx context.Context) (*iam_model.Loc } if esErr != nil { logging.Log("EVENT-mS9od").WithError(esErr).Debug("error retrieving new events") - return iam_es_model.LockoutViewToModel(policy), nil + return iam_view_model.LockoutViewToModel(policy), nil } policyCopy := *policy for _, event := range events { if err := policyCopy.AppendEvent(event); err != nil { - return iam_es_model.LockoutViewToModel(policy), nil + return iam_view_model.LockoutViewToModel(policy), nil } } - return iam_es_model.LockoutViewToModel(policy), nil + return iam_view_model.LockoutViewToModel(policy), nil } func (repo *OrgRepository) GetDefaultLockoutPolicy(ctx context.Context) (*iam_model.LockoutPolicyView, error) { @@ -532,7 +515,7 @@ func (repo *OrgRepository) GetDefaultLockoutPolicy(ctx context.Context) (*iam_mo return nil, viewErr } if errors.IsNotFound(viewErr) { - policy = new(iam_es_model.LockoutPolicyView) + policy = new(iam_view_model.LockoutPolicyView) } events, esErr := repo.getIAMEvents(ctx, policy.Sequence) if errors.IsNotFound(viewErr) && len(events) == 0 { @@ -540,16 +523,16 @@ func (repo *OrgRepository) GetDefaultLockoutPolicy(ctx context.Context) (*iam_mo } if esErr != nil { logging.Log("EVENT-2Ms9f").WithError(esErr).Debug("error retrieving new events") - return iam_es_model.LockoutViewToModel(policy), nil + return iam_view_model.LockoutViewToModel(policy), nil } policyCopy := *policy for _, event := range events { if err := policyCopy.AppendEvent(event); err != nil { - return iam_es_model.LockoutViewToModel(policy), nil + return iam_view_model.LockoutViewToModel(policy), nil } } policy.Default = true - return iam_es_model.LockoutViewToModel(policy), nil + return iam_view_model.LockoutViewToModel(policy), nil } func (repo *OrgRepository) GetPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error) { @@ -557,7 +540,7 @@ func (repo *OrgRepository) GetPrivacyPolicy(ctx context.Context) (*iam_model.Pri if errors.IsNotFound(err) { return repo.GetDefaultPrivacyPolicy(ctx) } - return iam_es_model.PrivacyViewToModel(policy), nil + return iam_view_model.PrivacyViewToModel(policy), nil } func (repo *OrgRepository) GetDefaultPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error) { @@ -566,7 +549,7 @@ func (repo *OrgRepository) GetDefaultPrivacyPolicy(ctx context.Context) (*iam_mo return nil, err } policy.Default = true - return iam_es_model.PrivacyViewToModel(policy), nil + return iam_view_model.PrivacyViewToModel(policy), nil } func (repo *OrgRepository) GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) { @@ -575,7 +558,7 @@ func (repo *OrgRepository) GetDefaultMailTemplate(ctx context.Context) (*iam_mod return nil, err } template.Default = true - return iam_es_model.MailTemplateViewToModel(template), err + return iam_view_model.MailTemplateViewToModel(template), err } func (repo *OrgRepository) GetMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) { @@ -590,7 +573,7 @@ func (repo *OrgRepository) GetMailTemplate(ctx context.Context) (*iam_model.Mail if err != nil { return nil, err } - return iam_es_model.MailTemplateViewToModel(template), err + return iam_view_model.MailTemplateViewToModel(template), err } func (repo *OrgRepository) GetDefaultMessageText(ctx context.Context, textType, lang string) (*domain.CustomMessageText, error) { @@ -644,7 +627,7 @@ func (repo *OrgRepository) GetMessageText(ctx context.Context, orgID, textType, if len(texts) == 0 { return repo.GetDefaultMessageText(ctx, textType, lang) } - return iam_es_model.CustomTextViewsToMessageDomain(repo.SystemDefaults.IamID, lang, texts), err + return iam_view_model.CustomTextViewsToMessageDomain(repo.SystemDefaults.IamID, lang, texts), err } func (repo *OrgRepository) GetDefaultLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error) { @@ -694,7 +677,7 @@ func (repo *OrgRepository) GetLoginTexts(ctx context.Context, orgID, lang string if err != nil { return nil, err } - return iam_es_model.CustomTextViewsToLoginDomain(repo.SystemDefaults.IamID, lang, texts), err + return iam_view_model.CustomTextViewsToLoginDomain(repo.SystemDefaults.IamID, lang, texts), err } func (repo *OrgRepository) getOrgChanges(ctx context.Context, orgID string, lastSequence uint64, limit uint64, sortAscending bool, auditLogRetention time.Duration) (*org_model.OrgChanges, error) { diff --git a/internal/management/repository/eventsourcing/eventstore/user.go b/internal/management/repository/eventsourcing/eventstore/user.go index 74e0609598..bb2b16841a 100644 --- a/internal/management/repository/eventsourcing/eventstore/user.go +++ b/internal/management/repository/eventsourcing/eventstore/user.go @@ -4,24 +4,21 @@ import ( "context" "time" - "github.com/golang/protobuf/ptypes" - - "github.com/caos/zitadel/internal/domain" - v1 "github.com/caos/zitadel/internal/eventstore/v1" - "github.com/caos/zitadel/internal/eventstore/v1/models" - iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" - usr_view "github.com/caos/zitadel/internal/user/repository/view" - "github.com/caos/logging" + "github.com/golang/protobuf/ptypes" "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/config/systemdefaults" + "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" - caos_errs "github.com/caos/zitadel/internal/errors" + v1 "github.com/caos/zitadel/internal/eventstore/v1" + "github.com/caos/zitadel/internal/eventstore/v1/models" + iam_model "github.com/caos/zitadel/internal/iam/repository/view/model" key_model "github.com/caos/zitadel/internal/key/model" key_view_model "github.com/caos/zitadel/internal/key/repository/view/model" "github.com/caos/zitadel/internal/management/repository/eventsourcing/view" usr_model "github.com/caos/zitadel/internal/user/model" + usr_view "github.com/caos/zitadel/internal/user/repository/view" "github.com/caos/zitadel/internal/user/repository/view/model" "github.com/caos/zitadel/internal/view/repository" ) @@ -36,16 +33,16 @@ type UserRepo struct { func (repo *UserRepo) UserByID(ctx context.Context, id string) (*usr_model.UserView, error) { user, viewErr := repo.View.UserByID(id) - if viewErr != nil && !caos_errs.IsNotFound(viewErr) { + if viewErr != nil && !errors.IsNotFound(viewErr) { return nil, viewErr } - if caos_errs.IsNotFound(viewErr) { + if errors.IsNotFound(viewErr) { user = new(model.UserView) } events, esErr := repo.getUserEvents(ctx, id, user.Sequence) - if caos_errs.IsNotFound(viewErr) && len(events) == 0 { - return nil, caos_errs.ThrowNotFound(nil, "EVENT-Lsoj7", "Errors.User.NotFound") + if errors.IsNotFound(viewErr) && len(events) == 0 { + return nil, errors.ThrowNotFound(nil, "EVENT-Lsoj7", "Errors.User.NotFound") } if esErr != nil { logging.Log("EVENT-PSoc3").WithError(esErr).Debug("error retrieving new events") @@ -58,23 +55,23 @@ func (repo *UserRepo) UserByID(ctx context.Context, id string) (*usr_model.UserV } } if userCopy.State == int32(usr_model.UserStateDeleted) { - return nil, caos_errs.ThrowNotFound(nil, "EVENT-4Fm9s", "Errors.User.NotFound") + return nil, errors.ThrowNotFound(nil, "EVENT-4Fm9s", "Errors.User.NotFound") } return model.UserToModel(&userCopy, repo.PrefixAvatarURL), nil } func (repo *UserRepo) UserByIDAndResourceOwner(ctx context.Context, id, resourceOwner string) (*usr_model.UserView, error) { user, viewErr := repo.View.UserByIDAndResourceOwner(id, resourceOwner) - if viewErr != nil && !caos_errs.IsNotFound(viewErr) { + if viewErr != nil && !errors.IsNotFound(viewErr) { return nil, viewErr } - if caos_errs.IsNotFound(viewErr) { + if errors.IsNotFound(viewErr) { user = new(model.UserView) } events, esErr := repo.getUserEvents(ctx, id, user.Sequence) - if caos_errs.IsNotFound(viewErr) && len(events) == 0 { - return nil, caos_errs.ThrowNotFound(nil, "EVENT-Lsoj7", "Errors.User.NotFound") + if errors.IsNotFound(viewErr) && len(events) == 0 { + return nil, errors.ThrowNotFound(nil, "EVENT-Lsoj7", "Errors.User.NotFound") } if esErr != nil { logging.Log("EVENT-PSoc3").WithError(esErr).Debug("error retrieving new events") @@ -87,7 +84,7 @@ func (repo *UserRepo) UserByIDAndResourceOwner(ctx context.Context, id, resource } } if userCopy.State == int32(usr_model.UserStateDeleted) || userCopy.ResourceOwner != resourceOwner { - return nil, caos_errs.ThrowNotFound(nil, "EVENT-4Fm9s", "Errors.User.NotFound") + return nil, errors.ThrowNotFound(nil, "EVENT-4Fm9s", "Errors.User.NotFound") } return model.UserToModel(&userCopy, repo.PrefixAvatarURL), nil } diff --git a/internal/management/repository/eventsourcing/handler/handler.go b/internal/management/repository/eventsourcing/handler/handler.go index 2141d1935c..d2c4ecb2b7 100644 --- a/internal/management/repository/eventsourcing/handler/handler.go +++ b/internal/management/repository/eventsourcing/handler/handler.go @@ -44,8 +44,6 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es newUser(handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es}, defaults.IamID), newUserGrant(handler{view, bulkLimit, configs.cycleDuration("UserGrant"), errorCount, es}), - newOrg( - handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount, es}), newOrgMember( handler{view, bulkLimit, configs.cycleDuration("OrgMember"), errorCount, es}), newOrgDomain( diff --git a/internal/management/repository/eventsourcing/handler/org.go b/internal/management/repository/eventsourcing/handler/org.go deleted file mode 100644 index 0f2be8e16c..0000000000 --- a/internal/management/repository/eventsourcing/handler/org.go +++ /dev/null @@ -1,99 +0,0 @@ -package handler - -import ( - "github.com/caos/logging" - "github.com/caos/zitadel/internal/eventstore/v1" - "github.com/caos/zitadel/internal/org/repository/view" - - es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "github.com/caos/zitadel/internal/eventstore/v1/query" - "github.com/caos/zitadel/internal/eventstore/v1/spooler" - "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" - org_model "github.com/caos/zitadel/internal/org/repository/view/model" -) - -const ( - orgTable = "management.orgs" -) - -type Org struct { - handler - subscription *v1.Subscription -} - -func newOrg(handler handler) *Org { - h := &Org{ - handler: handler, - } - - h.subscribe() - - return h -} - -func (m *Org) subscribe() { - m.subscription = m.es.Subscribe(m.AggregateTypes()...) - go func() { - for event := range m.subscription.Events { - query.ReduceEvent(m, event) - } - }() -} - -func (o *Org) ViewModel() string { - return orgTable -} - -func (o *Org) Subscription() *v1.Subscription { - return o.subscription -} - -func (_ *Org) AggregateTypes() []es_models.AggregateType { - return []es_models.AggregateType{model.OrgAggregate} -} - -func (o *Org) CurrentSequence() (uint64, error) { - sequence, err := o.view.GetLatestOrgSequence() - if err != nil { - return 0, err - } - return sequence.CurrentSequence, nil -} - -func (o *Org) EventQuery() (*es_models.SearchQuery, error) { - sequence, err := o.view.GetLatestOrgSequence() - if err != nil { - return nil, err - } - return view.OrgQuery(sequence.CurrentSequence), nil -} - -func (o *Org) Reduce(event *es_models.Event) (err error) { - org := new(org_model.OrgView) - - switch event.Type { - case model.OrgAdded: - err = org.AppendEvent(event) - case model.OrgChanged: - org, err = o.view.OrgByID(event.ResourceOwner) - if err != nil { - return err - } - err = org.AppendEvent(event) - default: - return o.view.ProcessedOrgSequence(event) - } - if err != nil { - return err - } - return o.view.PutOrg(org, event) -} - -func (o *Org) OnError(event *es_models.Event, spoolerErr error) error { - logging.LogWithFields("SPOOL-ls9ew", "id", event.AggregateID).WithError(spoolerErr).Warn("something went wrong in project app handler") - return spooler.HandleError(event, spoolerErr, o.view.GetLatestOrgFailedEvent, o.view.ProcessedOrgFailedEvent, o.view.ProcessedOrgSequence, o.errorCountUntilSkip) -} - -func (o *Org) OnSuccess() error { - return spooler.HandleSuccess(o.view.UpdateOrgSpoolerRunTimestamp) -} diff --git a/internal/management/repository/eventsourcing/view/org.go b/internal/management/repository/eventsourcing/view/org.go deleted file mode 100644 index 5e45712351..0000000000 --- a/internal/management/repository/eventsourcing/view/org.go +++ /dev/null @@ -1,44 +0,0 @@ -package view - -import ( - "github.com/caos/zitadel/internal/eventstore/v1/models" - org_view "github.com/caos/zitadel/internal/org/repository/view" - "github.com/caos/zitadel/internal/org/repository/view/model" - "github.com/caos/zitadel/internal/view/repository" -) - -const ( - orgTable = "management.orgs" -) - -func (v *View) OrgByID(orgID string) (*model.OrgView, error) { - return org_view.OrgByID(v.Db, orgTable, orgID) -} - -func (v *View) PutOrg(org *model.OrgView, event *models.Event) error { - err := org_view.PutOrg(v.Db, orgTable, org) - if err != nil { - return err - } - return v.ProcessedOrgSequence(event) -} - -func (v *View) GetLatestOrgFailedEvent(sequence uint64) (*repository.FailedEvent, error) { - return v.latestFailedEvent(orgTable, sequence) -} - -func (v *View) ProcessedOrgFailedEvent(failedEvent *repository.FailedEvent) error { - return v.saveFailedEvent(failedEvent) -} - -func (v *View) UpdateOrgSpoolerRunTimestamp() error { - return v.updateSpoolerRunSequence(orgTable) -} - -func (v *View) GetLatestOrgSequence() (*repository.CurrentSequence, error) { - return v.latestSequence(orgTable) -} - -func (v *View) ProcessedOrgSequence(event *models.Event) error { - return v.saveCurrentSequence(orgTable, event) -} diff --git a/internal/management/repository/org.go b/internal/management/repository/org.go index 5661730285..7a528e2a92 100644 --- a/internal/management/repository/org.go +++ b/internal/management/repository/org.go @@ -14,8 +14,6 @@ import ( type OrgRepository interface { Languages(ctx context.Context) ([]language.Tag, error) - OrgByID(ctx context.Context, id string) (*org_model.OrgView, error) - OrgByDomainGlobal(ctx context.Context, domain string) (*org_model.OrgView, error) OrgChanges(ctx context.Context, id string, lastSequence uint64, limit uint64, sortAscending bool, auditLogRetention time.Duration) (*org_model.OrgChanges, error) SearchMyOrgDomains(ctx context.Context, request *org_model.OrgDomainSearchRequest) (*org_model.OrgDomainSearchResponse, error) diff --git a/internal/org/repository/view/org_view.go b/internal/org/repository/view/org_view.go deleted file mode 100644 index 46706aae65..0000000000 --- a/internal/org/repository/view/org_view.go +++ /dev/null @@ -1,50 +0,0 @@ -package view - -import ( - "github.com/jinzhu/gorm" - - caos_errs "github.com/caos/zitadel/internal/errors" - org_model "github.com/caos/zitadel/internal/org/model" - "github.com/caos/zitadel/internal/org/repository/view/model" - "github.com/caos/zitadel/internal/view/repository" -) - -func OrgByID(db *gorm.DB, table, orgID string) (*model.OrgView, error) { - org := new(model.OrgView) - query := repository.PrepareGetByKey(table, model.OrgSearchKey(org_model.OrgSearchKeyOrgID), orgID) - err := query(db, org) - if caos_errs.IsNotFound(err) { - return nil, caos_errs.ThrowNotFound(nil, "VIEW-GEwea", "Errors.Org.NotFound") - } - return org, err -} - -func OrgByPrimaryDomain(db *gorm.DB, table, primaryDomain string) (*model.OrgView, error) { - org := new(model.OrgView) - query := repository.PrepareGetByKey(table, model.OrgSearchKey(org_model.OrgSearchKeyOrgDomain), primaryDomain) - err := query(db, org) - if caos_errs.IsNotFound(err) { - return nil, caos_errs.ThrowNotFound(nil, "VIEW-GEwea", "Errors.Org.NotFound") - } - return org, err -} - -func SearchOrgs(db *gorm.DB, table string, req *org_model.OrgSearchRequest) ([]*model.OrgView, uint64, error) { - orgs := make([]*model.OrgView, 0) - query := repository.PrepareSearchQuery(table, model.OrgSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries, SortingColumn: req.SortingColumn, Asc: req.Asc}) - count, err := query(db, &orgs) - if err != nil { - return nil, 0, err - } - return orgs, count, nil -} - -func PutOrg(db *gorm.DB, table string, org *model.OrgView) error { - save := repository.PrepareSave(table) - return save(db, org) -} - -func DeleteOrg(db *gorm.DB, table, orgID string) error { - delete := repository.PrepareDeleteByKey(table, model.OrgSearchKey(org_model.OrgSearchKeyOrgID), orgID) - return delete(db) -} diff --git a/internal/query/action.go b/internal/query/action.go index 191f966b04..311969206c 100644 --- a/internal/query/action.go +++ b/internal/query/action.go @@ -2,76 +2,80 @@ package query import ( "context" + "database/sql" + errs "errors" "time" - "github.com/Masterminds/squirrel" + sq "github.com/Masterminds/squirrel" + "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/query/projection" ) -var actionsQuery = squirrel.StatementBuilder.Select("creation_date", "change_date", "resource_owner", "sequence", "id", "action_state", "name", "script", "timeout", "allowed_to_fail"). - From("zitadel.projections.actions").PlaceholderFormat(squirrel.Dollar) - -func (q *Queries) GetAction(ctx context.Context, id string, orgID string) (*Action, error) { - idQuery, _ := newActionIDSearchQuery(id) - actions, err := q.SearchActions(ctx, &ActionSearchQueries{Queries: []SearchQuery{idQuery}}) - if err != nil { - return nil, err +var ( + actionTable = table{ + name: projection.ActionTable, } - if len(actions) != 1 { - return nil, errors.ThrowNotFound(nil, "QUERY-dft2g", "Errors.Action.NotFound") + ActionColumnID = Column{ + name: projection.ActionIDCol, + table: actionTable, } - return actions[0], err -} - -func (q *Queries) SearchActions(ctx context.Context, query *ActionSearchQueries) ([]*Action, error) { - stmt, args, err := query.ToQuery(actionsQuery).ToSql() - if err != nil { - return nil, errors.ThrowInvalidArgument(err, "QUERY-wQ3by", "Errors.orgs.invalid.request") + ActionColumnCreationDate = Column{ + name: projection.ActionCreationDateCol, + table: actionTable, } - - rows, err := q.client.QueryContext(ctx, stmt, args...) - if err != nil { - return nil, errors.ThrowInternal(err, "QUERY-M6mYN", "Errors.orgs.internal") + ActionColumnChangeDate = Column{ + name: projection.ActionChangeDateCol, + table: actionTable, } - - actions := []*Action{} - for rows.Next() { - org := new(Action) - rows.Scan( - &org.CreationDate, - &org.ChangeDate, - &org.ResourceOwner, - &org.Sequence, - &org.ID, - &org.State, - &org.Name, - &org.Script, - &org.Timeout, - &org.AllowedToFail, - ) - actions = append(actions, org) + ActionColumnResourceOwner = Column{ + name: projection.ActionResourceOwnerCol, + table: actionTable, } - - if err := rows.Err(); err != nil { - return nil, errors.ThrowInternal(err, "QUERY-pA0Wj", "Errors.actions.internal") + ActionColumnSequence = Column{ + name: projection.ActionSequenceCol, + table: actionTable, } + ActionColumnState = Column{ + name: projection.ActionStateCol, + table: actionTable, + } + ActionColumnName = Column{ + name: projection.ActionNameCol, + table: actionTable, + } + ActionColumnScript = Column{ + name: projection.ActionScriptCol, + table: actionTable, + } + ActionColumnTimeout = Column{ + name: projection.ActionTimeoutCol, + table: actionTable, + } + ActionColumnAllowedToFail = Column{ + name: projection.ActionAllowedToFailCol, + table: actionTable, + } +) - return actions, nil +type Actions struct { + SearchResponse + Actions []*Action } type Action struct { - ID string `col:"id"` - CreationDate time.Time `col:"creation_date"` - ChangeDate time.Time `col:"change_date"` - ResourceOwner string `col:"resource_owner"` - State domain.ActionState `col:"action_state"` - Sequence uint64 `col:"sequence"` + ID string + CreationDate time.Time + ChangeDate time.Time + ResourceOwner string + State domain.ActionState + Sequence uint64 - Name string `col:"name"` - Script string `col:"script"` - Timeout time.Duration `col:"-"` - AllowedToFail bool `col:"-"` + Name string + Script string + Timeout time.Duration + AllowedToFail bool } type ActionSearchQueries struct { @@ -79,26 +83,146 @@ type ActionSearchQueries struct { Queries []SearchQuery } -func (q *ActionSearchQueries) ToQuery(query squirrel.SelectBuilder) squirrel.SelectBuilder { - query = q.SearchRequest.ToQuery(query) +func (q *ActionSearchQueries) ToQuery(query sq.SelectBuilder) sq.SelectBuilder { + query = q.SearchRequest.toQuery(query) for _, q := range q.Queries { query = q.ToQuery(query) } return query } +func (q *Queries) SearchActions(ctx context.Context, queries *ActionSearchQueries) (actions *Actions, err error) { + query, scan := prepareActionsQuery() + stmt, args, err := queries.toQuery(query).ToSql() + if err != nil { + return nil, errors.ThrowInvalidArgument(err, "QUERY-SDgwg", "Errors.Query.InvalidRequest") + } + + rows, err := q.client.QueryContext(ctx, stmt, args...) + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-SDfr52", "Errors.Internal") + } + actions, err = scan(rows) + if err != nil { + return nil, err + } + actions.LatestSequence, err = q.latestSequence(ctx, actionTable) + return actions, err +} + +func (q *Queries) GetActionByID(ctx context.Context, id string, orgID string) (*Action, error) { + stmt, scan := prepareActionQuery() + query, args, err := stmt.Where( + sq.Eq{ + ActionColumnID.identifier(): id, + }, + sq.Eq{ + ActionColumnResourceOwner.identifier(): orgID, + }).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-Dgff3", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, query, args...) + return scan(row) +} + func NewActionResourceOwnerQuery(id string) (SearchQuery, error) { - return NewTextQuery("resource_owner", id, TextEquals) + return NewTextQuery(ActionColumnResourceOwner, id, TextEquals) } func NewActionNameSearchQuery(method TextComparison, value string) (SearchQuery, error) { - return NewTextQuery("name", value, method) + return NewTextQuery(ActionColumnName, value, method) } func NewActionStateSearchQuery(value domain.ActionState) (SearchQuery, error) { - return NewIntQuery("state", int(value), IntEquals) + return NewNumberQuery(ActionColumnState, int(value), NumberEquals) } -func newActionIDSearchQuery(id string) (SearchQuery, error) { - return NewTextQuery("id", id, TextEquals) +func prepareActionsQuery() (sq.SelectBuilder, func(rows *sql.Rows) (*Actions, error)) { + return sq.Select( + ActionColumnID.identifier(), + ActionColumnCreationDate.identifier(), + ActionColumnChangeDate.identifier(), + ActionColumnResourceOwner.identifier(), + ActionColumnSequence.identifier(), + ActionColumnState.identifier(), + ActionColumnName.identifier(), + ActionColumnScript.identifier(), + ActionColumnTimeout.identifier(), + ActionColumnAllowedToFail.identifier(), + countColumn.identifier(), + ).From(actionTable.identifier()).PlaceholderFormat(sq.Dollar), + func(rows *sql.Rows) (*Actions, error) { + actions := make([]*Action, 0) + var count uint64 + for rows.Next() { + action := new(Action) + err := rows.Scan( + &action.ID, + &action.CreationDate, + &action.ChangeDate, + &action.ResourceOwner, + &action.Sequence, + &action.State, + &action.Name, + &action.Script, + &action.Timeout, + &action.AllowedToFail, + &count, + ) + if err != nil { + return nil, err + } + actions = append(actions, action) + } + + if err := rows.Close(); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-EGdff", "Errors.Query.CloseRows") + } + + return &Actions{ + Actions: actions, + SearchResponse: SearchResponse{ + Count: count, + }, + }, nil + } +} + +func prepareActionQuery() (sq.SelectBuilder, func(row *sql.Row) (*Action, error)) { + return sq.Select( + ActionColumnID.identifier(), + ActionColumnCreationDate.identifier(), + ActionColumnChangeDate.identifier(), + ActionColumnResourceOwner.identifier(), + ActionColumnSequence.identifier(), + ActionColumnState.identifier(), + ActionColumnName.identifier(), + ActionColumnScript.identifier(), + ActionColumnTimeout.identifier(), + ActionColumnAllowedToFail.identifier(), + ).From(actionTable.identifier()).PlaceholderFormat(sq.Dollar), + func(row *sql.Row) (*Action, error) { + action := new(Action) + err := row.Scan( + &action.ID, + &action.CreationDate, + &action.ChangeDate, + &action.ResourceOwner, + &action.Sequence, + &action.State, + &action.Name, + &action.Script, + &action.Timeout, + &action.AllowedToFail, + ) + if err != nil { + if errs.Is(err, sql.ErrNoRows) { + return nil, errors.ThrowNotFound(err, "QUERY-GEfnb", "Errors.Action.NotFound") + } + return nil, errors.ThrowInternal(err, "QUERY-Dbnt4", "Errors.Internal") + } + return action, nil + } } diff --git a/internal/query/action_flow.go b/internal/query/action_flow.go index a391627c8d..5b656ebedb 100644 --- a/internal/query/action_flow.go +++ b/internal/query/action_flow.go @@ -2,172 +2,227 @@ package query import ( "context" + "database/sql" "time" "github.com/Masterminds/squirrel" + sq "github.com/Masterminds/squirrel" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/query/projection" ) -func (q *Queries) GetActionsByFlowAndTriggerType(ctx context.Context, flowType domain.FlowType, triggerType domain.TriggerType) ([]*Action, error) { - flowTypeQuery, _ := NewTriggerActionFlowTypeSearchQuery(flowType) - triggerTypeQuery, _ := NewTriggerActionTriggerTypeSearchQuery(triggerType) - return q.SearchActionsFromFlow(ctx, &TriggerActionSearchQueries{Queries: []SearchQuery{flowTypeQuery, triggerTypeQuery}}) +var ( + flowsTriggersTable = table{ + name: projection.FlowTriggerTable, + } + FlowsTriggersColumnFlowType = Column{ + name: projection.FlowTypeCol, + table: flowsTriggersTable, + } + FlowsTriggersColumnTriggerType = Column{ + name: projection.FlowTriggerTypeCol, + table: flowsTriggersTable, + } + FlowsTriggersColumnResourceOwner = Column{ + name: projection.FlowResourceOwnerCol, + table: flowsTriggersTable, + } + FlowsTriggersColumnTriggerSequence = Column{ + name: projection.FlowActionTriggerSequenceCol, + table: flowsTriggersTable, + } + FlowsTriggersColumnActionID = Column{ + name: projection.FlowActionIDCol, + table: flowsTriggersTable, + } +) + +type Flow struct { + ID string + CreationDate time.Time + ChangeDate time.Time + ResourceOwner string + Sequence uint64 + Type domain.FlowType + + TriggerActions map[domain.TriggerType][]*Action } -var triggerActionsQuery = squirrel.StatementBuilder.Select("creation_date", "change_date", "resource_owner", "sequence", "action_id", "name", "script", "trigger_type", "trigger_sequence"). - From("zitadel.projections.flows_actions_triggers").PlaceholderFormat(squirrel.Dollar) - -func (q *Queries) SearchActionsFromFlow(ctx context.Context, query *TriggerActionSearchQueries) ([]*Action, error) { - stmt, args, err := query.ToQuery(triggerActionsQuery).OrderBy("flow_type", "trigger_type", "trigger_sequence").ToSql() +func (q *Queries) GetFlow(ctx context.Context, flowType domain.FlowType, orgID string) (*Flow, error) { + query, scan := q.prepareFlowQuery() + stmt, args, err := query.Where( + sq.Eq{ + FlowsTriggersColumnFlowType.identifier(): flowType, + }, + sq.Eq{ + FlowsTriggersColumnResourceOwner.identifier(): orgID, + }).ToSql() if err != nil { - return nil, errors.ThrowInvalidArgument(err, "QUERY-wQ3by", "Errors.orgs.invalid.request") + return nil, errors.ThrowInvalidArgument(err, "QUERY-HBRh3", "Errors.Query.InvalidRequest") } rows, err := q.client.QueryContext(ctx, stmt, args...) if err != nil { - return nil, errors.ThrowInternal(err, "QUERY-M6mYN", "Errors.orgs.internal") + return nil, errors.ThrowInternal(err, "QUERY-Gg42f", "Errors.Internal") } - - actions := []*Action{} - for rows.Next() { - action := new(Action) - var triggerType domain.TriggerType - var triggerSequence int - rows.Scan( - &action.CreationDate, - &action.ChangeDate, - &action.ResourceOwner, - &action.Sequence, - //&action.State, //TODO: state in next release - &action.ID, - &action.Name, - &action.Script, - &triggerType, - &triggerSequence, - ) - actions = append(actions, action) - } - - if err := rows.Err(); err != nil { - return nil, errors.ThrowInternal(err, "QUERY-pA0Wj", "Errors.actions.internal") - } - - return actions, nil + return scan(rows) } -func (q *Queries) GetFlow(ctx context.Context, flowType domain.FlowType) (*Flow, error) { - flowTypeQuery, _ := NewTriggerActionFlowTypeSearchQuery(flowType) - return q.SearchFlow(ctx, &TriggerActionSearchQueries{Queries: []SearchQuery{flowTypeQuery}}) -} - -func (q *Queries) SearchFlow(ctx context.Context, query *TriggerActionSearchQueries) (*Flow, error) { - stmt, args, err := query.ToQuery(triggerActionsQuery.OrderBy("flow_type", "trigger_type", "trigger_sequence")).ToSql() +func (q *Queries) GetActionsByFlowAndTriggerType(ctx context.Context, flowType domain.FlowType, triggerType domain.TriggerType, orgID string) ([]*Action, error) { + stmt, scan := q.prepareTriggerActionsQuery() + query, args, err := stmt.Where( + sq.Eq{ + FlowsTriggersColumnFlowType.identifier(): flowType, + }, + sq.Eq{ + FlowsTriggersColumnTriggerType.identifier(): triggerType, + }, + sq.Eq{ + FlowsTriggersColumnResourceOwner.identifier(): orgID, + }).ToSql() if err != nil { - return nil, errors.ThrowInvalidArgument(err, "QUERY-wQ3by", "Errors.orgs.invalid.request") + return nil, errors.ThrowInternal(err, "QUERY-Dgff3", "Errors.Query.SQLStatement") } - rows, err := q.client.QueryContext(ctx, stmt, args...) + rows, err := q.client.QueryContext(ctx, query, args...) if err != nil { - return nil, errors.ThrowInternal(err, "QUERY-M6mYN", "Errors.orgs.internal") + return nil, errors.ThrowInternal(err, "QUERY-SDf52", "Errors.Internal") } - - flow := &Flow{ - TriggerActions: make(map[domain.TriggerType][]*Action), - } - for rows.Next() { - action := new(Action) - var triggerType domain.TriggerType - var triggerSequence int - rows.Scan( - &action.CreationDate, - &action.ChangeDate, - &action.ResourceOwner, - &action.Sequence, - //&action.State, //TODO: state in next release - &action.ID, - &action.Name, - &action.Script, - &triggerType, - &triggerSequence, - ) - - flow.TriggerActions[triggerType] = append(flow.TriggerActions[triggerType], action) - } - - if err := rows.Err(); err != nil { - return nil, errors.ThrowInternal(err, "QUERY-pA0Wj", "Errors.actions.internal") - } - - return flow, nil + return scan(rows) } func (q *Queries) GetFlowTypesOfActionID(ctx context.Context, actionID string) ([]domain.FlowType, error) { - actionIDQuery, _ := NewTriggerActionActionIDSearchQuery(actionID) - query := &TriggerActionSearchQueries{Queries: []SearchQuery{actionIDQuery}} - stmt, args, err := query.ToQuery( - squirrel.StatementBuilder. - Select("flow_type"). - From("zitadel.projections.flows_actions_triggers"). - PlaceholderFormat(squirrel.Dollar)).ToSql() + stmt, args, err := squirrel.StatementBuilder. + Select(FlowsTriggersColumnFlowType.identifier()). + From(flowsTriggersTable.identifier()). + Where(sq.Eq{ + FlowsTriggersColumnActionID.identifier(): actionID, + }). + PlaceholderFormat(squirrel.Dollar). + ToSql() if err != nil { - return nil, errors.ThrowInvalidArgument(err, "QUERY-wQ3by", "Errors.orgs.invalid.request") + return nil, errors.ThrowInvalidArgument(err, "QUERY-Dh311", "Errors.Query.InvalidRequest") } rows, err := q.client.QueryContext(ctx, stmt, args...) if err != nil { - return nil, errors.ThrowInternal(err, "QUERY-M6mYN", "Errors.orgs.internal") + return nil, errors.ThrowInternal(err, "QUERY-Bhj4w", "Errors.Internal") } flowTypes := make([]domain.FlowType, 0) for rows.Next() { - var flow_type domain.FlowType - rows.Scan( - &flow_type, + var flowType domain.FlowType + err := rows.Scan( + &flowType, ) - - flowTypes = append(flowTypes, flow_type) + if err != nil { + return nil, err + } + flowTypes = append(flowTypes, flowType) } - if err := rows.Err(); err != nil { - return nil, errors.ThrowInternal(err, "QUERY-pA0Wj", "Errors.actions.internal") + if err := rows.Close(); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-Fbgnh", "Errors.Query.CloseRows") } return flowTypes, nil } -type Flow struct { - ID string `col:"id"` - CreationDate time.Time `col:"creation_date"` - ChangeDate time.Time `col:"change_date"` - ResourceOwner string `col:"resource_owner"` - Sequence uint64 `col:"sequence"` - Type domain.FlowType `col:"flow_type"` +func (q *Queries) prepareTriggerActionsQuery() (sq.SelectBuilder, func(*sql.Rows) ([]*Action, error)) { + return sq.Select( + ActionColumnID.identifier(), + ActionColumnCreationDate.identifier(), + ActionColumnChangeDate.identifier(), + ActionColumnResourceOwner.identifier(), + //ActionColumnState.identifier(), + ActionColumnSequence.identifier(), + ActionColumnName.identifier(), + ActionColumnScript.identifier(), + FlowsTriggersColumnTriggerType.identifier(), + FlowsTriggersColumnTriggerSequence.identifier(), + ). + From(flowsTriggersTable.name). + LeftJoin(join(ActionColumnID, FlowsTriggersColumnActionID)). + PlaceholderFormat(sq.Dollar), + func(rows *sql.Rows) ([]*Action, error) { + actions := make([]*Action, 0) + for rows.Next() { + action := new(Action) + var triggerType domain.TriggerType + var triggerSequence int + err := rows.Scan( + &action.ID, + &action.CreationDate, + &action.ChangeDate, + &action.ResourceOwner, + //&action.State, //TODO: state in next release + &action.Sequence, + &action.Name, + &action.Script, + &triggerType, + &triggerSequence, + ) + if err != nil { + return nil, err + } + actions = append(actions, action) + } - TriggerActions map[domain.TriggerType][]*Action + if err := rows.Close(); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-Df42d", "Errors.Query.CloseRows") + } + + return actions, nil + } } -type TriggerActionSearchQueries struct { - SearchRequest - Queries []SearchQuery -} +func (q *Queries) prepareFlowQuery() (sq.SelectBuilder, func(*sql.Rows) (*Flow, error)) { + return sq.Select( + ActionColumnID.identifier(), + ActionColumnCreationDate.identifier(), + ActionColumnChangeDate.identifier(), + ActionColumnResourceOwner.identifier(), + //ActionColumnState.identifier(), + ActionColumnSequence.identifier(), + ActionColumnName.identifier(), + ActionColumnScript.identifier(), + FlowsTriggersColumnTriggerType.identifier(), + FlowsTriggersColumnTriggerSequence.identifier(), + ). + From(flowsTriggersTable.name). + LeftJoin(join(ActionColumnID, FlowsTriggersColumnActionID)). + PlaceholderFormat(sq.Dollar), + func(rows *sql.Rows) (*Flow, error) { + flow := &Flow{ + TriggerActions: make(map[domain.TriggerType][]*Action), + } + for rows.Next() { + action := new(Action) + var triggerType domain.TriggerType + var triggerSequence int + err := rows.Scan( + &action.ID, + &action.CreationDate, + &action.ChangeDate, + &action.ResourceOwner, + //&action.State, //TODO: state in next release + &action.Sequence, + &action.Name, + &action.Script, + &triggerType, + &triggerSequence, + ) + if err != nil { + return nil, err + } + flow.TriggerActions[triggerType] = append(flow.TriggerActions[triggerType], action) + } -func (q *TriggerActionSearchQueries) ToQuery(query squirrel.SelectBuilder) squirrel.SelectBuilder { - query = q.SearchRequest.ToQuery(query) - for _, q := range q.Queries { - query = q.ToQuery(query) - } - return query -} + if err := rows.Close(); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-Dfbe2", "Errors.Query.CloseRows") + } -func NewTriggerActionTriggerTypeSearchQuery(value domain.TriggerType) (SearchQuery, error) { - return NewIntQuery("trigger_type", int(value), IntEquals) -} - -func NewTriggerActionFlowTypeSearchQuery(value domain.FlowType) (SearchQuery, error) { - return NewIntQuery("flow_type", int(value), IntEquals) -} - -func NewTriggerActionActionIDSearchQuery(actionID string) (SearchQuery, error) { - return NewTextQuery("action_id", actionID, TextEquals) + return flow, nil + } } diff --git a/internal/query/current_sequence.go b/internal/query/current_sequence.go new file mode 100644 index 0000000000..045747f4a0 --- /dev/null +++ b/internal/query/current_sequence.go @@ -0,0 +1,75 @@ +package query + +import ( + "context" + "database/sql" + errs "errors" + "time" + + sq "github.com/Masterminds/squirrel" + + "github.com/caos/zitadel/internal/query/projection" + + "github.com/caos/zitadel/internal/errors" +) + +type LatestSequence struct { + Sequence uint64 + Timestamp time.Time +} + +func prepareLatestSequence() (sq.SelectBuilder, func(*sql.Row) (*LatestSequence, error)) { + return sq.Select( + CurrentSequenceColCurrentSequence.identifier(), + CurrentSequenceColTimestamp.identifier()). + From(currentSequencesTable.identifier()).PlaceholderFormat(sq.Dollar), + func(row *sql.Row) (*LatestSequence, error) { + seq := new(LatestSequence) + err := row.Scan( + &seq.Sequence, + &seq.Timestamp, + ) + if err != nil { + if errs.Is(err, sql.ErrNoRows) { + return nil, errors.ThrowNotFound(err, "QUERY-gmd9o", "Errors.CurrentSequence.NotFound") + } + return nil, errors.ThrowInternal(err, "QUERY-aAZ1D", "Errors.Internal") + } + return seq, nil + } +} + +func (q *Queries) latestSequence(ctx context.Context, projection table) (*LatestSequence, error) { + query, scan := prepareLatestSequence() + stmt, args, err := query.Where(sq.Eq{ + CurrentSequenceColProjectionName.identifier(): projection.name, + }).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-5CfX9", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, stmt, args...) + return scan(row) +} + +var ( + currentSequencesTable = table{ + name: projection.CurrentSeqTable, + } + CurrentSequenceColAggregateType = Column{ + name: "aggregate_type", + table: currentSequencesTable, + } + CurrentSequenceColCurrentSequence = Column{ + name: "current_sequence", + table: currentSequencesTable, + } + CurrentSequenceColTimestamp = Column{ + name: "timestamp", + table: currentSequencesTable, + } + CurrentSequenceColProjectionName = Column{ + name: "projection_name", + table: currentSequencesTable, + } +) diff --git a/internal/query/org.go b/internal/query/org.go new file mode 100644 index 0000000000..bcfd9129c3 --- /dev/null +++ b/internal/query/org.go @@ -0,0 +1,252 @@ +package query + +import ( + "context" + "database/sql" + errs "errors" + "time" + + sq "github.com/Masterminds/squirrel" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/query/projection" +) + +var ( + orgsTable = table{ + name: projection.OrgProjectionTable, + } + OrgColumnID = Column{ + name: projection.OrgColumnID, + table: orgsTable, + } + OrgColumnCreationDate = Column{ + name: projection.OrgColumnCreationDate, + table: orgsTable, + } + OrgColumnChangeDate = Column{ + name: projection.OrgColumnChangeDate, + table: orgsTable, + } + OrgColumnResourceOwner = Column{ + name: projection.OrgColumnResourceOwner, + table: orgsTable, + } + OrgColumnState = Column{ + name: projection.OrgColumnState, + table: orgsTable, + } + OrgColumnSequence = Column{ + name: projection.OrgColumnSequence, + table: orgsTable, + } + OrgColumnName = Column{ + name: projection.OrgColumnName, + table: orgsTable, + } + OrgColumnDomain = Column{ + name: projection.OrgColumnDomain, + table: orgsTable, + } +) + +type Orgs struct { + SearchResponse + Orgs []*Org +} + +type Org struct { + ID string + CreationDate time.Time + ChangeDate time.Time + ResourceOwner string + State domain.OrgState + Sequence uint64 + + Name string + Domain string +} + +type OrgSearchQueries struct { + SearchRequest + Queries []SearchQuery +} + +func (q *OrgSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { + query = q.SearchRequest.toQuery(query) + for _, q := range q.Queries { + query = q.ToQuery(query) + } + return query +} + +func (q *Queries) OrgByID(ctx context.Context, id string) (*Org, error) { + stmt, scan := prepareOrgQuery() + query, args, err := stmt.Where(sq.Eq{ + OrgColumnID.identifier(): id, + }).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-AWx52", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, query, args...) + return scan(row) +} + +func (q *Queries) OrgByDomainGlobal(ctx context.Context, domain string) (*Org, error) { + stmt, scan := prepareOrgQuery() + query, args, err := stmt.Where(sq.Eq{ + OrgColumnDomain.identifier(): domain, + }).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-TYUCE", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, query, args...) + return scan(row) +} + +func (q *Queries) IsOrgUnique(ctx context.Context, name, domain string) (isUnique bool, err error) { + query, scan := prepareOrgUniqueQuery() + stmt, args, err := query.Where( + sq.Or{ + sq.Eq{ + OrgColumnDomain.identifier(): domain, + }, + sq.Eq{ + OrgColumnName.identifier(): name, + }, + }).ToSql() + if err != nil { + return false, errors.ThrowInternal(err, "QUERY-Dgbe2", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, stmt, args...) + return scan(row) +} + +func (q *Queries) ExistsOrg(ctx context.Context, id string) (err error) { + _, err = q.OrgByID(ctx, id) + return err +} + +func (q *Queries) SearchOrgs(ctx context.Context, queries *OrgSearchQueries) (orgs *Orgs, err error) { + query, scan := prepareOrgsQuery() + stmt, args, err := queries.toQuery(query).ToSql() + if err != nil { + return nil, errors.ThrowInvalidArgument(err, "QUERY-wQ3by", "Errors.Query.InvalidRequest") + } + + rows, err := q.client.QueryContext(ctx, stmt, args...) + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-M6mYN", "Errors.Internal") + } + orgs, err = scan(rows) + if err != nil { + return nil, err + } + orgs.LatestSequence, err = q.latestSequence(ctx, orgsTable) + return orgs, err +} + +func NewOrgDomainSearchQuery(method TextComparison, value string) (SearchQuery, error) { + return NewTextQuery(OrgColumnDomain, value, method) +} + +func NewOrgNameSearchQuery(method TextComparison, value string) (SearchQuery, error) { + return NewTextQuery(OrgColumnName, value, method) +} + +func prepareOrgsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Orgs, error)) { + return sq.Select( + OrgColumnID.identifier(), + OrgColumnCreationDate.identifier(), + OrgColumnChangeDate.identifier(), + OrgColumnResourceOwner.identifier(), + OrgColumnState.identifier(), + OrgColumnSequence.identifier(), + OrgColumnName.identifier(), + OrgColumnDomain.identifier(), + countColumn.identifier()). + From(orgsTable.identifier()).PlaceholderFormat(sq.Dollar), + func(rows *sql.Rows) (*Orgs, error) { + orgs := make([]*Org, 0) + var count uint64 + for rows.Next() { + org := new(Org) + err := rows.Scan( + &org.ID, + &org.CreationDate, + &org.ChangeDate, + &org.ResourceOwner, + &org.State, + &org.Sequence, + &org.Name, + &org.Domain, + &count, + ) + if err != nil { + return nil, err + } + orgs = append(orgs, org) + } + + if err := rows.Close(); err != nil { + return nil, errors.ThrowInternal(err, "QUERY-QMXJv", "Errors.Query.CloseRows") + } + + return &Orgs{ + Orgs: orgs, + SearchResponse: SearchResponse{ + Count: count, + }, + }, nil + } +} + +func prepareOrgQuery() (sq.SelectBuilder, func(*sql.Row) (*Org, error)) { + return sq.Select( + OrgColumnID.identifier(), + OrgColumnCreationDate.identifier(), + OrgColumnChangeDate.identifier(), + OrgColumnResourceOwner.identifier(), + OrgColumnState.identifier(), + OrgColumnSequence.identifier(), + OrgColumnName.identifier(), + OrgColumnDomain.identifier(), + ). + From(orgsTable.identifier()).PlaceholderFormat(sq.Dollar), + func(row *sql.Row) (*Org, error) { + o := new(Org) + err := row.Scan( + &o.ID, + &o.CreationDate, + &o.ChangeDate, + &o.ResourceOwner, + &o.State, + &o.Sequence, + &o.Name, + &o.Domain, + ) + if err != nil { + if errs.Is(err, sql.ErrNoRows) { + return nil, errors.ThrowNotFound(err, "QUERY-iTTGJ", "Errors.Org.NotFound") + } + return nil, errors.ThrowInternal(err, "QUERY-pWS5H", "Errors.Internal") + } + return o, nil + } +} + +func prepareOrgUniqueQuery() (sq.SelectBuilder, func(*sql.Row) (bool, error)) { + return sq.Select(uniqueColumn.identifier()). + From(orgsTable.identifier()).PlaceholderFormat(sq.Dollar), + func(row *sql.Row) (isUnique bool, err error) { + err = row.Scan(&isUnique) + if err != nil { + return false, errors.ThrowInternal(err, "QUERY-e6EiG", "Errors.Internal") + } + return isUnique, err + } +} diff --git a/internal/query/projection/action.go b/internal/query/projection/action.go index 6d299c2a51..7871f67f48 100644 --- a/internal/query/projection/action.go +++ b/internal/query/projection/action.go @@ -4,6 +4,7 @@ import ( "context" "github.com/caos/logging" + "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore" @@ -12,13 +13,27 @@ import ( "github.com/caos/zitadel/internal/repository/action" ) +const ( + ActionTable = "zitadel.projections.actions" + ActionIDCol = "id" + ActionCreationDateCol = "creation_date" + ActionChangeDateCol = "change_date" + ActionResourceOwnerCol = "resource_owner" + ActionStateCol = "action_state" + ActionSequenceCol = "sequence" + ActionNameCol = "name" + ActionScriptCol = "script" + ActionTimeoutCol = "timeout" + ActionAllowedToFailCol = "allowed_to_fail" +) + type ActionProjection struct { crdb.StatementHandler } func NewActionProjection(ctx context.Context, config crdb.StatementHandlerConfig) *ActionProjection { p := &ActionProjection{} - config.ProjectionName = "projections.actions" + config.ProjectionName = ActionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) return p @@ -54,38 +69,25 @@ func (p *ActionProjection) reducers() []handler.AggregateReducer { } } -const ( - actionIDCol = "id" - actionCreationDateCol = "creation_date" - actionChangeDateCol = "change_date" - actionResourceOwnerCol = "resource_owner" - actionStateCol = "action_state" - actionSequenceCol = "sequence" - actionNameCol = "name" - actionScriptCol = "script" - actionTimeoutCol = "timeout" - actionAllowedToFailCol = "allowed_to_fail" -) - func (p *ActionProjection) reduceActionAdded(event eventstore.EventReader) (*handler.Statement, error) { e, ok := event.(*action.AddedEvent) if !ok { - logging.LogWithFields("HANDL-zWCk3", "seq", event.Sequence, "expectedType", action.AddedEventType).Error("was not an event") - return nil, errors.ThrowInvalidArgument(nil, "HANDL-uYq4r", "reduce.wrong.event.type") + logging.LogWithFields("HANDL-Sgg31", "seq", event.Sequence, "expectedType", action.AddedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-Dff21", "reduce.wrong.event.type") } return crdb.NewCreateStatement( e, []handler.Column{ - handler.NewCol(actionIDCol, e.Aggregate().ID), - handler.NewCol(actionCreationDateCol, e.CreationDate()), - handler.NewCol(actionChangeDateCol, e.CreationDate()), - handler.NewCol(actionResourceOwnerCol, e.Aggregate().ResourceOwner), - handler.NewCol(actionSequenceCol, e.Sequence()), - handler.NewCol(actionNameCol, e.Name), - handler.NewCol(actionScriptCol, e.Script), - handler.NewCol(actionTimeoutCol, e.Timeout), - handler.NewCol(actionAllowedToFailCol, e.AllowedToFail), - handler.NewCol(actionStateCol, domain.ActionStateActive), + handler.NewCol(ActionIDCol, e.Aggregate().ID), + handler.NewCol(ActionCreationDateCol, e.CreationDate()), + handler.NewCol(ActionChangeDateCol, e.CreationDate()), + handler.NewCol(ActionResourceOwnerCol, e.Aggregate().ResourceOwner), + handler.NewCol(ActionSequenceCol, e.Sequence()), + handler.NewCol(ActionNameCol, e.Name), + handler.NewCol(ActionScriptCol, e.Script), + handler.NewCol(ActionTimeoutCol, e.Timeout), + handler.NewCol(ActionAllowedToFailCol, e.AllowedToFail), + handler.NewCol(ActionStateCol, domain.ActionStateActive), }, ), nil } @@ -93,30 +95,30 @@ func (p *ActionProjection) reduceActionAdded(event eventstore.EventReader) (*han func (p *ActionProjection) reduceActionChanged(event eventstore.EventReader) (*handler.Statement, error) { e, ok := event.(*action.ChangedEvent) if !ok { - logging.LogWithFields("HANDL-q4oq8", "seq", event.Sequence, "expected", action.ChangedEventType).Error("wrong event type") - return nil, errors.ThrowInvalidArgument(nil, "HANDL-Bg8oM", "reduce.wrong.event.type") + logging.LogWithFields("HANDL-Dg2th", "seq", event.Sequence, "expected", action.ChangedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-Gg43d", "reduce.wrong.event.type") } values := []handler.Column{ - handler.NewCol(actionChangeDateCol, e.CreationDate()), - handler.NewCol(actionSequenceCol, e.Sequence()), + handler.NewCol(ActionChangeDateCol, e.CreationDate()), + handler.NewCol(ActionSequenceCol, e.Sequence()), } if e.Name != nil { - values = append(values, handler.NewCol(actionNameCol, *e.Name)) + values = append(values, handler.NewCol(ActionNameCol, *e.Name)) } if e.Script != nil { - values = append(values, handler.NewCol(actionScriptCol, *e.Script)) + values = append(values, handler.NewCol(ActionScriptCol, *e.Script)) } if e.Timeout != nil { - values = append(values, handler.NewCol(actionTimeoutCol, *e.Timeout)) + values = append(values, handler.NewCol(ActionTimeoutCol, *e.Timeout)) } if e.AllowedToFail != nil { - values = append(values, handler.NewCol(actionAllowedToFailCol, *e.AllowedToFail)) + values = append(values, handler.NewCol(ActionAllowedToFailCol, *e.AllowedToFail)) } return crdb.NewUpdateStatement( e, values, []handler.Condition{ - handler.NewCond(actionIDCol, e.Aggregate().ID), + handler.NewCond(ActionIDCol, e.Aggregate().ID), }, ), nil } @@ -124,18 +126,18 @@ func (p *ActionProjection) reduceActionChanged(event eventstore.EventReader) (*h func (p *ActionProjection) reduceActionDeactivated(event eventstore.EventReader) (*handler.Statement, error) { e, ok := event.(*action.DeactivatedEvent) if !ok { - logging.LogWithFields("HANDL-1gwdc", "seq", event.Sequence, "expectedType", action.DeactivatedEventType).Error("wrong event type") - return nil, errors.ThrowInvalidArgument(nil, "HANDL-BApK4", "reduce.wrong.event.type") + logging.LogWithFields("HANDL-Fhhjd", "seq", event.Sequence, "expectedType", action.DeactivatedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-Fgh32", "reduce.wrong.event.type") } return crdb.NewUpdateStatement( e, []handler.Column{ - handler.NewCol(actionChangeDateCol, e.CreationDate()), - handler.NewCol(actionSequenceCol, e.Sequence()), - handler.NewCol(actionStateCol, domain.ActionStateInactive), + handler.NewCol(ActionChangeDateCol, e.CreationDate()), + handler.NewCol(ActionSequenceCol, e.Sequence()), + handler.NewCol(ActionStateCol, domain.ActionStateInactive), }, []handler.Condition{ - handler.NewCond(actionIDCol, e.Aggregate().ID), + handler.NewCond(ActionIDCol, e.Aggregate().ID), }, ), nil } @@ -143,18 +145,18 @@ func (p *ActionProjection) reduceActionDeactivated(event eventstore.EventReader) func (p *ActionProjection) reduceActionReactivated(event eventstore.EventReader) (*handler.Statement, error) { e, ok := event.(*action.ReactivatedEvent) if !ok { - logging.LogWithFields("HANDL-Vjwiy", "seq", event.Sequence, "expectedType", action.ReactivatedEventType).Error("wrong event type") - return nil, errors.ThrowInvalidArgument(nil, "HANDL-o37De", "reduce.wrong.event.type") + logging.LogWithFields("HANDL-Fg4r3", "seq", event.Sequence, "expectedType", action.ReactivatedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-hwdqa", "reduce.wrong.event.type") } return crdb.NewUpdateStatement( e, []handler.Column{ - handler.NewCol(actionChangeDateCol, e.CreationDate()), - handler.NewCol(actionSequenceCol, e.Sequence()), - handler.NewCol(actionStateCol, domain.ActionStateActive), + handler.NewCol(ActionChangeDateCol, e.CreationDate()), + handler.NewCol(ActionSequenceCol, e.Sequence()), + handler.NewCol(ActionStateCol, domain.ActionStateActive), }, []handler.Condition{ - handler.NewCond(actionIDCol, e.Aggregate().ID), + handler.NewCond(ActionIDCol, e.Aggregate().ID), }, ), nil } @@ -162,13 +164,13 @@ func (p *ActionProjection) reduceActionReactivated(event eventstore.EventReader) func (p *ActionProjection) reduceActionRemoved(event eventstore.EventReader) (*handler.Statement, error) { e, ok := event.(*action.RemovedEvent) if !ok { - logging.LogWithFields("HANDL-79OhB", "seq", event.Sequence, "expectedType", action.RemovedEventType).Error("wrong event type") - return nil, errors.ThrowInvalidArgument(nil, "HANDL-4TbKT", "reduce.wrong.event.type") + logging.LogWithFields("HANDL-Dgwh2", "seq", event.Sequence, "expectedType", action.RemovedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-Dgh2d", "reduce.wrong.event.type") } return crdb.NewDeleteStatement( e, []handler.Condition{ - handler.NewCond(actionIDCol, e.Aggregate().ID), + handler.NewCond(ActionIDCol, e.Aggregate().ID), }, ), nil } diff --git a/internal/query/projection/action_test.go b/internal/query/projection/action_test.go new file mode 100644 index 0000000000..abc20f8c4d --- /dev/null +++ b/internal/query/projection/action_test.go @@ -0,0 +1,178 @@ +package projection + +import ( + "testing" + "time" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/repository/action" +) + +func TestActionProjection_reduces(t *testing.T) { + type args struct { + event func(t *testing.T) eventstore.EventReader + } + tests := []struct { + name string + args args + reduce func(event eventstore.EventReader) (*handler.Statement, error) + want wantReduce + }{ + { + name: "reduceActionAdded", + args: args{ + event: getEvent(testEvent( + repository.EventType(action.AddedEventType), + action.AggregateType, + []byte(`{"name": "name", "script":"name(){}","timeout": 3000000000, "allowedToFail": true}`), + ), action.AddedEventMapper), + }, + reduce: (&ActionProjection{}).reduceActionAdded, + want: wantReduce{ + projection: ActionTable, + aggregateType: eventstore.AggregateType("action"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + shouldExec: true, + expectedStmt: "INSERT INTO zitadel.projections.actions (id, creation_date, change_date, resource_owner, sequence, name, script, timeout, allowed_to_fail, action_state) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + "ro-id", + uint64(15), + "name", + "name(){}", + 3 * time.Second, + true, + domain.ActionStateActive, + }, + }, + }, + }, + { + name: "reduceActionChanged", + args: args{ + event: getEvent(testEvent( + repository.EventType(action.ChangedEventType), + action.AggregateType, + []byte(`{"name": "name2", "script":"name2(){}"}`), + ), action.ChangedEventMapper), + }, + reduce: (&ActionProjection{}).reduceActionChanged, + want: wantReduce{ + projection: ActionTable, + aggregateType: eventstore.AggregateType("action"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + shouldExec: true, + expectedStmt: "UPDATE zitadel.projections.actions SET (change_date, sequence, name, script) = ($1, $2, $3, $4) WHERE (id = $5)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "name2", + "name2(){}", + "agg-id", + }, + }, + }, + }, + { + name: "reduceActionDeactivated", + args: args{ + event: getEvent(testEvent( + repository.EventType(action.ChangedEventType), + action.AggregateType, + []byte(`{}`), + ), action.DeactivatedEventMapper), + }, + reduce: (&ActionProjection{}).reduceActionDeactivated, + want: wantReduce{ + projection: ActionTable, + aggregateType: eventstore.AggregateType("action"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + shouldExec: true, + expectedStmt: "UPDATE zitadel.projections.actions SET (change_date, sequence, action_state) = ($1, $2, $3) WHERE (id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.ActionStateInactive, + "agg-id", + }, + }, + }, + }, + { + name: "reduceActionReactivated", + args: args{ + event: getEvent(testEvent( + repository.EventType(action.ChangedEventType), + action.AggregateType, + []byte(`{}`), + ), action.ReactivatedEventMapper), + }, + reduce: (&ActionProjection{}).reduceActionReactivated, + want: wantReduce{ + projection: ActionTable, + aggregateType: eventstore.AggregateType("action"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + shouldExec: true, + expectedStmt: "UPDATE zitadel.projections.actions SET (change_date, sequence, action_state) = ($1, $2, $3) WHERE (id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.ActionStateActive, + "agg-id", + }, + }, + }, + }, + { + name: "reduceActionRemoved", + args: args{ + event: getEvent(testEvent( + repository.EventType(action.ChangedEventType), + action.AggregateType, + []byte(`{}`), + ), action.RemovedEventMapper), + }, + reduce: (&ActionProjection{}).reduceActionRemoved, + want: wantReduce{ + projection: ActionTable, + aggregateType: eventstore.AggregateType("action"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + shouldExec: true, + expectedStmt: "DELETE FROM zitadel.projections.actions WHERE (id = $1)", + expectedArgs: []interface{}{ + "agg-id", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + event := baseEvent(t) + got, err := tt.reduce(event) + if _, ok := err.(errors.InvalidArgument); !ok { + t.Errorf("no wrong event mapping: %v, got: %v", err, got) + } + + event = tt.args.event(t) + got, err = tt.reduce(event) + assertReduce(t, got, err, tt.want) + }) + } +} diff --git a/internal/query/projection/config.go b/internal/query/projection/config.go index 0e5bc4fefe..44e16597fa 100644 --- a/internal/query/projection/config.go +++ b/internal/query/projection/config.go @@ -9,6 +9,7 @@ type Config struct { BulkLimit uint64 CRDB types.SQL Customizations map[string]CustomConfig + MaxIterators int } type CustomConfig struct { diff --git a/internal/query/projection/flow.go b/internal/query/projection/flow.go new file mode 100644 index 0000000000..d9190a441e --- /dev/null +++ b/internal/query/projection/flow.go @@ -0,0 +1,93 @@ +package projection + +import ( + "context" + + "github.com/caos/logging" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/handler/crdb" + "github.com/caos/zitadel/internal/repository/org" +) + +const ( + FlowTriggerTable = "zitadel.projections.flows_triggers" + FlowTypeCol = "flow_type" + FlowTriggerTypeCol = "trigger_type" + FlowResourceOwnerCol = "resource_owner" + FlowActionTriggerSequenceCol = "trigger_sequence" + FlowActionIDCol = "action_id" +) + +type FlowProjection struct { + crdb.StatementHandler +} + +func NewFlowProjection(ctx context.Context, config crdb.StatementHandlerConfig) *FlowProjection { + p := &FlowProjection{} + config.ProjectionName = FlowTriggerTable + config.Reducers = p.reducers() + p.StatementHandler = crdb.NewStatementHandler(ctx, config) + return p +} + +func (p *FlowProjection) reducers() []handler.AggregateReducer { + return []handler.AggregateReducer{ + { + Aggregate: org.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: org.TriggerActionsSetEventType, + Reduce: p.reduceTriggerActionsSetEventType, + }, + { + Event: org.FlowClearedEventType, + Reduce: p.reduceFlowClearedEventType, + }, + }, + }, + } +} + +func (p *FlowProjection) reduceTriggerActionsSetEventType(event eventstore.EventReader) (*handler.Statement, error) { + e, ok := event.(*org.TriggerActionsSetEvent) + if !ok { + logging.LogWithFields("HANDL-zWCk3", "seq", event.Sequence, "expectedType", org.TriggerActionsSetEventType).Error("was not an trigger actions set event") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-uYq4r", "reduce.wrong.event.type") + } + stmts := make([]func(reader eventstore.EventReader) crdb.Exec, len(e.ActionIDs)+1) + stmts[0] = crdb.AddDeleteStatement( + []handler.Condition{ + handler.NewCond(FlowTypeCol, e.FlowType), + handler.NewCond(FlowTriggerTypeCol, e.TriggerType), + }, + ) + for i, id := range e.ActionIDs { + stmts[i+1] = crdb.AddCreateStatement( + []handler.Column{ + handler.NewCol(FlowResourceOwnerCol, e.Aggregate().ResourceOwner), + handler.NewCol(FlowTypeCol, e.FlowType), + handler.NewCol(FlowTriggerTypeCol, e.TriggerType), + handler.NewCol(FlowActionIDCol, id), + handler.NewCol(FlowActionTriggerSequenceCol, i), + }, + ) + } + return crdb.NewMultiStatement(e, stmts...), nil +} + +func (p *FlowProjection) reduceFlowClearedEventType(event eventstore.EventReader) (*handler.Statement, error) { + e, ok := event.(*org.FlowClearedEvent) + if !ok { + logging.LogWithFields("HANDL-zWCk3", "seq", event.Sequence, "expectedType", org.FlowClearedEventType).Error("was not a flow cleared event") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-uYq4r", "reduce.wrong.event.type") + } + return crdb.NewDeleteStatement( + e, + []handler.Condition{ + handler.NewCond(FlowTypeCol, e.FlowType), + }, + ), nil +} diff --git a/internal/query/projection/flow_test.go b/internal/query/projection/flow_test.go new file mode 100644 index 0000000000..0ffd34efbf --- /dev/null +++ b/internal/query/projection/flow_test.go @@ -0,0 +1,93 @@ +package projection + +import ( + "testing" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/repository/org" +) + +func TestFlowProjection_reduces(t *testing.T) { + type args struct { + event func(t *testing.T) eventstore.EventReader + } + tests := []struct { + name string + args args + reduce func(event eventstore.EventReader) (*handler.Statement, error) + want wantReduce + }{ + //TODO: multi stmt tests + //{ + // name: "reduceTriggerActionsSetEventType", + // args: args{ + // event: getEvent(testEvent( + // repository.EventType(org.TriggerActionsSetEventType), + // org.AggregateType, + // []byte(`{"flowType": 1, "triggerType": 1, "actionIDs": ["id1", "id2"]}`), + // ), org.TriggerActionsSetEventMapper), + // }, + // reduce: (&FlowProjection{}).reduceTriggerActionsSetEventType, + // want: wantReduce{ + // projection: FlowTriggerTable, + // aggregateType: eventstore.AggregateType("org"), + // sequence: 15, + // previousSequence: 10, + // executer: &testExecuter{ + // shouldExec: true, + // expectedStmt: "DELETE FROM zitadel.projections.actions WHERE (flow_type, trigger_type) = ($1, $2); INSERT INTO zitadel.projections.actions (resource_owner, flow_type, trigger_type, action_id, trigger_sequence) = ($3, $1, $2, $4, $5); INSERT INTO zitadel.projections.actions (resource_owner, flow_type, trigger_type, action_id, trigger_sequence) = ($3, $1, $2, $6, $7)", + // expectedArgs: []interface{}{ + // domain.FlowTypeExternalAuthentication, + // domain.TriggerTypePostAuthentication, + // "ro-id", + // "id1", + // 0, + // "id2", + // 1, + // }, + // }, + // }, + //}, + { + name: "reduceFlowClearedEventType", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.FlowClearedEventType), + org.AggregateType, + []byte(`{"flowType": 1}`), + ), org.FlowClearedEventMapper), + }, + reduce: (&FlowProjection{}).reduceFlowClearedEventType, + want: wantReduce{ + projection: FlowTriggerTable, + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + shouldExec: true, + expectedStmt: "DELETE FROM zitadel.projections.flows_triggers WHERE (flow_type = $1)", + expectedArgs: []interface{}{ + domain.FlowTypeExternalAuthentication, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + event := baseEvent(t) + got, err := tt.reduce(event) + if _, ok := err.(errors.InvalidArgument); !ok { + t.Errorf("no wrong event mapping: %v, got: %v", err, got) + } + + event = tt.args.event(t) + got, err = tt.reduce(event) + assertReduce(t, got, err, tt.want) + }) + } +} diff --git a/internal/query/projection/org.go b/internal/query/projection/org.go index adb9d7ff39..21e5d14bec 100644 --- a/internal/query/projection/org.go +++ b/internal/query/projection/org.go @@ -4,6 +4,7 @@ import ( "context" "github.com/caos/logging" + "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore" @@ -16,9 +17,13 @@ type OrgProjection struct { crdb.StatementHandler } +const ( + OrgProjectionTable = "zitadel.projections.orgs" +) + func NewOrgProjection(ctx context.Context, config crdb.StatementHandlerConfig) *OrgProjection { p := &OrgProjection{} - config.ProjectionName = "projections.orgs" + config.ProjectionName = OrgProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) return p @@ -54,33 +59,35 @@ func (p *OrgProjection) reducers() []handler.AggregateReducer { } } +type OrgColumn string + const ( - orgIDCol = "id" - orgCreationDateCol = "creation_date" - orgChangeDateCol = "change_date" - orgResourceOwnerCol = "resource_owner" - orgStateCol = "org_state" - orgSequenceCol = "sequence" - orgDomainCol = "domain" - orgNameCol = "name" + OrgColumnID = "id" + OrgColumnCreationDate = "creation_date" + OrgColumnChangeDate = "change_date" + OrgColumnResourceOwner = "resource_owner" + OrgColumnState = "org_state" + OrgColumnSequence = "sequence" + OrgColumnName = "name" + OrgColumnDomain = "primary_domain" ) func (p *OrgProjection) reduceOrgAdded(event eventstore.EventReader) (*handler.Statement, error) { e, ok := event.(*org.OrgAddedEvent) if !ok { - logging.LogWithFields("HANDL-zWCk3", "seq", event.Sequence, "expectedType", org.OrgAddedEventType).Error("was not an event") + logging.LogWithFields("HANDL-zWCk3", "seq", event.Sequence(), "expectedType", org.OrgAddedEventType).Error("was not an event") return nil, errors.ThrowInvalidArgument(nil, "HANDL-uYq4r", "reduce.wrong.event.type") } return crdb.NewCreateStatement( e, []handler.Column{ - handler.NewCol(orgIDCol, e.Aggregate().ID), - handler.NewCol(orgCreationDateCol, e.CreationDate()), - handler.NewCol(orgChangeDateCol, e.CreationDate()), - handler.NewCol(orgResourceOwnerCol, e.Aggregate().ResourceOwner), - handler.NewCol(orgSequenceCol, e.Sequence()), - handler.NewCol(orgNameCol, e.Name), - handler.NewCol(orgStateCol, domain.OrgStateActive), + handler.NewCol(OrgColumnID, e.Aggregate().ID), + handler.NewCol(OrgColumnCreationDate, e.CreationDate()), + handler.NewCol(OrgColumnChangeDate, e.CreationDate()), + handler.NewCol(OrgColumnResourceOwner, e.Aggregate().ResourceOwner), + handler.NewCol(OrgColumnSequence, e.Sequence()), + handler.NewCol(OrgColumnName, e.Name), + handler.NewCol(OrgColumnState, domain.OrgStateActive), }, ), nil } @@ -88,21 +95,21 @@ func (p *OrgProjection) reduceOrgAdded(event eventstore.EventReader) (*handler.S func (p *OrgProjection) reduceOrgChanged(event eventstore.EventReader) (*handler.Statement, error) { e, ok := event.(*org.OrgChangedEvent) if !ok { - logging.LogWithFields("HANDL-q4oq8", "seq", event.Sequence, "expected", org.OrgChangedEventType).Error("wrong event type") + logging.LogWithFields("HANDL-q4oq8", "seq", event.Sequence(), "expected", org.OrgChangedEventType).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-Bg8oM", "reduce.wrong.event.type") } - values := []handler.Column{ - handler.NewCol(orgChangeDateCol, e.CreationDate()), - handler.NewCol(orgSequenceCol, e.Sequence()), - } - if e.Name != "" { - values = append(values, handler.NewCol(orgNameCol, e.Name)) + if e.Name == "" { + return crdb.NewNoOpStatement(e), nil } return crdb.NewUpdateStatement( e, - values, + []handler.Column{ + handler.NewCol(OrgColumnChangeDate, e.CreationDate()), + handler.NewCol(OrgColumnSequence, e.Sequence()), + handler.NewCol(OrgColumnName, e.Name), + }, []handler.Condition{ - handler.NewCond(orgIDCol, e.Aggregate().ID), + handler.NewCond(OrgColumnID, e.Aggregate().ID), }, ), nil } @@ -110,18 +117,18 @@ func (p *OrgProjection) reduceOrgChanged(event eventstore.EventReader) (*handler func (p *OrgProjection) reduceOrgDeactivated(event eventstore.EventReader) (*handler.Statement, error) { e, ok := event.(*org.OrgDeactivatedEvent) if !ok { - logging.LogWithFields("HANDL-1gwdc", "seq", event.Sequence, "expectedType", org.OrgDeactivatedEventType).Error("wrong event type") + logging.LogWithFields("HANDL-1gwdc", "seq", event.Sequence(), "expectedType", org.OrgDeactivatedEventType).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-BApK4", "reduce.wrong.event.type") } return crdb.NewUpdateStatement( e, []handler.Column{ - handler.NewCol(orgChangeDateCol, e.CreationDate()), - handler.NewCol(orgSequenceCol, e.Sequence()), - handler.NewCol(orgStateCol, domain.OrgStateInactive), + handler.NewCol(OrgColumnChangeDate, e.CreationDate()), + handler.NewCol(OrgColumnSequence, e.Sequence()), + handler.NewCol(OrgColumnState, domain.OrgStateInactive), }, []handler.Condition{ - handler.NewCond(orgIDCol, e.Aggregate().ID), + handler.NewCond(OrgColumnID, e.Aggregate().ID), }, ), nil } @@ -129,18 +136,18 @@ func (p *OrgProjection) reduceOrgDeactivated(event eventstore.EventReader) (*han func (p *OrgProjection) reduceOrgReactivated(event eventstore.EventReader) (*handler.Statement, error) { e, ok := event.(*org.OrgReactivatedEvent) if !ok { - logging.LogWithFields("HANDL-Vjwiy", "seq", event.Sequence, "expectedType", org.OrgReactivatedEventType).Error("wrong event type") + logging.LogWithFields("HANDL-Vjwiy", "seq", event.Sequence(), "expectedType", org.OrgReactivatedEventType).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-o37De", "reduce.wrong.event.type") } return crdb.NewUpdateStatement( e, []handler.Column{ - handler.NewCol(orgChangeDateCol, e.CreationDate()), - handler.NewCol(orgSequenceCol, e.Sequence()), - handler.NewCol(orgStateCol, domain.OrgStateActive), + handler.NewCol(OrgColumnChangeDate, e.CreationDate()), + handler.NewCol(OrgColumnSequence, e.Sequence()), + handler.NewCol(OrgColumnState, domain.OrgStateActive), }, []handler.Condition{ - handler.NewCond(orgIDCol, e.Aggregate().ID), + handler.NewCond(OrgColumnID, e.Aggregate().ID), }, ), nil } @@ -148,18 +155,18 @@ func (p *OrgProjection) reduceOrgReactivated(event eventstore.EventReader) (*han func (p *OrgProjection) reducePrimaryDomainSet(event eventstore.EventReader) (*handler.Statement, error) { e, ok := event.(*org.DomainPrimarySetEvent) if !ok { - logging.LogWithFields("HANDL-79OhB", "seq", event.Sequence, "expectedType", org.OrgDomainPrimarySetEventType).Error("wrong event type") + logging.LogWithFields("HANDL-79OhB", "seq", event.Sequence(), "expectedType", org.OrgDomainPrimarySetEventType).Error("wrong event type") return nil, errors.ThrowInvalidArgument(nil, "HANDL-4TbKT", "reduce.wrong.event.type") } return crdb.NewUpdateStatement( e, []handler.Column{ - handler.NewCol(orgChangeDateCol, e.CreationDate()), - handler.NewCol(orgSequenceCol, e.Sequence()), - handler.NewCol(orgDomainCol, e.Domain), + handler.NewCol(OrgColumnChangeDate, e.CreationDate()), + handler.NewCol(OrgColumnSequence, e.Sequence()), + handler.NewCol(OrgColumnDomain, e.Domain), }, []handler.Condition{ - handler.NewCond(orgIDCol, e.Aggregate().ID), + handler.NewCond(OrgColumnID, e.Aggregate().ID), }, ), nil } diff --git a/internal/query/projection/org/owner/projection.go b/internal/query/projection/org/owner/projection.go index 48d68f36de..8102689c12 100644 --- a/internal/query/projection/org/owner/projection.go +++ b/internal/query/projection/org/owner/projection.go @@ -16,15 +16,15 @@ import ( ) type OrgOwner struct { - OrgID string `col:"org_id"` - OrgName string `col:"org_name"` - OrgCreationDate time.Time `col:"org_creation_date"` - OwnerID string `col:"owner_id"` - OwnerLanguage *language.Tag `col:"owner_language"` - OwnerEmailAddress string `col:"owner_email"` - OwnerFirstName string `col:"owner_first_name"` - OwnerLastName string `col:"owner_last_name"` - OwnerGender domain.Gender `col:"owner_gender"` + OrgID string + OrgName string + OrgCreationDate time.Time + OwnerID string + OwnerLanguage *language.Tag + OwnerEmailAddress string + OwnerFirstName string + OwnerLastName string + OwnerGender domain.Gender } type OrgOwnerProjection struct { @@ -49,7 +49,7 @@ const ( func NewOrgOwnerProjection(ctx context.Context, config crdb.StatementHandlerConfig) *OrgOwnerProjection { p := &OrgOwnerProjection{} - config.ProjectionName = "projections.org_owners" + config.ProjectionName = "zitadel.projections.org_owners" config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) return p diff --git a/internal/query/projection/org_test.go b/internal/query/projection/org_test.go new file mode 100644 index 0000000000..9376613308 --- /dev/null +++ b/internal/query/projection/org_test.go @@ -0,0 +1,196 @@ +package projection + +import ( + "testing" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/repository/org" +) + +func TestOrgProjection_reduces(t *testing.T) { + type args struct { + event func(t *testing.T) eventstore.EventReader + } + tests := []struct { + name string + args args + reduce func(event eventstore.EventReader) (*handler.Statement, error) + want wantReduce + }{ + { + name: "reducePrimaryDomainSet", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.OrgDomainPrimarySetEventType), + org.AggregateType, + []byte(`{"domain": "domain.new"}`), + ), org.DomainPrimarySetEventMapper), + }, + reduce: (&OrgProjection{}).reducePrimaryDomainSet, + want: wantReduce{ + projection: OrgProjectionTable, + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + shouldExec: true, + expectedStmt: "UPDATE zitadel.projections.orgs SET (change_date, sequence, primary_domain) = ($1, $2, $3) WHERE (id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "domain.new", + "agg-id", + }, + }, + }, + }, + { + name: "reduceOrgReactivated", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.OrgReactivatedEventType), + org.AggregateType, + nil, + ), org.OrgReactivatedEventMapper), + }, + reduce: (&OrgProjection{}).reduceOrgReactivated, + want: wantReduce{ + projection: OrgProjectionTable, + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + shouldExec: true, + expectedStmt: "UPDATE zitadel.projections.orgs SET (change_date, sequence, org_state) = ($1, $2, $3) WHERE (id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.OrgStateActive, + "agg-id", + }, + }, + }, + }, + { + name: "reduceOrgDeactivated", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.OrgDeactivatedEventType), + org.AggregateType, + nil, + ), org.OrgDeactivatedEventMapper), + }, + reduce: (&OrgProjection{}).reduceOrgDeactivated, + want: wantReduce{ + projection: OrgProjectionTable, + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + shouldExec: true, + expectedStmt: "UPDATE zitadel.projections.orgs SET (change_date, sequence, org_state) = ($1, $2, $3) WHERE (id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.OrgStateInactive, + "agg-id", + }, + }, + }, + }, + { + name: "reduceOrgChanged", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.OrgChangedEventType), + org.AggregateType, + []byte(`{"name": "new name"}`), + ), org.OrgChangedEventMapper), + }, + reduce: (&OrgProjection{}).reduceOrgChanged, + want: wantReduce{ + projection: OrgProjectionTable, + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + shouldExec: true, + expectedStmt: "UPDATE zitadel.projections.orgs SET (change_date, sequence, name) = ($1, $2, $3) WHERE (id = $4)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "new name", + "agg-id", + }, + }, + }, + }, + { + name: "reduceOrgChanged no changes", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.OrgChangedEventType), + org.AggregateType, + []byte(`{}`), + ), org.OrgChangedEventMapper), + }, + reduce: (&OrgProjection{}).reduceOrgChanged, + want: wantReduce{ + projection: OrgProjectionTable, + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + shouldExec: false, + }, + }, + }, + { + name: "reduceOrgAdded", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.OrgAddedEventType), + org.AggregateType, + []byte(`{"name": "name"}`), + ), org.OrgAddedEventMapper), + }, + reduce: (&OrgProjection{}).reduceOrgAdded, + want: wantReduce{ + projection: OrgProjectionTable, + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + shouldExec: true, + expectedStmt: "INSERT INTO zitadel.projections.orgs (id, creation_date, change_date, resource_owner, sequence, name, org_state) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + "ro-id", + uint64(15), + "name", + domain.OrgStateActive, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + event := baseEvent(t) + got, err := tt.reduce(event) + if _, ok := err.(errors.InvalidArgument); !ok { + t.Errorf("no wrong event mapping: %v, got: %v", err, got) + } + + event = tt.args.event(t) + got, err = tt.reduce(event) + assertReduce(t, got, err, tt.want) + }) + } +} diff --git a/internal/query/projection/project.go b/internal/query/projection/project.go index e4eea357fc..c8c9ee8acf 100644 --- a/internal/query/projection/project.go +++ b/internal/query/projection/project.go @@ -15,7 +15,7 @@ type ProjectProjection struct { func NewProjectProjection(ctx context.Context, config crdb.StatementHandlerConfig) *ProjectProjection { p := &ProjectProjection{} - config.ProjectionName = "projections.projects" + config.ProjectionName = "zitadel.projections.projects" config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) return p diff --git a/internal/query/projection/projection.go b/internal/query/projection/projection.go index 4668a48bfd..112c448235 100644 --- a/internal/query/projection/projection.go +++ b/internal/query/projection/projection.go @@ -2,25 +2,21 @@ package projection import ( "context" + "database/sql" + "time" "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore/handler" "github.com/caos/zitadel/internal/eventstore/handler/crdb" - "github.com/caos/zitadel/internal/query/projection/flow" ) const ( - currentSeqTable = "projections.current_sequences" + CurrentSeqTable = "projections.current_sequences" locksTable = "projections.locks" failedEventsTable = "projections.failed_events" ) -func Start(ctx context.Context, es *eventstore.Eventstore, config Config) error { - sqlClient, err := config.CRDB.Start() - if err != nil { - return err - } - +func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, config Config) error { projectionConfig := crdb.StatementHandlerConfig{ ProjectionHandlerConfig: handler.ProjectionHandlerConfig{ HandlerConfig: handler.HandlerConfig{ @@ -30,19 +26,18 @@ func Start(ctx context.Context, es *eventstore.Eventstore, config Config) error RetryFailedAfter: config.RetryFailedAfter.Duration, }, Client: sqlClient, - SequenceTable: currentSeqTable, + SequenceTable: CurrentSeqTable, LockTable: locksTable, FailedEventsTable: failedEventsTable, MaxFailureCount: config.MaxFailureCount, BulkLimit: config.BulkLimit, } - // turned off for this release - //NewOrgProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["orgs"])) + NewOrgProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["orgs"])) //NewProjectProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["projects"])) //owner.NewOrgOwnerProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["org_owners"])) NewActionProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["actions"])) - flow.NewFlowProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["flows"])) + NewFlowProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["flows"])) return nil } @@ -62,3 +57,20 @@ func applyCustomConfig(config crdb.StatementHandlerConfig, customConfig CustomCo return config } + +func iteratorPool(workerCount int) chan func() { + if workerCount <= 0 { + return nil + } + + queue := make(chan func()) + for i := 0; i < workerCount; i++ { + go func() { + for iteration := range queue { + iteration() + time.Sleep(2 * time.Second) + } + }() + } + return queue +} diff --git a/internal/query/projection/test_event.go b/internal/query/projection/test_event.go new file mode 100644 index 0000000000..b4d3a9168f --- /dev/null +++ b/internal/query/projection/test_event.go @@ -0,0 +1,87 @@ +package projection + +import ( + "testing" + "time" + + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/repository" +) + +func testEvent( + eventType repository.EventType, + aggregateType repository.AggregateType, + data []byte, +) *repository.Event { + return &repository.Event{ + Sequence: 15, + PreviousAggregateSequence: 10, + PreviousAggregateTypeSequence: 10, + CreationDate: time.Now(), + Type: eventType, + AggregateType: aggregateType, + Data: data, + Version: "v1", + AggregateID: "agg-id", + ResourceOwner: "ro-id", + ID: "event-id", + EditorService: "editor-svc", + EditorUser: "editor-user", + } +} + +func baseEvent(*testing.T) eventstore.EventReader { + return &eventstore.BaseEvent{} +} + +func getEvent(event *repository.Event, mapper func(*repository.Event) (eventstore.EventReader, error)) func(t *testing.T) eventstore.EventReader { + return func(t *testing.T) eventstore.EventReader { + e, err := mapper(event) + if err != nil { + t.Fatalf("mapper failed: %v", err) + } + return e + } +} + +type wantReduce struct { + projection string + aggregateType eventstore.AggregateType + sequence uint64 + previousSequence uint64 + executer *testExecuter + err func(error) bool +} + +func assertReduce(t *testing.T, stmt *handler.Statement, err error, want wantReduce) { + t.Helper() + if want.err == nil && err != nil { + t.Errorf("unexpected error of type %T: %v", err, err) + return + } + if want.err != nil && want.err(err) { + return + } + if stmt.AggregateType != want.aggregateType { + t.Errorf("wront aggregate type: want: %q got: %q", want.aggregateType, stmt.AggregateType) + } + + if stmt.PreviousSequence != want.previousSequence { + t.Errorf("wront previous sequence: want: %d got: %d", want.previousSequence, stmt.PreviousSequence) + } + + if stmt.Sequence != want.sequence { + t.Errorf("wront sequence: want: %d got: %d", want.sequence, stmt.Sequence) + } + if stmt.Execute == nil { + want.executer.Validate(t) + return + } + err = stmt.Execute(want.executer, want.projection) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + want.executer.Validate(t) +} diff --git a/internal/query/projection/test_executer.go b/internal/query/projection/test_executer.go new file mode 100644 index 0000000000..dd201e6c15 --- /dev/null +++ b/internal/query/projection/test_executer.go @@ -0,0 +1,48 @@ +package projection + +import ( + "database/sql" + "testing" +) + +type testExecuter struct { + expectedStmt string + gottenStmt string + shouldExec bool + + expectedArgs []interface{} + gottenArgs []interface{} + gotExecuted bool +} + +type anyArg struct{} + +func (e *testExecuter) Exec(stmt string, args ...interface{}) (sql.Result, error) { + e.gottenStmt = stmt + e.gottenArgs = args + e.gotExecuted = true + return nil, nil +} + +func (e *testExecuter) Validate(t *testing.T) { + t.Helper() + if e.shouldExec != e.gotExecuted { + t.Error("expected to be executed") + return + } + if len(e.gottenArgs) != len(e.expectedArgs) { + t.Errorf("wrong arg len expected: %d got: %d", len(e.expectedArgs), len(e.gottenArgs)) + } else { + for i := 0; i < len(e.expectedArgs); i++ { + if _, ok := e.expectedArgs[i].(anyArg); ok { + continue + } + if e.expectedArgs[i] != e.gottenArgs[i] { + t.Errorf("wrong argument at index %d: got: %v want: %v", i, e.gottenArgs[i], e.expectedArgs[i]) + } + } + } + if e.gottenStmt != e.expectedStmt { + t.Errorf("wrong stmt want:\n%s\ngot:\n%s", e.expectedStmt, e.gottenStmt) + } +} diff --git a/internal/query/query.go b/internal/query/query.go index dfb3303437..9a26a26a86 100644 --- a/internal/query/query.go +++ b/internal/query/query.go @@ -6,10 +6,8 @@ import ( sd "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/types" - "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/eventstore" iam_model "github.com/caos/zitadel/internal/iam/model" - "github.com/caos/zitadel/internal/id" "github.com/caos/zitadel/internal/query/projection" "github.com/caos/zitadel/internal/repository/action" iam_repo "github.com/caos/zitadel/internal/repository/iam" @@ -20,12 +18,9 @@ import ( ) type Queries struct { - iamID string - eventstore *eventstore.Eventstore - idGenerator id.Generator - secretCrypto crypto.Crypto - - client *sql.DB + iamID string + eventstore *eventstore.Eventstore + client *sql.DB } type Config struct { @@ -39,10 +34,9 @@ func StartQueries(ctx context.Context, es *eventstore.Eventstore, projections pr } repo = &Queries{ - iamID: defaults.IamID, - eventstore: es, - idGenerator: id.SonyFlakeGenerator, - client: sqlClient, + iamID: defaults.IamID, + eventstore: es, + client: sqlClient, } iam_repo.RegisterEventMappers(repo.eventstore) usr_repo.RegisterEventMappers(repo.eventstore) @@ -50,12 +44,7 @@ func StartQueries(ctx context.Context, es *eventstore.Eventstore, projections pr project.RegisterEventMappers(repo.eventstore) action.RegisterEventMappers(repo.eventstore) - repo.secretCrypto, err = crypto.NewAESCrypto(defaults.IDPConfigVerificationKey) - if err != nil { - return nil, err - } - - err = projection.Start(ctx, es, projections) + err = projection.Start(ctx, sqlClient, es, projections) if err != nil { return nil, err } diff --git a/internal/query/search_query.go b/internal/query/search_query.go index e380783fbc..deecbb5dde 100644 --- a/internal/query/search_query.go +++ b/internal/query/search_query.go @@ -2,19 +2,27 @@ package query import ( "errors" - "strings" + "reflect" sq "github.com/Masterminds/squirrel" + "github.com/lib/pq" + + "github.com/caos/zitadel/internal/domain" ) +type SearchResponse struct { + Count uint64 + *LatestSequence +} + type SearchRequest struct { Offset uint64 Limit uint64 - SortingColumn string + SortingColumn Column Asc bool } -func (req *SearchRequest) ToQuery(query sq.SelectBuilder) sq.SelectBuilder { +func (req *SearchRequest) toQuery(query sq.SelectBuilder) sq.SelectBuilder { if req.Offset > 0 { query = query.Offset(req.Offset) } @@ -22,12 +30,12 @@ func (req *SearchRequest) ToQuery(query sq.SelectBuilder) sq.SelectBuilder { query = query.Limit(req.Limit) } - if req.SortingColumn != "" { - clause := "LOWER(?)" + if !req.SortingColumn.isZero() { + clause := "LOWER(" + sqlPlaceholder + ")" if !req.Asc { clause += " DESC" } - query.OrderByClause(clause, req.SortingColumn) + query = query.OrderByClause(clause, req.SortingColumn.identifier()) } return query @@ -40,110 +48,232 @@ type SearchQuery interface { } type TextQuery struct { - Column string + Column Column Text string Compare TextComparison } -func NewTextQuery(column, value string, compare TextComparison) (*TextQuery, error) { - if compare < 0 || compare >= textMax { - return nil, errors.New("invalid compare") +var ( + ErrInvalidCompare = errors.New("invalid compare") + ErrMissingColumn = errors.New("missing column") + ErrInvalidNumber = errors.New("value is no number") +) + +func NewTextQuery(col Column, value string, compare TextComparison) (*TextQuery, error) { + if compare < 0 || compare >= textCompareMax { + return nil, ErrInvalidCompare } - if column == "" { - return nil, errors.New("missing column") + if col.isZero() { + return nil, ErrMissingColumn } return &TextQuery{ - Column: column, + Column: col, Text: value, Compare: compare, }, nil } func (q *TextQuery) ToQuery(query sq.SelectBuilder) sq.SelectBuilder { - query = query.Where(q.comp()) - return query + where, args := q.comp() + return query.Where(where, args...) } -func (s *TextQuery) comp() map[string]interface{} { +func (s *TextQuery) comp() (comparison interface{}, args []interface{}) { switch s.Compare { case TextEquals: - return sq.Eq{s.Column: s.Text} - case TextEqualsIgnore: - return sq.Eq{"LOWER(" + s.Column + ")": strings.ToLower(s.Text)} + return sq.Eq{s.Column.identifier(): s.Text}, nil + case TextEqualsIgnoreCase: + return sq.ILike{s.Column.identifier(): s.Text}, nil case TextStartsWith: - return sq.Like{s.Column: s.Text + sqlPlaceholder} - case TextStartsWithIgnore: - return sq.Like{"LOWER(" + s.Column + ")": strings.ToLower(s.Text) + sqlPlaceholder} + return sq.Like{s.Column.identifier(): s.Text + "%"}, nil + case TextStartsWithIgnoreCase: + return sq.ILike{s.Column.identifier(): s.Text + "%"}, nil case TextEndsWith: - return sq.Like{s.Column: sqlPlaceholder + s.Text} - case TextEndsWithIgnore: - return sq.Like{"LOWER(" + s.Column + ")": sqlPlaceholder + strings.ToLower(s.Text)} + return sq.Like{s.Column.identifier(): "%" + s.Text}, nil + case TextEndsWithIgnoreCase: + return sq.ILike{s.Column.identifier(): "%" + s.Text}, nil case TextContains: - return sq.Like{s.Column: sqlPlaceholder + s.Text + sqlPlaceholder} - case TextContainsIgnore: - return sq.Like{"LOWER(" + s.Column + ")": sqlPlaceholder + strings.ToLower(s.Text) + sqlPlaceholder} + return sq.Like{s.Column.identifier(): "%" + s.Text + "%"}, nil + case TextContainsIgnoreCase: + return sq.ILike{s.Column.identifier(): "%" + s.Text + "%"}, nil + case TextListContains: + return s.Column.identifier() + " @> ? ", []interface{}{pq.StringArray{s.Text}} } - return nil + return nil, nil } type TextComparison int const ( TextEquals TextComparison = iota - TextEqualsIgnore + TextEqualsIgnoreCase TextStartsWith - TextStartsWithIgnore + TextStartsWithIgnoreCase TextEndsWith - TextEndsWithIgnore + TextEndsWithIgnoreCase TextContains - TextContainsIgnore + TextContainsIgnoreCase + TextListContains - textMax + textCompareMax ) -type IntQuery struct { - Column string - Int int - Compare IntComparison +//Deprecated: Use TextComparison, will be removed as soon as all calls are changed to query +func TextComparisonFromMethod(m domain.SearchMethod) TextComparison { + switch m { + case domain.SearchMethodEquals: + return TextEquals + case domain.SearchMethodEqualsIgnoreCase: + return TextEqualsIgnoreCase + case domain.SearchMethodStartsWith: + return TextStartsWith + case domain.SearchMethodStartsWithIgnoreCase: + return TextStartsWithIgnoreCase + case domain.SearchMethodContains: + return TextContains + case domain.SearchMethodContainsIgnoreCase: + return TextContainsIgnoreCase + case domain.SearchMethodEndsWith: + return TextEndsWith + case domain.SearchMethodEndsWithIgnoreCase: + return TextEndsWithIgnoreCase + case domain.SearchMethodListContains: + return TextListContains + default: + return textCompareMax + } } -func NewIntQuery(column string, value int, compare IntComparison) (*IntQuery, error) { - if compare < 0 || compare >= intMax { - return nil, errors.New("invalid compare") +type NumberQuery struct { + Column Column + Number interface{} + Compare NumberComparison +} + +func NewNumberQuery(c Column, value interface{}, compare NumberComparison) (*NumberQuery, error) { + if compare < 0 || compare >= numberCompareMax { + return nil, ErrInvalidCompare } - if column == "" { - return nil, errors.New("missing column") + if c.isZero() { + return nil, ErrMissingColumn } - return &IntQuery{ - Column: column, - Int: value, + switch reflect.TypeOf(value).Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: + //everything fine + default: + return nil, ErrInvalidNumber + } + return &NumberQuery{ + Column: c, + Number: value, Compare: compare, }, nil } -func (q *IntQuery) ToQuery(query sq.SelectBuilder) sq.SelectBuilder { - query = query.Where(q.comp()) - return query +func (q *NumberQuery) ToQuery(query sq.SelectBuilder) sq.SelectBuilder { + where, args := q.comp() + return query.Where(where, args...) } -func (s *IntQuery) comp() sq.Sqlizer { +func (s *NumberQuery) comp() (comparison interface{}, args []interface{}) { switch s.Compare { - case IntEquals: - return sq.Eq{s.Column: s.Int} - case IntGreater: - return sq.Gt{s.Column: s.Int} - case IntLess: - return sq.Lt{s.Column: s.Int} + case NumberEquals: + return sq.Eq{s.Column.identifier(): s.Number}, nil + case NumberNotEquals: + return sq.NotEq{s.Column.identifier(): s.Number}, nil + case NumberLess: + return sq.Lt{s.Column.identifier(): s.Number}, nil + case NumberGreater: + return sq.Gt{s.Column.identifier(): s.Number}, nil + case NumberListContains: + return s.Column.identifier() + " @> ? ", []interface{}{pq.Array(s.Number)} } - return nil + return nil, nil } -type IntComparison int +type NumberComparison int const ( - IntEquals IntComparison = iota - IntGreater - IntLess + NumberEquals NumberComparison = iota + NumberNotEquals + NumberLess + NumberGreater + NumberListContains - intMax + numberCompareMax ) + +//Deprecated: Use NumberComparison, will be removed as soon as all calls are changed to query +func NumberComparisonFromMethod(m domain.SearchMethod) NumberComparison { + switch m { + case domain.SearchMethodEquals: + return NumberEquals + case domain.SearchMethodNotEquals: + return NumberNotEquals + case domain.SearchMethodGreaterThan: + return NumberGreater + case domain.SearchMethodLessThan: + return NumberLess + case domain.SearchMethodListContains: + return NumberListContains + default: + return numberCompareMax + } +} + +var ( + //countColumn represents the default counter for search responses + countColumn = Column{ + name: "COUNT(*) OVER ()", + } + //uniqueColumn shows if there are any results + uniqueColumn = Column{ + name: "COUNT(*) = 0", + } +) + +type table struct { + name string + alias string +} + +func (t table) setAlias(a string) table { + t.alias = a + return t +} + +func (t table) identifier() string { + if t.alias == "" { + return t.name + } + return t.name + " as " + t.alias +} + +func (t table) isZero() bool { + return t.name == "" +} + +type Column struct { + name string + table table +} + +func (c Column) identifier() string { + if c.table.alias == "" { + return c.table.name + "." + c.name + } + return c.table.alias + "." + c.name +} + +func (c Column) setTable(t table) Column { + c.table = t + return c +} + +func (c Column) isZero() bool { + return c.table.isZero() || c.name == "" +} + +func join(join, from Column) string { + return join.table.identifier() + " ON " + from.identifier() + " = " + join.identifier() +} diff --git a/internal/query/search_query_test.go b/internal/query/search_query_test.go new file mode 100644 index 0000000000..390cfaabd5 --- /dev/null +++ b/internal/query/search_query_test.go @@ -0,0 +1,767 @@ +package query + +import ( + "errors" + "reflect" + "testing" + + sq "github.com/Masterminds/squirrel" + "github.com/caos/zitadel/internal/domain" + "github.com/lib/pq" +) + +var ( + testTable = table{ + name: "test_table", + alias: "test_table", + } + testCol = Column{ + name: "test_col", + table: testTable, + } + testNoCol = Column{ + name: "", + table: testTable, + } +) + +func TestSearchRequest_ToQuery(t *testing.T) { + type fields struct { + Offset uint64 + Limit uint64 + SortingColumn Column + Asc bool + } + type want struct { + stmtAddition string + args []interface{} + } + tests := []struct { + name string + fields fields + want want + }{ + { + name: "no queries", + fields: fields{}, + want: want{ + stmtAddition: "", + args: nil, + }, + }, + { + name: "offset", + fields: fields{ + Offset: 5, + }, + want: want{ + stmtAddition: "OFFSET 5", + args: nil, + }, + }, + { + name: "limit", + fields: fields{ + Limit: 5, + }, + want: want{ + stmtAddition: "LIMIT 5", + args: nil, + }, + }, + { + name: "sort asc", + fields: fields{ + SortingColumn: testCol, + Asc: true, + }, + want: want{ + stmtAddition: "ORDER BY LOWER(?)", + args: []interface{}{"test_table.test_col"}, + }, + }, + { + name: "sort desc", + fields: fields{ + SortingColumn: testCol, + }, + want: want{ + stmtAddition: "ORDER BY LOWER(?) DESC", + args: []interface{}{"test_table.test_col"}, + }, + }, + { + name: "all", + fields: fields{ + Offset: 5, + Limit: 10, + SortingColumn: testCol, + Asc: true, + }, + want: want{ + stmtAddition: "ORDER BY LOWER(?) LIMIT 10 OFFSET 5", + args: []interface{}{"test_table.test_col"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := &SearchRequest{ + Offset: tt.fields.Offset, + Limit: tt.fields.Limit, + SortingColumn: tt.fields.SortingColumn, + Asc: tt.fields.Asc, + } + + query := sq.Select((testCol).identifier()).From(testTable.identifier()) + expectedQuery, _, _ := query.ToSql() + + stmt, args, err := req.toQuery(query).ToSql() + if len(tt.want.stmtAddition) > 0 { + expectedQuery += " " + tt.want.stmtAddition + } + if expectedQuery != stmt { + t.Errorf("stmt = %q, want %q", stmt, expectedQuery) + } + + if !reflect.DeepEqual(args, tt.want.args) { + t.Errorf("args = %v, want %v", args, tt.want.stmtAddition) + } + + if err != nil { + t.Errorf("no error expected but got %v", err) + } + }) + } +} + +func TestNewTextQuery(t *testing.T) { + type args struct { + column Column + value string + compare TextComparison + } + tests := []struct { + name string + args args + want *TextQuery + wantErr func(error) bool + }{ + { + name: "too low compare", + args: args{ + column: testCol, + value: "hurst", + compare: -1, + }, + wantErr: func(err error) bool { + return errors.Is(err, ErrInvalidCompare) + }, + }, + { + name: "too high compare", + args: args{ + column: testCol, + value: "hurst", + compare: textCompareMax, + }, + wantErr: func(err error) bool { + return errors.Is(err, ErrInvalidCompare) + }, + }, + { + name: "no column", + args: args{ + column: Column{}, + value: "hurst", + compare: TextEquals, + }, + wantErr: func(err error) bool { + return errors.Is(err, ErrMissingColumn) + }, + }, + { + name: "no column name", + args: args{ + column: testNoCol, + value: "hurst", + compare: TextEquals, + }, + wantErr: func(err error) bool { + return errors.Is(err, ErrMissingColumn) + }, + }, + { + name: "correct", + args: args{ + column: testCol, + value: "hurst", + compare: TextEquals, + }, + want: &TextQuery{ + Column: testCol, + Text: "hurst", + Compare: TextEquals, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewTextQuery(tt.args.column, tt.args.value, tt.args.compare) + if err != nil && tt.wantErr == nil { + t.Errorf("NewTextQuery() no error expected got %v", err) + return + } else if tt.wantErr != nil && !tt.wantErr(err) { + t.Errorf("NewTextQuery() unexpeted error = %v", err) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewTextQuery() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTextQuery_comp(t *testing.T) { + type fields struct { + Column Column + Text string + Compare TextComparison + } + type want struct { + query interface{} + args []interface{} + isNil bool + } + tests := []struct { + name string + fields fields + want want + }{ + { + name: "equals", + fields: fields{ + Column: testCol, + Text: "Hurst", + Compare: TextEquals, + }, + want: want{ + query: sq.Eq{"test_table.test_col": "Hurst"}, + args: nil, + }, + }, + { + name: "equals ignore case", + fields: fields{ + Column: testCol, + Text: "Hurst", + Compare: TextEqualsIgnoreCase, + }, + want: want{ + query: sq.ILike{"test_table.test_col": "Hurst"}, + args: nil, + }, + }, + { + name: "starts with", + fields: fields{ + Column: testCol, + Text: "Hurst", + Compare: TextStartsWith, + }, + want: want{ + query: sq.Like{"test_table.test_col": "Hurst%"}, + args: nil, + }, + }, + { + name: "starts with ignore case", + fields: fields{ + Column: testCol, + Text: "Hurst", + Compare: TextStartsWithIgnoreCase, + }, + want: want{ + query: sq.ILike{"test_table.test_col": "Hurst%"}, + args: nil, + }, + }, + { + name: "ends with", + fields: fields{ + Column: testCol, + Text: "Hurst", + Compare: TextEndsWith, + }, + want: want{ + query: sq.Like{"test_table.test_col": "%Hurst"}, + args: nil, + }, + }, + { + name: "ends with ignore case", + fields: fields{ + Column: testCol, + Text: "Hurst", + Compare: TextEndsWithIgnoreCase, + }, + want: want{ + query: sq.ILike{"test_table.test_col": "%Hurst"}, + args: nil, + }, + }, + { + name: "contains", + fields: fields{ + Column: testCol, + Text: "Hurst", + Compare: TextContains, + }, + want: want{ + query: sq.Like{"test_table.test_col": "%Hurst%"}, + args: nil, + }, + }, + { + name: "containts ignore case", + fields: fields{ + Column: testCol, + Text: "Hurst", + Compare: TextContainsIgnoreCase, + }, + want: want{ + query: sq.ILike{"test_table.test_col": "%Hurst%"}, + args: nil, + }, + }, + { + name: "list containts", + fields: fields{ + Column: testCol, + Text: "Hurst", + Compare: TextListContains, + }, + want: want{ + query: "test_table.test_col @> ? ", + args: []interface{}{pq.StringArray{"Hurst"}}, + }, + }, + { + name: "too high comparison", + fields: fields{ + Column: testCol, + Text: "Hurst", + Compare: textCompareMax, + }, + want: want{ + isNil: true, + }, + }, + { + name: "too low comparison", + fields: fields{ + Column: testCol, + Text: "Hurst", + Compare: -1, + }, + want: want{ + isNil: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &TextQuery{ + Column: tt.fields.Column, + Text: tt.fields.Text, + Compare: tt.fields.Compare, + } + query, args := s.comp() + if query == nil && tt.want.isNil { + return + } else if tt.want.isNil && query != nil { + t.Error("query should not be nil") + } + + if !reflect.DeepEqual(query, tt.want.query) { + t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, query, query) + } + + if !reflect.DeepEqual(args, tt.want.args) { + t.Errorf("wrong args: want: %v, (%T), got: %v (%T)", tt.want.args, tt.want.args, args, args) + } + }) + } +} + +func TestTextComparisonFromMethod(t *testing.T) { + type args struct { + m domain.SearchMethod + } + tests := []struct { + name string + args args + want TextComparison + }{ + { + name: "equals", + args: args{ + m: domain.SearchMethodEquals, + }, + want: TextEquals, + }, + { + name: "equals ignore case", + args: args{ + m: domain.SearchMethodEqualsIgnoreCase, + }, + want: TextEqualsIgnoreCase, + }, + { + name: "starts with", + args: args{ + m: domain.SearchMethodStartsWith, + }, + want: TextStartsWith, + }, + { + name: "starts with ignore case", + args: args{ + m: domain.SearchMethodStartsWithIgnoreCase, + }, + want: TextStartsWithIgnoreCase, + }, + { + name: "ends with", + args: args{ + m: domain.SearchMethodEndsWith, + }, + want: TextEndsWith, + }, + { + name: "ends with ignore case", + args: args{ + m: domain.SearchMethodEndsWithIgnoreCase, + }, + want: TextEndsWithIgnoreCase, + }, + { + name: "contains", + args: args{ + m: domain.SearchMethodContains, + }, + want: TextContains, + }, + { + name: "list contains", + args: args{ + m: domain.SearchMethodListContains, + }, + want: TextListContains, + }, + { + name: "containts ignore case", + args: args{ + m: domain.SearchMethodContainsIgnoreCase, + }, + want: TextContainsIgnoreCase, + }, + { + name: "invalid search method", + args: args{ + m: -1, + }, + want: textCompareMax, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := TextComparisonFromMethod(tt.args.m); got != tt.want { + t.Errorf("TextCompareFromMethod() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNewNumberQuery(t *testing.T) { + type args struct { + column Column + value interface{} + compare NumberComparison + } + tests := []struct { + name string + args args + want *NumberQuery + wantErr func(error) bool + }{ + { + name: "too low compare", + args: args{ + column: testCol, + value: "hurst", + compare: -1, + }, + wantErr: func(err error) bool { + return errors.Is(err, ErrInvalidCompare) + }, + }, + { + name: "too high compare", + args: args{ + column: testCol, + value: "hurst", + compare: numberCompareMax, + }, + wantErr: func(err error) bool { + return errors.Is(err, ErrInvalidCompare) + }, + }, + { + name: "no column", + args: args{ + column: Column{}, + value: "hurst", + compare: NumberEquals, + }, + wantErr: func(err error) bool { + return errors.Is(err, ErrMissingColumn) + }, + }, + { + name: "no column name", + args: args{ + column: testNoCol, + value: "hurst", + compare: NumberEquals, + }, + wantErr: func(err error) bool { + return errors.Is(err, ErrMissingColumn) + }, + }, + { + name: "no number", + args: args{ + column: testCol, + value: "hurst", + compare: NumberEquals, + }, + wantErr: func(err error) bool { + return errors.Is(err, ErrInvalidNumber) + }, + }, + { + name: "correct", + args: args{ + column: testCol, + value: 5, + compare: NumberEquals, + }, + want: &NumberQuery{ + Column: testCol, + Number: 5, + Compare: NumberEquals, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewNumberQuery(tt.args.column, tt.args.value, tt.args.compare) + if err != nil && tt.wantErr == nil { + t.Errorf("NewNumberQuery() no error expected got %v", err) + return + } else if tt.wantErr != nil && !tt.wantErr(err) { + t.Errorf("NewNumberQuery() unexpeted error = %v", err) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewNumberQuery() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNumberQuery_comp(t *testing.T) { + type fields struct { + Column Column + Number interface{} + Compare NumberComparison + } + type want struct { + query interface{} + args []interface{} + isNil bool + } + tests := []struct { + name string + fields fields + want want + }{ + { + name: "equals", + fields: fields{ + Column: testCol, + Number: 42, + Compare: NumberEquals, + }, + want: want{ + query: sq.Eq{"test_table.test_col": 42}, + args: nil, + }, + }, + { + name: "not equals", + fields: fields{ + Column: testCol, + Number: 42, + Compare: NumberNotEquals, + }, + want: want{ + query: sq.NotEq{"test_table.test_col": 42}, + args: nil, + }, + }, + { + name: "less", + fields: fields{ + Column: testCol, + Number: 42, + Compare: NumberLess, + }, + want: want{ + query: sq.Lt{"test_table.test_col": 42}, + args: nil, + }, + }, + { + name: "greater", + fields: fields{ + Column: testCol, + Number: 42, + Compare: NumberGreater, + }, + want: want{ + query: sq.Gt{"test_table.test_col": 42}, + args: nil, + }, + }, + { + name: "list containts", + fields: fields{ + Column: testCol, + Number: 42, + Compare: NumberListContains, + }, + want: want{ + query: "test_table.test_col @> ? ", + args: []interface{}{pq.Array(42)}, + }, + }, + { + name: "too high comparison", + fields: fields{ + Column: testCol, + Number: 42, + Compare: numberCompareMax, + }, + want: want{ + isNil: true, + }, + }, + { + name: "too low comparison", + fields: fields{ + Column: testCol, + Number: 42, + Compare: -1, + }, + want: want{ + isNil: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &NumberQuery{ + Column: tt.fields.Column, + Number: tt.fields.Number, + Compare: tt.fields.Compare, + } + query, args := s.comp() + if query == nil && tt.want.isNil { + return + } else if tt.want.isNil && query != nil { + t.Error("query should not be nil") + } + + if !reflect.DeepEqual(query, tt.want.query) { + t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, query, query) + } + + if !reflect.DeepEqual(args, tt.want.args) { + t.Errorf("wrong args: want: %v, (%T), got: %v (%T)", tt.want.args, tt.want.args, args, args) + } + }) + } +} + +func TestNumberComparisonFromMethod(t *testing.T) { + type args struct { + m domain.SearchMethod + } + tests := []struct { + name string + args args + want NumberComparison + }{ + { + name: "equals", + args: args{ + m: domain.SearchMethodEquals, + }, + want: NumberEquals, + }, + { + name: "not equals", + args: args{ + m: domain.SearchMethodNotEquals, + }, + want: NumberNotEquals, + }, + { + name: "less than", + args: args{ + m: domain.SearchMethodLessThan, + }, + want: NumberLess, + }, + { + name: "greater than", + args: args{ + m: domain.SearchMethodGreaterThan, + }, + want: NumberGreater, + }, + { + name: "list contains", + args: args{ + m: domain.SearchMethodListContains, + }, + want: NumberListContains, + }, + { + name: "invalid search method", + args: args{ + m: -1, + }, + want: numberCompareMax, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NumberComparisonFromMethod(tt.args.m); got != tt.want { + t.Errorf("TextCompareFromMethod() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/repository/flow/flow.go b/internal/repository/flow/flow.go index a84de9413c..40bbcec49f 100644 --- a/internal/repository/flow/flow.go +++ b/internal/repository/flow/flow.go @@ -20,9 +20,9 @@ const ( type TriggerActionsSetEvent struct { eventstore.BaseEvent - FlowType domain.FlowType - TriggerType domain.TriggerType - ActionIDs []string + FlowType domain.FlowType `json:"flowType"` + TriggerType domain.TriggerType `json:"triggerType"` + ActionIDs []string `json:"actionIDs"` } func (e *TriggerActionsSetEvent) Data() interface{} { @@ -63,9 +63,9 @@ func TriggerActionsSetEventMapper(event *repository.Event) (eventstore.EventRead type TriggerActionsCascadeRemovedEvent struct { eventstore.BaseEvent - FlowType domain.FlowType - TriggerType domain.TriggerType - ActionID string + FlowType domain.FlowType `json:"flowType"` + TriggerType domain.TriggerType `json:"triggerType"` + ActionID string `json:"actionID"` } func (e *TriggerActionsCascadeRemovedEvent) Data() interface{} { @@ -104,7 +104,7 @@ func TriggerActionsCascadeRemovedEventMapper(event *repository.Event) (eventstor type FlowClearedEvent struct { eventstore.BaseEvent - FlowType domain.FlowType + FlowType domain.FlowType `json:"flowType"` } func (e *FlowClearedEvent) Data() interface{} { diff --git a/internal/repository/org/org.go b/internal/repository/org/org.go index 2788cf9c9e..5b61ce3f40 100644 --- a/internal/repository/org/org.go +++ b/internal/repository/org/org.go @@ -133,15 +133,9 @@ func NewOrgDeactivatedEvent(ctx context.Context, aggregate *eventstore.Aggregate } func OrgDeactivatedEventMapper(event *repository.Event) (eventstore.EventReader, error) { - orgChanged := &OrgDeactivatedEvent{ + return &OrgDeactivatedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), - } - err := json.Unmarshal(event.Data, orgChanged) - if err != nil { - return nil, errors.ThrowInternal(err, "ORG-DAfbs", "unable to unmarshal org deactivated") - } - - return orgChanged, nil + }, nil } type OrgReactivatedEvent struct { @@ -167,15 +161,9 @@ func NewOrgReactivatedEvent(ctx context.Context, aggregate *eventstore.Aggregate } func OrgReactivatedEventMapper(event *repository.Event) (eventstore.EventReader, error) { - orgChanged := &OrgReactivatedEvent{ + return &OrgReactivatedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), - } - err := json.Unmarshal(event.Data, orgChanged) - if err != nil { - return nil, errors.ThrowInternal(err, "ORG-DAfbs", "unable to unmarshal org deactivated") - } - - return orgChanged, nil + }, nil } type OrgRemovedEvent struct { diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index a233679342..e7144e9945 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -376,6 +376,10 @@ Errors: WrongTriggerType: TriggerType ist ungültig NoChanges: Keine Änderungen ActionIDsNotExist: ActionIDs existieren nicht + Query: + CloseRows: SQL Statement konnte nicht abgeschlossen werden + SQLStatement: SQL Statement konnte nicht erstellt werden + InvalidRequest: Anfage ist ungültig EventTypes: user: added: Benutzer hinzugefügt diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index 9f642c5067..c6d7bd0f58 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -376,6 +376,10 @@ Errors: WrongTriggerType: TriggerType is invalid NoChanges: No Changes ActionIDsNotExist: ActionIDs do not exist + Query: + CloseRows: SQL Statement could not be finished + SQLStatement: SQL Statement coud not be created + InvalidRequest: Request is invalid EventTypes: user: added: User added diff --git a/internal/ui/login/handler/custom_action.go b/internal/ui/login/handler/custom_action.go index cdb06014f3..0d7b48743a 100644 --- a/internal/ui/login/handler/custom_action.go +++ b/internal/ui/login/handler/custom_action.go @@ -4,20 +4,32 @@ import ( "context" "github.com/caos/oidc/pkg/oidc" + "github.com/caos/zitadel/internal/actions" "github.com/caos/zitadel/internal/domain" iam_model "github.com/caos/zitadel/internal/iam/model" ) -func (l *Login) customExternalUserMapping(user *domain.ExternalUser, tokens *oidc.Tokens, req *domain.AuthRequest, config *iam_model.IDPConfigView) (*domain.ExternalUser, error) { - triggerActions, err := l.query.GetActionsByFlowAndTriggerType(context.TODO(), domain.FlowTypeExternalAuthentication, domain.TriggerTypePostAuthentication) +func (l *Login) customExternalUserMapping(ctx context.Context, user *domain.ExternalUser, tokens *oidc.Tokens, req *domain.AuthRequest, config *iam_model.IDPConfigView) (*domain.ExternalUser, error) { + resourceOwner := req.RequestedOrgID + if resourceOwner == "" { + resourceOwner = config.AggregateID + } + if resourceOwner == domain.IAMID { + iam, err := l.authRepo.GetIAM(ctx) + if err != nil { + return nil, err + } + resourceOwner = iam.GlobalOrgID + } + triggerActions, err := l.query.GetActionsByFlowAndTriggerType(ctx, domain.FlowTypeExternalAuthentication, domain.TriggerTypePostAuthentication, resourceOwner) if err != nil { return nil, err } - ctx := (&actions.Context{}).SetToken(tokens) + actionCtx := (&actions.Context{}).SetToken(tokens) api := (&actions.API{}).SetExternalUser(user).SetMetadata(&user.Metadatas) for _, a := range triggerActions { - err = actions.Run(ctx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail) + err = actions.Run(actionCtx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail) if err != nil { return nil, err } @@ -25,15 +37,15 @@ func (l *Login) customExternalUserMapping(user *domain.ExternalUser, tokens *oid return user, err } -func (l *Login) customExternalUserToLoginUserMapping(user *domain.Human, tokens *oidc.Tokens, req *domain.AuthRequest, config *iam_model.IDPConfigView, metadata []*domain.Metadata) (*domain.Human, []*domain.Metadata, error) { - triggerActions, err := l.query.GetActionsByFlowAndTriggerType(context.TODO(), domain.FlowTypeExternalAuthentication, domain.TriggerTypePreCreation) +func (l *Login) customExternalUserToLoginUserMapping(user *domain.Human, tokens *oidc.Tokens, req *domain.AuthRequest, config *iam_model.IDPConfigView, metadata []*domain.Metadata, resourceOwner string) (*domain.Human, []*domain.Metadata, error) { + triggerActions, err := l.query.GetActionsByFlowAndTriggerType(context.TODO(), domain.FlowTypeExternalAuthentication, domain.TriggerTypePreCreation, resourceOwner) if err != nil { return nil, nil, err } - ctx := (&actions.Context{}).SetToken(tokens) + actionCtx := (&actions.Context{}).SetToken(tokens) api := (&actions.API{}).SetHuman(user).SetMetadata(&metadata) for _, a := range triggerActions { - err = actions.Run(ctx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail) + err = actions.Run(actionCtx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail) if err != nil { return nil, nil, err } @@ -41,16 +53,16 @@ func (l *Login) customExternalUserToLoginUserMapping(user *domain.Human, tokens return user, metadata, err } -func (l *Login) customGrants(userID string, tokens *oidc.Tokens, req *domain.AuthRequest, config *iam_model.IDPConfigView) ([]*domain.UserGrant, error) { - triggerActions, err := l.query.GetActionsByFlowAndTriggerType(context.TODO(), domain.FlowTypeExternalAuthentication, domain.TriggerTypePostCreation) +func (l *Login) customGrants(userID string, tokens *oidc.Tokens, req *domain.AuthRequest, config *iam_model.IDPConfigView, resourceOwner string) ([]*domain.UserGrant, error) { + triggerActions, err := l.query.GetActionsByFlowAndTriggerType(context.TODO(), domain.FlowTypeExternalAuthentication, domain.TriggerTypePostCreation, resourceOwner) if err != nil { return nil, err } - ctx := (&actions.Context{}).SetToken(tokens) + actionCtx := (&actions.Context{}).SetToken(tokens) actionUserGrants := make([]actions.UserGrant, 0) api := (&actions.API{}).SetUserGrants(&actionUserGrants) for _, a := range triggerActions { - err = actions.Run(ctx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail) + err = actions.Run(actionCtx, api, a.Script, a.Name, a.Timeout, a.AllowedToFail) if err != nil { return nil, err } diff --git a/internal/ui/login/handler/external_login_handler.go b/internal/ui/login/handler/external_login_handler.go index 014f41a595..1d07e720a5 100644 --- a/internal/ui/login/handler/external_login_handler.go +++ b/internal/ui/login/handler/external_login_handler.go @@ -184,7 +184,7 @@ func (l *Login) getRPConfig(w http.ResponseWriter, r *http.Request, authReq *dom func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) { externalUser := l.mapTokenToLoginUser(tokens, idpConfig) - externalUser, err := l.customExternalUserMapping(externalUser, tokens, authReq, idpConfig) + externalUser, err := l.customExternalUserMapping(r.Context(), externalUser, tokens, authReq, idpConfig) if err != nil { l.renderError(w, r, authReq, err) return @@ -284,7 +284,7 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) linkingUser := authReq.LinkingUsers[len(authReq.LinkingUsers)-1] user, externalIDP, metadata := l.mapExternalUserToLoginUser(orgIamPolicy, linkingUser, idpConfig) - user, metadata, err = l.customExternalUserToLoginUserMapping(user, nil, authReq, idpConfig, metadata) + user, metadata, err = l.customExternalUserToLoginUserMapping(user, nil, authReq, idpConfig, metadata, resourceOwner) err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, memberRoles, authReq.ID, userAgentID, resourceOwner, metadata, domain.BrowserInfoFromRequest(r)) if err != nil { l.renderExternalNotFoundOption(w, r, authReq, err) @@ -295,7 +295,7 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR l.renderError(w, r, authReq, err) return } - userGrants, err := l.customGrants(authReq.UserID, nil, authReq, idpConfig) + userGrants, err := l.customGrants(authReq.UserID, nil, authReq, idpConfig, resourceOwner) if err != nil { l.renderError(w, r, authReq, err) return diff --git a/internal/ui/login/handler/jwt_handler.go b/internal/ui/login/handler/jwt_handler.go index 8faf1e7548..cd6c9f0bc9 100644 --- a/internal/ui/login/handler/jwt_handler.go +++ b/internal/ui/login/handler/jwt_handler.go @@ -11,6 +11,7 @@ import ( "github.com/caos/logging" "github.com/caos/oidc/pkg/client/rp" "github.com/caos/oidc/pkg/oidc" + http_util "github.com/caos/zitadel/internal/api/http" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" @@ -75,7 +76,7 @@ func (l *Login) handleJWTExtraction(w http.ResponseWriter, r *http.Request, auth } tokens := &oidc.Tokens{IDToken: token, IDTokenClaims: tokenClaims} externalUser := l.mapTokenToLoginUser(tokens, idpConfig) - externalUser, err = l.customExternalUserMapping(externalUser, tokens, authReq, idpConfig) + externalUser, err = l.customExternalUserMapping(r.Context(), externalUser, tokens, authReq, idpConfig) if err != nil { l.renderError(w, r, authReq, err) return @@ -126,7 +127,7 @@ func (l *Login) jwtExtractionUserNotFound(w http.ResponseWriter, r *http.Request return } user, externalIDP, metadata := l.mapExternalUserToLoginUser(orgIamPolicy, authReq.LinkingUsers[len(authReq.LinkingUsers)-1], idpConfig) - user, metadata, err = l.customExternalUserToLoginUserMapping(user, tokens, authReq, idpConfig, metadata) + user, metadata, err = l.customExternalUserToLoginUserMapping(user, tokens, authReq, idpConfig, metadata, resourceOwner) if err != nil { l.renderError(w, r, authReq, err) return @@ -141,7 +142,7 @@ func (l *Login) jwtExtractionUserNotFound(w http.ResponseWriter, r *http.Request l.renderError(w, r, authReq, err) return } - userGrants, err := l.customGrants(authReq.UserID, tokens, authReq, idpConfig) + userGrants, err := l.customGrants(authReq.UserID, tokens, authReq, idpConfig, resourceOwner) if err != nil { l.renderError(w, r, authReq, err) return diff --git a/internal/ui/login/handler/login.go b/internal/ui/login/handler/login.go index e4c752020e..504b2ea681 100644 --- a/internal/ui/login/handler/login.go +++ b/internal/ui/login/handler/login.go @@ -6,6 +6,10 @@ import ( "net/http" "github.com/caos/logging" + "github.com/gorilla/csrf" + "github.com/rakyll/statik/fs" + "golang.org/x/text/language" + "github.com/caos/zitadel/internal/api/authz" http_utils "github.com/caos/zitadel/internal/api/http" "github.com/caos/zitadel/internal/api/http/middleware" @@ -23,9 +27,6 @@ import ( "github.com/caos/zitadel/internal/static" _ "github.com/caos/zitadel/internal/ui/login/statik" usr_model "github.com/caos/zitadel/internal/user/model" - "github.com/gorilla/csrf" - "github.com/rakyll/statik/fs" - "golang.org/x/text/language" ) type Login struct { diff --git a/migrations/cockroach/V1.73__fix_projections.sql b/migrations/cockroach/V1.73__fix_projections.sql new file mode 100644 index 0000000000..bbe5939649 --- /dev/null +++ b/migrations/cockroach/V1.73__fix_projections.sql @@ -0,0 +1,9 @@ +ALTER TABLE zitadel.projections.orgs RENAME COLUMN domain TO primary_domain; + +DROP VIEW zitadel.projections.flows_actions_triggers; +ALTER TABLE zitadel.projections.flows_triggers DROP CONSTRAINT fk_action; +DROP TABLE zitadel.projections.flows_actions; +DELETE FROM zitadel.projections.current_sequences where projection_name in ( + 'zitadel.projections.actions', + 'zitadel.projections.flows_actions', + 'zitadel.projections.flows_triggers'); diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index 1dc87fe1f5..374a462e69 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -153,6 +153,32 @@ service AdminService { }; } + // Returns an organisation by id + rpc GetOrgByID(GetOrgByIDRequest) returns (GetOrgByIDResponse) { + option (google.api.http) = { + get: "/orgs/{id}"; + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.read"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "orgs"; + tags: "global"; + external_docs: { + url: "https://docs.zitadel.ch/administrate#Organizations"; + description: "detailed information about organizations"; + }; + responses: { + key: "200"; + value: { + description: "requested org found"; + }; + }; + }; + } + //Checks whether an organisation exists by the given parameters rpc IsOrgUnique(IsOrgUniqueRequest) returns (IsOrgUniqueResponse) { option (google.api.http) = { @@ -185,32 +211,6 @@ service AdminService { }; } - // Returns an organisation by id - rpc GetOrgByID(GetOrgByIDRequest) returns (GetOrgByIDResponse) { - option (google.api.http) = { - get: "/orgs/{id}"; - }; - - option (zitadel.v1.auth_option) = { - permission: "iam.read"; - }; - - option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { - tags: "orgs"; - tags: "global"; - external_docs: { - url: "https://docs.zitadel.ch/administrate#Organizations"; - description: "detailed information about organizations"; - }; - responses: { - key: "200"; - value: { - description: "requested org found"; - }; - }; - }; - } - //Returns all organisations matching the request // all queries need to match (AND) rpc ListOrgs(ListOrgsRequest) returns (ListOrgsResponse) {