diff --git a/.devcontainer/base/docker-compose.yml b/.devcontainer/base/docker-compose.yml
index d1b26f1a7b..079ad69774 100644
--- a/.devcontainer/base/docker-compose.yml
+++ b/.devcontainer/base/docker-compose.yml
@@ -1,9 +1,16 @@
+x-build-cache: &build-cache
+ cache_from:
+ - type=gha
+ cache_to:
+ - type=gha,mode=max
+
services:
devcontainer:
container_name: devcontainer
build:
context: .
+ <<: *build-cache
volumes:
- ../../:/workspaces:cached
- /tmp/.X11-unix:/tmp/.X11-unix:cached
@@ -36,6 +43,7 @@ services:
container_name: mock-zitadel
build:
context: ../../apps/login/integration/core-mock
+ <<: *build-cache
ports:
- 22220:22220
- 22222:22222
@@ -45,6 +53,7 @@ services:
build:
context: ../..
dockerfile: build/login/Dockerfile
+ <<: *build-cache
image: "${LOGIN_TAG:-zitadel-login:local}"
env_file: ../../apps/login/.env.test
network_mode: service:devcontainer
@@ -80,6 +89,7 @@ services:
build:
context: ../../apps/login/acceptance/setup
dockerfile: ../go-command.Dockerfile
+ <<: *build-cache
entrypoint: "./setup.sh"
network_mode: service:devcontainer
environment:
@@ -116,6 +126,7 @@ services:
dockerfile: ../go-command.Dockerfile
args:
- LOGIN_TEST_ACCEPTANCE_GOLANG_TAG=${LOGIN_TEST_ACCEPTANCE_GOLANG_TAG:-golang:1.24-alpine}
+ <<: *build-cache
environment:
PORT: '3333'
command:
@@ -140,6 +151,7 @@ services:
dockerfile: ../go-command.Dockerfile
args:
- LOGIN_TEST_ACCEPTANCE_GOLANG_TAG=${LOGIN_TEST_ACCEPTANCE_GOLANG_TAG:-golang:1.24-alpine}
+ <<: *build-cache
network_mode: service:devcontainer
environment:
API_URL: 'http://localhost:8080'
@@ -163,6 +175,7 @@ services:
# dockerfile: ../../go-command.Dockerfile
# args:
# - LOGIN_TEST_ACCEPTANCE_GOLANG_TAG=${LOGIN_TEST_ACCEPTANCE_GOLANG_TAG:-golang:1.24-alpine}
+ # <<: *build-cache
# network_mode: service:devcontainer
# environment:
# API_URL: 'http://localhost:8080'
@@ -184,6 +197,7 @@ services:
dockerfile: ../go-command.Dockerfile
args:
- LOGIN_TEST_ACCEPTANCE_GOLANG_TAG=${LOGIN_TEST_ACCEPTANCE_GOLANG_TAG:-golang:1.24-alpine}
+ <<: *build-cache
network_mode: service:devcontainer
environment:
API_URL: 'http://localhost:8080'
@@ -198,27 +212,27 @@ services:
depends_on:
configure-login:
condition: "service_completed_successfully"
-
-# mock-samlidp:
-# container_name: mock-samlidp
-# build:
-# context: ../../apps/login/acceptance/idp/saml
-# dockerfile: ../../go-command.Dockerfile
-# args:
-# - LOGIN_TEST_ACCEPTANCE_GOLANG_TAG=${LOGIN_TEST_ACCEPTANCE_GOLANG_TAG:-golang:1.24-alpine}
-# network_mode: service:devcontainer
-# environment:
-# API_URL: 'http://localhost:8080'
-# API_DOMAIN: 'localhost'
-# PAT_FILE: '/pat/zitadel-admin-sa.pat'
-# SCHEMA: 'http'
-# HOST: 'localhost'
-# PORT: "8003"
-# volumes:
-# - "../apps/login/packages/acceptance/pat:/pat"
-# depends_on:
-# configure-login:
-# condition: "service_completed_successfully"
+ # mock-samlidp:
+ # container_name: mock-samlidp
+ # build:
+ # context: ../../apps/login/acceptance/idp/saml
+ # dockerfile: ../../go-command.Dockerfile
+ # args:
+ # - LOGIN_TEST_ACCEPTANCE_GOLANG_TAG=${LOGIN_TEST_ACCEPTANCE_GOLANG_TAG:-golang:1.24-alpine}
+ # <<: *build-cache
+ # network_mode: service:devcontainer
+ # environment:
+ # API_URL: 'http://localhost:8080'
+ # API_DOMAIN: 'localhost'
+ # PAT_FILE: '/pat/zitadel-admin-sa.pat'
+ # SCHEMA: 'http'
+ # HOST: 'localhost'
+ # PORT: "8003"
+ # volumes:
+ # - "../apps/login/packages/acceptance/pat:/pat"
+ # depends_on:
+ # configure-login:
+ # condition: "service_completed_successfully"
volumes:
postgres-data:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ac1ced50c7..b805c99060 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -54,8 +54,6 @@ jobs:
console_cache_path: ${{ needs.console.outputs.cache_path }}
version: ${{ needs.version.outputs.version }}
node_version: "20"
- secrets:
- DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
core-unit-test:
needs: core
@@ -103,8 +101,6 @@ jobs:
with:
login_build_image_name: "ghcr.io/zitadel/zitadel-login-build"
node_version: "20"
- secrets:
- DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
e2e:
uses: ./.github/workflows/e2e.yml
diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml
index 65e7851d48..e1493cfcff 100644
--- a/.github/workflows/compile.yml
+++ b/.github/workflows/compile.yml
@@ -21,9 +21,6 @@ on:
node_version:
required: true
type: string
- secrets:
- DEPOT_TOKEN:
- required: true
jobs:
executable:
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 9459fe638a..ce824e94e8 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -53,6 +53,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
- name: Run lint and unit tests in dev container
uses: devcontainers/ci@v0.3
with:
diff --git a/.github/workflows/login-container.yml b/.github/workflows/login-container.yml
index 5137213cc4..4e78ba68cd 100644
--- a/.github/workflows/login-container.yml
+++ b/.github/workflows/login-container.yml
@@ -14,9 +14,6 @@ on:
login_build_image:
description: 'The full image tag of the standalone login image'
value: '${{ inputs.login_build_image_name }}:${{ github.sha }}'
- secrets:
- DEPOT_TOKEN:
- required: true
permissions:
packages: write
@@ -35,7 +32,6 @@ jobs:
packages: write
steps:
- uses: actions/checkout@v4
- - uses: depot/setup-action@v1
- name: Login meta
id: login-meta
uses: docker/metadata-action@v5
@@ -52,17 +48,20 @@ jobs:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
- name: Bake login multi-arch
- uses: depot/bake-action@v1
+ uses: docker/bake-action@v6
env:
- DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
NODE_VERSION: ${{ inputs.node_version }}
with:
push: true
provenance: true
sbom: true
targets: login-standalone
- project: w47wkxzdtw
+ set: |
+ *.cache-from=type=gha
+ *.cache-to=type=gha,mode=max
files: |
./apps/login/docker-bake.hcl
./apps/login/docker-bake-release.hcl
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4c1ae53072..3d6e517a8a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -200,7 +200,7 @@ With [make](https://www.gnu.org/software/make/), you build a debuggable Zitadel
Then, you test your changes via the console your binary is serving at http://localhost:8080 and by verifying the database.
Once you are happy with your changes, you run end-to-end tests and tear everything down.
-Zitadel uses [golangci-lint](https://golangci-lint.run) for code quality checks. Please use [this configuration](.golangci.yaml) when running `golangci-lint`. We recommend to set golangci-lint as linter in your IDE.
+Zitadel uses [golangci-lint v2](https://golangci-lint.run) for code quality checks. Please use [this configuration](.golangci.yaml) when running `golangci-lint`. We recommend to set golangci-lint as linter in your IDE.
The commands in this section are tested against the following software versions:
diff --git a/apps/login/acceptance/package.json b/apps/login/acceptance/package.json
index fc4a191373..87cbe9a7e1 100644
--- a/apps/login/acceptance/package.json
+++ b/apps/login/acceptance/package.json
@@ -4,7 +4,8 @@
"scripts": {
"test:acceptance": "dotenv -e ../login/.env.test.local playwright",
"test:acceptance:setup": "cd ../.. && make login_test_acceptance_setup_env && NODE_ENV=test turbo run test:acceptance:setup:dev",
- "test:acceptance:setup:dev": "cd ../.. && make login_test_acceptance_setup_dev"
+ "test:acceptance:setup:dev": "cd ../.. && make login_test_acceptance_setup_dev",
+ "clean": "rm -rf .turbo node_modules"
},
"devDependencies": {
"@faker-js/faker": "^9.7.0",
diff --git a/cmd/start/start.go b/cmd/start/start.go
index 9c1e2a4d28..adbac7f822 100644
--- a/cmd/start/start.go
+++ b/cmd/start/start.go
@@ -482,7 +482,7 @@ func startAPIs(
if err := apis.RegisterService(ctx, session_v2beta.CreateServer(commands, queries, permissionCheck)); err != nil {
return nil, err
}
- if err := apis.RegisterService(ctx, settings_v2beta.CreateServer(commands, queries)); err != nil {
+ if err := apis.RegisterService(ctx, settings_v2beta.CreateServer(config.SystemDefaults, commands, queries, permissionCheck)); err != nil {
return nil, err
}
if err := apis.RegisterService(ctx, org_v2beta.CreateServer(config.SystemDefaults, commands, queries, permissionCheck)); err != nil {
diff --git a/console/package.json b/console/package.json
index 698ea7984e..1eafec0502 100644
--- a/console/package.json
+++ b/console/package.json
@@ -10,7 +10,8 @@
"lint:check:ng": "ng lint",
"lint:check:prettier": "prettier --check src",
"lint:fix": "prettier --write src",
- "generate": "pnpm exec buf generate ../proto --include-imports --include-wkt"
+ "generate": "pnpm exec buf generate ../proto --include-imports --include-wkt",
+ "clean": "rm -rf dist .angular .turbo node_modules src/app/proto/generated"
},
"private": true,
"dependencies": {
diff --git a/console/src/app/modules/actions-two/actions-two-actions/actions-two-actions.component.ts b/console/src/app/modules/actions-two/actions-two-actions/actions-two-actions.component.ts
index 7e0d457dd5..6fa6c2f307 100644
--- a/console/src/app/modules/actions-two/actions-two-actions/actions-two-actions.component.ts
+++ b/console/src/app/modules/actions-two/actions-two-actions/actions-two-actions.component.ts
@@ -45,7 +45,7 @@ export class ActionsTwoActionsComponent {
switchMap(() => {
return this.actionService.listExecutions({ sortingColumn: ExecutionFieldName.ID, pagination: { asc: true } });
}),
- map(({ result }) => result.map(correctlyTypeExecution)),
+ map(({ executions }) => executions.map(correctlyTypeExecution)),
catchError((err) => {
this.toast.showError(err);
return of([]);
@@ -59,7 +59,7 @@ export class ActionsTwoActionsComponent {
switchMap(() => {
return this.actionService.listTargets({});
}),
- map(({ result }) => result),
+ map(({ targets }) => targets),
catchError((err) => {
this.toast.showError(err);
return of([]);
diff --git a/console/src/app/modules/actions-two/actions-two-add-action/actions-two-add-action-target/actions-two-add-action-target.component.ts b/console/src/app/modules/actions-two/actions-two-add-action/actions-two-add-action-target/actions-two-add-action-target.component.ts
index 60b4025650..e791227c99 100644
--- a/console/src/app/modules/actions-two/actions-two-add-action/actions-two-add-action-target/actions-two-add-action-target.component.ts
+++ b/console/src/app/modules/actions-two/actions-two-add-action/actions-two-add-action-target/actions-two-add-action-target.component.ts
@@ -115,13 +115,13 @@ export class ActionsTwoAddActionTargetComponent {
this.actionService
.listTargets({})
- .then(({ result }) => {
- const targets = result.reduce((acc, target) => {
+ .then(({ targets }) => {
+ const result = targets.reduce((acc, target) => {
acc.set(target.id, target);
return acc;
}, new Map());
- targetsSignal.set({ state: 'loaded', targets });
+ targetsSignal.set({ state: 'loaded', targets: result });
})
.catch((error) => {
this.toast.showError(error);
diff --git a/console/src/app/modules/actions-two/actions-two-targets/actions-two-targets.component.ts b/console/src/app/modules/actions-two/actions-two-targets/actions-two-targets.component.ts
index f678b86482..074d6a910d 100644
--- a/console/src/app/modules/actions-two/actions-two-targets/actions-two-targets.component.ts
+++ b/console/src/app/modules/actions-two/actions-two-targets/actions-two-targets.component.ts
@@ -39,7 +39,7 @@ export class ActionsTwoTargetsComponent {
switchMap(() => {
return this.actionService.listTargets({});
}),
- map(({ result }) => result),
+ map(({ targets }) => targets),
catchError((err) => {
this.toast.showError(err);
return of([]);
diff --git a/docs/docs/apis/actions/external-authentication.md b/docs/docs/apis/actions/external-authentication.md
index 114185871b..33afb3d344 100644
--- a/docs/docs/apis/actions/external-authentication.md
+++ b/docs/docs/apis/actions/external-authentication.md
@@ -27,7 +27,7 @@ The first parameter contains the following fields
- `idToken` *string*
The id token provided by the identity provider.
- `v1`
- - `externalUser()` [*externalUser*](./objects#external-user)
+ - `externalUser` [*externalUser*](./objects#external-user)
- `authError` *string*
This is a verification errors string representation. If the verification succeeds, this is "none"
- `authRequest` [*auth request*](/docs/apis/actions/objects#auth-request)
diff --git a/docs/docs/apis/openidoauth/endpoints.mdx b/docs/docs/apis/openidoauth/endpoints.mdx
index 13745eaeb1..79d533ab3a 100644
--- a/docs/docs/apis/openidoauth/endpoints.mdx
+++ b/docs/docs/apis/openidoauth/endpoints.mdx
@@ -656,12 +656,14 @@ The endpoint has to be opened in the user agent (browser) to terminate the user
No parameters are needed apart from the user agent cookie, but you can provide the following to customize the behavior:
-| Parameter | Description |
-| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
-| id_token_hint | the id_token that was previously issued to the client |
-| client_id | client_id of the application |
-| post_logout_redirect_uri | Callback uri of the logout where the user (agent) will be redirected to. Must match exactly one of the preregistered in Console. |
-| state | Opaque value used to maintain state between the request and the callback |
+| Parameter | Description |
+| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| id_token_hint | the id_token that was previously issued to the client |
+| client_id | client_id of the application |
+| post_logout_redirect_uri | Callback uri of the logout where the user (agent) will be redirected to. Must match exactly one of the preregistered in Console. |
+| state | Opaque value used to maintain state between the request and the callback |
+| logout_hint | A valid login name of a user. Will be used to select the user to logout. Only supported when using the login UI V2. |
+| ui_locales | Spaces delimited list of preferred locales for the login UI, e.g. `de-CH de en`. If none is provided or matches the possible locales provided by the login UI, the `accept-language` header of the browser will be taken into account. |
The `post_logout_redirect_uri` will be checked against the previously registered uris of the client provided by the `azp` claim of the `id_token_hint` or the `client_id` parameter.
If both parameters are provided, they must be equal.
diff --git a/e2e/package.json b/e2e/package.json
index b465d6b6d9..b10f313c57 100644
--- a/e2e/package.json
+++ b/e2e/package.json
@@ -13,7 +13,8 @@
"test:open:angular": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 CYPRESS_WEBHOOK_HANDLER_HOST=host.docker.internal pnpm run open --",
"test:e2e:angular": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 CYPRESS_WEBHOOK_HANDLER_HOST=host.docker.internal pnpm run e2e --",
"lint": "prettier --check cypress",
- "lint:fix": "prettier --write cypress"
+ "lint:fix": "prettier --write cypress",
+ "clean": "rm -rf .turbo node_modules"
},
"private": true,
"dependencies": {
diff --git a/go.mod b/go.mod
index 155fdd4b12..cbbc2bff88 100644
--- a/go.mod
+++ b/go.mod
@@ -84,7 +84,7 @@ require (
github.com/twilio/twilio-go v1.26.1
github.com/zitadel/exifremove v0.1.0
github.com/zitadel/logging v0.6.2
- github.com/zitadel/oidc/v3 v3.39.1
+ github.com/zitadel/oidc/v3 v3.42.0
github.com/zitadel/passwap v0.9.0
github.com/zitadel/saml v0.3.5
github.com/zitadel/schema v1.3.1
@@ -103,8 +103,8 @@ require (
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
golang.org/x/net v0.40.0
golang.org/x/oauth2 v0.30.0
- golang.org/x/sync v0.15.0
- golang.org/x/text v0.26.0
+ golang.org/x/sync v0.16.0
+ golang.org/x/text v0.27.0
google.golang.org/api v0.233.0
google.golang.org/genproto/googleapis/api v0.0.0-20250512202823-5a2f75b736a9
google.golang.org/grpc v1.72.1
@@ -124,7 +124,7 @@ require (
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
- github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect
+ github.com/bmatcuk/doublestar/v4 v4.9.0 // indirect
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
github.com/crewjam/httperr v0.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
diff --git a/go.sum b/go.sum
index e312184c51..257ec0e37b 100644
--- a/go.sum
+++ b/go.sum
@@ -108,8 +108,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
-github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
+github.com/bmatcuk/doublestar/v4 v4.9.0 h1:DBvuZxjdKkRP/dr4GVV4w2fnmrk5Hxc90T51LZjv0JA=
+github.com/bmatcuk/doublestar/v4 v4.9.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4=
github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
@@ -827,8 +827,8 @@ github.com/zitadel/exifremove v0.1.0 h1:qD50ezWsfeeqfcvs79QyyjVfK+snN12v0U0deaU8
github.com/zitadel/exifremove v0.1.0/go.mod h1:rzKJ3woL/Rz2KthVBiSBKIBptNTvgmk9PLaeUKTm+ek=
github.com/zitadel/logging v0.6.2 h1:MW2kDDR0ieQynPZ0KIZPrh9ote2WkxfBif5QoARDQcU=
github.com/zitadel/logging v0.6.2/go.mod h1:z6VWLWUkJpnNVDSLzrPSQSQyttysKZ6bCRongw0ROK4=
-github.com/zitadel/oidc/v3 v3.39.1 h1:6QwGwI3yxh4somT7fwRCeT1KOn/HOGv0PA0dFciwJjE=
-github.com/zitadel/oidc/v3 v3.39.1/go.mod h1:aH8brOrzoliAybVdfq2xIdGvbtl0j/VsKRNa7WE72gI=
+github.com/zitadel/oidc/v3 v3.42.0 h1:cqlCYIEapmDprp5a5hUl9ivkUOu1SQxOqbrKdalHqGk=
+github.com/zitadel/oidc/v3 v3.42.0/go.mod h1:Y/rY7mHTzMGrZgf7REgQZFWxySlaSVqqFdBmNZq+9wA=
github.com/zitadel/passwap v0.9.0 h1:QvDK8OHKdb73C0m+mwXvu87UJSBqix3oFwTVENHdv80=
github.com/zitadel/passwap v0.9.0/go.mod h1:6QzwFjDkIr3FfudzSogTOx5Ydhq4046dRJtDM/kX+G8=
github.com/zitadel/saml v0.3.5 h1:L1RKWS5y66cGepVxUGjx/WSBOtrtSpRA/J3nn5BJLOY=
@@ -965,8 +965,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
-golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
+golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1009,8 +1009,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
-golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
+golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
+golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
diff --git a/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go b/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go
index 9fa568fb8b..a2e6131e11 100644
--- a/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go
+++ b/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go
@@ -477,10 +477,10 @@ func waitForExecutionOnCondition(ctx context.Context, t *testing.T, instance *in
if !assert.NoError(ttt, err) {
return
}
- if !assert.Len(ttt, got.GetResult(), 1) {
+ if !assert.Len(ttt, got.GetExecutions(), 1) {
return
}
- gotTargets := got.GetResult()[0].GetTargets()
+ gotTargets := got.GetExecutions()[0].GetTargets()
// always first check length, otherwise its failed anyway
if assert.Len(ttt, gotTargets, len(targets)) {
for i := range targets {
@@ -506,10 +506,10 @@ func waitForTarget(ctx context.Context, t *testing.T, instance *integration.Inst
if !assert.NoError(ttt, err) {
return
}
- if !assert.Len(ttt, got.GetResult(), 1) {
+ if !assert.Len(ttt, got.GetTargets(), 1) {
return
}
- config := got.GetResult()[0]
+ config := got.GetTargets()[0]
assert.Equal(ttt, config.GetEndpoint(), endpoint)
switch ty {
case domain.TargetTypeWebhook:
@@ -605,7 +605,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) {
{Key: "added", Value: "value"},
},
}
- return expectPreUserinfoExecution(ctx, t, instance, req, response)
+ return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response)
},
req: &oidc_pb.CreateCallbackRequest{
AuthRequestId: func() string {
@@ -630,7 +630,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) {
"addedLog",
},
}
- return expectPreUserinfoExecution(ctx, t, instance, req, response)
+ return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response)
},
req: &oidc_pb.CreateCallbackRequest{
AuthRequestId: func() string {
@@ -655,7 +655,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) {
{Key: "key", Value: []byte("value")},
},
}
- return expectPreUserinfoExecution(ctx, t, instance, req, response)
+ return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response)
},
req: &oidc_pb.CreateCallbackRequest{
AuthRequestId: func() string {
@@ -692,7 +692,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) {
{Key: "added3", Value: "value3"},
},
}
- return expectPreUserinfoExecution(ctx, t, instance, req, response)
+ return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response)
},
req: &oidc_pb.CreateCallbackRequest{
AuthRequestId: func() string {
@@ -755,7 +755,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) {
}
}
-func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *integration.Instance, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) {
+func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *integration.Instance, clientID string, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) {
userEmail := gofakeit.Email()
userPhone := "+41" + gofakeit.Phone()
userResp := instance.CreateHumanUserVerified(ctx, instance.DefaultOrg.Id, userEmail, userPhone)
@@ -767,7 +767,7 @@ func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *int
SessionToken: sessionResp.GetSessionToken(),
},
}
- expectedContextInfo := contextInfoForUserOIDC(instance, "function/preuserinfo", userResp, userEmail, userPhone)
+ expectedContextInfo := contextInfoForUserOIDC(instance, "function/preuserinfo", clientID, userResp, userEmail, userPhone)
targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response)
@@ -845,7 +845,7 @@ func getAccessTokenClaims(ctx context.Context, t *testing.T, instance *integrati
return claims
}
-func contextInfoForUserOIDC(instance *integration.Instance, function string, userResp *user.AddHumanUserResponse, email, phone string) *oidc_api.ContextInfo {
+func contextInfoForUserOIDC(instance *integration.Instance, function string, clientID string, userResp *user.AddHumanUserResponse, email, phone string) *oidc_api.ContextInfo {
return &oidc_api.ContextInfo{
Function: function,
UserInfo: &oidc.UserInfo{
@@ -878,6 +878,9 @@ func contextInfoForUserOIDC(instance *integration.Instance, function string, use
},
},
UserMetadata: nil,
+ Application: &oidc_api.ContextInfoApplication{
+ ClientID: clientID,
+ },
Org: &query.UserInfoOrg{
ID: instance.DefaultOrg.GetId(),
Name: instance.DefaultOrg.GetName(),
@@ -918,7 +921,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) {
{Key: "added1", Value: "value"},
},
}
- return expectPreAccessTokenExecution(ctx, t, instance, req, response)
+ return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response)
},
req: &oidc_pb.CreateCallbackRequest{
AuthRequestId: func() string {
@@ -943,7 +946,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) {
"addedLog",
},
}
- return expectPreAccessTokenExecution(ctx, t, instance, req, response)
+ return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response)
},
req: &oidc_pb.CreateCallbackRequest{
AuthRequestId: func() string {
@@ -968,7 +971,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) {
{Key: "key", Value: []byte("value")},
},
}
- return expectPreAccessTokenExecution(ctx, t, instance, req, response)
+ return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response)
},
req: &oidc_pb.CreateCallbackRequest{
AuthRequestId: func() string {
@@ -1005,7 +1008,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) {
{Key: "added3", Value: "value3"},
},
}
- return expectPreAccessTokenExecution(ctx, t, instance, req, response)
+ return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response)
},
req: &oidc_pb.CreateCallbackRequest{
AuthRequestId: func() string {
@@ -1060,7 +1063,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) {
}
}
-func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance *integration.Instance, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) {
+func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance *integration.Instance, clientID string, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) {
userEmail := gofakeit.Email()
userPhone := "+41" + gofakeit.Phone()
userResp := instance.CreateHumanUserVerified(ctx, instance.DefaultOrg.Id, userEmail, userPhone)
@@ -1072,7 +1075,7 @@ func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance *
SessionToken: sessionResp.GetSessionToken(),
},
}
- expectedContextInfo := contextInfoForUserOIDC(instance, "function/preaccesstoken", userResp, userEmail, userPhone)
+ expectedContextInfo := contextInfoForUserOIDC(instance, "function/preaccesstoken", clientID, userResp, userEmail, userPhone)
targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response)
diff --git a/internal/api/grpc/action/v2beta/integration_test/query_test.go b/internal/api/grpc/action/v2beta/integration_test/query_test.go
index 2d74486f3e..65cc541123 100644
--- a/internal/api/grpc/action/v2beta/integration_test/query_test.go
+++ b/internal/api/grpc/action/v2beta/integration_test/query_test.go
@@ -206,7 +206,7 @@ func TestServer_GetTarget(t *testing.T) {
}
assert.NoError(ttt, err)
assert.EqualExportedValues(ttt, tt.want, got)
- }, retryDuration, tick, "timeout waiting for expected target result")
+ }, retryDuration, tick, "timeout waiting for expected target Executions")
})
}
}
@@ -253,7 +253,7 @@ func TestServer_ListTargets(t *testing.T) {
TotalResult: 0,
AppliedLimit: 100,
},
- Result: []*action.Target{},
+ Targets: []*action.Target{},
},
},
{
@@ -269,11 +269,11 @@ func TestServer_ListTargets(t *testing.T) {
},
}
- response.Result[0].Id = resp.GetId()
- response.Result[0].Name = name
- response.Result[0].CreationDate = resp.GetCreationDate()
- response.Result[0].ChangeDate = resp.GetCreationDate()
- response.Result[0].SigningKey = resp.GetSigningKey()
+ response.Targets[0].Id = resp.GetId()
+ response.Targets[0].Name = name
+ response.Targets[0].CreationDate = resp.GetCreationDate()
+ response.Targets[0].ChangeDate = resp.GetCreationDate()
+ response.Targets[0].SigningKey = resp.GetSigningKey()
},
req: &action.ListTargetsRequest{
Filters: []*action.TargetSearchFilter{{}},
@@ -284,7 +284,7 @@ func TestServer_ListTargets(t *testing.T) {
TotalResult: 1,
AppliedLimit: 100,
},
- Result: []*action.Target{
+ Targets: []*action.Target{
{
Endpoint: "https://example.com",
TargetType: &action.Target_RestWebhook{
@@ -309,11 +309,11 @@ func TestServer_ListTargets(t *testing.T) {
},
}
- response.Result[0].Id = resp.GetId()
- response.Result[0].Name = name
- response.Result[0].CreationDate = resp.GetCreationDate()
- response.Result[0].ChangeDate = resp.GetCreationDate()
- response.Result[0].SigningKey = resp.GetSigningKey()
+ response.Targets[0].Id = resp.GetId()
+ response.Targets[0].Name = name
+ response.Targets[0].CreationDate = resp.GetCreationDate()
+ response.Targets[0].ChangeDate = resp.GetCreationDate()
+ response.Targets[0].SigningKey = resp.GetSigningKey()
},
req: &action.ListTargetsRequest{
Filters: []*action.TargetSearchFilter{{}},
@@ -324,7 +324,7 @@ func TestServer_ListTargets(t *testing.T) {
TotalResult: 1,
AppliedLimit: 100,
},
- Result: []*action.Target{
+ Targets: []*action.Target{
{
Endpoint: "https://example.com",
TargetType: &action.Target_RestWebhook{
@@ -354,23 +354,23 @@ func TestServer_ListTargets(t *testing.T) {
},
}
- response.Result[2].Id = resp1.GetId()
- response.Result[2].Name = name1
- response.Result[2].CreationDate = resp1.GetCreationDate()
- response.Result[2].ChangeDate = resp1.GetCreationDate()
- response.Result[2].SigningKey = resp1.GetSigningKey()
+ response.Targets[2].Id = resp1.GetId()
+ response.Targets[2].Name = name1
+ response.Targets[2].CreationDate = resp1.GetCreationDate()
+ response.Targets[2].ChangeDate = resp1.GetCreationDate()
+ response.Targets[2].SigningKey = resp1.GetSigningKey()
- response.Result[1].Id = resp2.GetId()
- response.Result[1].Name = name2
- response.Result[1].CreationDate = resp2.GetCreationDate()
- response.Result[1].ChangeDate = resp2.GetCreationDate()
- response.Result[1].SigningKey = resp2.GetSigningKey()
+ response.Targets[1].Id = resp2.GetId()
+ response.Targets[1].Name = name2
+ response.Targets[1].CreationDate = resp2.GetCreationDate()
+ response.Targets[1].ChangeDate = resp2.GetCreationDate()
+ response.Targets[1].SigningKey = resp2.GetSigningKey()
- response.Result[0].Id = resp3.GetId()
- response.Result[0].Name = name3
- response.Result[0].CreationDate = resp3.GetCreationDate()
- response.Result[0].ChangeDate = resp3.GetCreationDate()
- response.Result[0].SigningKey = resp3.GetSigningKey()
+ response.Targets[0].Id = resp3.GetId()
+ response.Targets[0].Name = name3
+ response.Targets[0].CreationDate = resp3.GetCreationDate()
+ response.Targets[0].ChangeDate = resp3.GetCreationDate()
+ response.Targets[0].SigningKey = resp3.GetSigningKey()
},
req: &action.ListTargetsRequest{
Filters: []*action.TargetSearchFilter{{}},
@@ -381,7 +381,7 @@ func TestServer_ListTargets(t *testing.T) {
TotalResult: 3,
AppliedLimit: 100,
},
- Result: []*action.Target{
+ Targets: []*action.Target{
{
Endpoint: "https://example.com",
TargetType: &action.Target_RestAsync{
@@ -427,13 +427,13 @@ func TestServer_ListTargets(t *testing.T) {
require.NoError(ttt, listErr)
// always first check length, otherwise its failed anyway
- if assert.Len(ttt, got.Result, len(tt.want.Result)) {
- for i := range tt.want.Result {
- assert.EqualExportedValues(ttt, tt.want.Result[i], got.Result[i])
+ if assert.Len(ttt, got.Targets, len(tt.want.Targets)) {
+ for i := range tt.want.Targets {
+ assert.EqualExportedValues(ttt, tt.want.Targets[i], got.Targets[i])
}
}
assertPaginationResponse(ttt, tt.want.Pagination, got.Pagination)
- }, retryDuration, tick, "timeout waiting for expected execution result")
+ }, retryDuration, tick, "timeout waiting for expected execution Executions")
})
}
}
@@ -476,9 +476,9 @@ func TestServer_ListExecutions(t *testing.T) {
resp := instance.SetExecution(ctx, t, cond, []string{targetResp.GetId()})
// Set expected response with used values for SetExecution
- response.Result[0].CreationDate = resp.GetSetDate()
- response.Result[0].ChangeDate = resp.GetSetDate()
- response.Result[0].Condition = cond
+ response.Executions[0].CreationDate = resp.GetSetDate()
+ response.Executions[0].ChangeDate = resp.GetSetDate()
+ response.Executions[0].Condition = cond
},
req: &action.ListExecutionsRequest{
Filters: []*action.ExecutionSearchFilter{{
@@ -503,7 +503,7 @@ func TestServer_ListExecutions(t *testing.T) {
TotalResult: 1,
AppliedLimit: 100,
},
- Result: []*action.Execution{
+ Executions: []*action.Execution{
{
Condition: &action.Condition{
ConditionType: &action.Condition_Request{
@@ -544,10 +544,10 @@ func TestServer_ListExecutions(t *testing.T) {
}
resp := instance.SetExecution(ctx, t, cond, []string{target.GetId()})
- response.Result[0].CreationDate = resp.GetSetDate()
- response.Result[0].ChangeDate = resp.GetSetDate()
- response.Result[0].Condition = cond
- response.Result[0].Targets = []string{target.GetId()}
+ response.Executions[0].CreationDate = resp.GetSetDate()
+ response.Executions[0].ChangeDate = resp.GetSetDate()
+ response.Executions[0].Condition = cond
+ response.Executions[0].Targets = []string{target.GetId()}
},
req: &action.ListExecutionsRequest{
Filters: []*action.ExecutionSearchFilter{{}},
@@ -558,7 +558,7 @@ func TestServer_ListExecutions(t *testing.T) {
TotalResult: 1,
AppliedLimit: 100,
},
- Result: []*action.Execution{
+ Executions: []*action.Execution{
{
Condition: &action.Condition{},
Targets: []string{""},
@@ -604,7 +604,7 @@ func TestServer_ListExecutions(t *testing.T) {
cond1 := request.Filters[0].GetInConditionsFilter().GetConditions()[0]
resp1 := instance.SetExecution(ctx, t, cond1, []string{targetResp.GetId()})
- response.Result[2] = &action.Execution{
+ response.Executions[2] = &action.Execution{
CreationDate: resp1.GetSetDate(),
ChangeDate: resp1.GetSetDate(),
Condition: cond1,
@@ -613,7 +613,7 @@ func TestServer_ListExecutions(t *testing.T) {
cond2 := request.Filters[0].GetInConditionsFilter().GetConditions()[1]
resp2 := instance.SetExecution(ctx, t, cond2, []string{targetResp.GetId()})
- response.Result[1] = &action.Execution{
+ response.Executions[1] = &action.Execution{
CreationDate: resp2.GetSetDate(),
ChangeDate: resp2.GetSetDate(),
Condition: cond2,
@@ -622,7 +622,7 @@ func TestServer_ListExecutions(t *testing.T) {
cond3 := request.Filters[0].GetInConditionsFilter().GetConditions()[2]
resp3 := instance.SetExecution(ctx, t, cond3, []string{targetResp.GetId()})
- response.Result[0] = &action.Execution{
+ response.Executions[0] = &action.Execution{
CreationDate: resp3.GetSetDate(),
ChangeDate: resp3.GetSetDate(),
Condition: cond3,
@@ -640,7 +640,7 @@ func TestServer_ListExecutions(t *testing.T) {
TotalResult: 3,
AppliedLimit: 100,
},
- Result: []*action.Execution{
+ Executions: []*action.Execution{
{}, {}, {},
},
},
@@ -653,7 +653,7 @@ func TestServer_ListExecutions(t *testing.T) {
conditions := request.Filters[0].GetInConditionsFilter().GetConditions()
for i, cond := range conditions {
resp := instance.SetExecution(ctx, t, cond, []string{targetResp.GetId()})
- response.Result[(len(conditions)-1)-i] = &action.Execution{
+ response.Executions[(len(conditions)-1)-i] = &action.Execution{
CreationDate: resp.GetSetDate(),
ChangeDate: resp.GetSetDate(),
Condition: cond,
@@ -687,7 +687,7 @@ func TestServer_ListExecutions(t *testing.T) {
TotalResult: 10,
AppliedLimit: 100,
},
- Result: []*action.Execution{
+ Executions: []*action.Execution{
{},
{},
{},
@@ -709,7 +709,7 @@ func TestServer_ListExecutions(t *testing.T) {
conditions := request.Filters[0].GetInConditionsFilter().GetConditions()
for i, cond := range conditions {
resp := instance.SetExecution(ctx, t, cond, []string{targetResp.GetId()})
- response.Result[i] = &action.Execution{
+ response.Executions[i] = &action.Execution{
CreationDate: resp.GetSetDate(),
ChangeDate: resp.GetSetDate(),
Condition: cond,
@@ -744,7 +744,7 @@ func TestServer_ListExecutions(t *testing.T) {
TotalResult: 10,
AppliedLimit: 100,
},
- Result: []*action.Execution{
+ Executions: []*action.Execution{
{},
{},
{},
@@ -774,11 +774,11 @@ func TestServer_ListExecutions(t *testing.T) {
}
require.NoError(ttt, listErr)
// always first check length, otherwise its failed anyway
- if assert.Len(ttt, got.Result, len(tt.want.Result)) {
- assert.EqualExportedValues(ttt, got.Result, tt.want.Result)
+ if assert.Len(ttt, got.Executions, len(tt.want.Executions)) {
+ assert.EqualExportedValues(ttt, got.Executions, tt.want.Executions)
}
assertPaginationResponse(ttt, tt.want.Pagination, got.Pagination)
- }, retryDuration, tick, "timeout waiting for expected execution result")
+ }, retryDuration, tick, "timeout waiting for expected execution Executions")
})
}
}
diff --git a/internal/api/grpc/action/v2beta/query.go b/internal/api/grpc/action/v2beta/query.go
index 9428b6ab7b..64cf3b7618 100644
--- a/internal/api/grpc/action/v2beta/query.go
+++ b/internal/api/grpc/action/v2beta/query.go
@@ -52,7 +52,7 @@ func (s *Server) ListTargets(ctx context.Context, req *connect.Request[action.Li
return nil, err
}
return connect.NewResponse(&action.ListTargetsResponse{
- Result: targetsToPb(resp.Targets),
+ Targets: targetsToPb(resp.Targets),
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
}), nil
}
@@ -67,7 +67,7 @@ func (s *Server) ListExecutions(ctx context.Context, req *connect.Request[action
return nil, err
}
return connect.NewResponse(&action.ListExecutionsResponse{
- Result: executionsToPb(resp.Executions),
+ Executions: executionsToPb(resp.Executions),
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
}), nil
}
diff --git a/internal/api/grpc/authorization/v2beta/integration_test/query_test.go b/internal/api/grpc/authorization/v2beta/integration_test/query_test.go
index fd0a807c1d..c3579d9192 100644
--- a/internal/api/grpc/authorization/v2beta/integration_test/query_test.go
+++ b/internal/api/grpc/authorization/v2beta/integration_test/query_test.go
@@ -549,7 +549,8 @@ func createGrantedProject(ctx context.Context, instance *integration.Instance, t
}
func TestServer_ListAuthorizations_PermissionsV2(t *testing.T) {
- ensureFeaturePermissionV2Enabled(t, InstancePermissionV2)
+ // removed as permission v2 is not implemented yet for project grant level permissions
+ // ensureFeaturePermissionV2Enabled(t, InstancePermissionV2)
iamOwnerCtx := InstancePermissionV2.WithAuthorizationToken(EmptyCTX, integration.UserTypeIAMOwner)
projectOwnerResp := InstancePermissionV2.CreateMachineUser(iamOwnerCtx)
@@ -558,11 +559,11 @@ func TestServer_ListAuthorizations_PermissionsV2(t *testing.T) {
InstancePermissionV2.CreateProjectMembership(t, iamOwnerCtx, projectResp.GetId(), projectOwnerResp.GetUserId())
projectOwnerCtx := integration.WithAuthorizationToken(EmptyCTX, projectOwnerPatResp.Token)
- //projectGrantOwnerResp := InstancePermissionV2.CreateMachineUser(iamOwnerCtx)
- //projectGrantOwnerPatResp := InstancePermissionV2.CreatePersonalAccessToken(iamOwnerCtx, projectGrantOwnerResp.GetUserId())
+ projectGrantOwnerResp := InstancePermissionV2.CreateMachineUser(iamOwnerCtx)
+ projectGrantOwnerPatResp := InstancePermissionV2.CreatePersonalAccessToken(iamOwnerCtx, projectGrantOwnerResp.GetUserId())
grantedProjectResp := createGrantedProject(iamOwnerCtx, InstancePermissionV2, t, projectResp)
- //InstancePermissionV2.CreateProjectGrantMembership(t, iamOwnerCtx, projectResp.GetId(), grantedProjectResp.GetGrantedOrganizationId(), projectGrantOwnerResp.GetUserId())
- //projectGrantOwnerCtx := integration.WithAuthorizationToken(EmptyCTX, projectGrantOwnerPatResp.Token)
+ InstancePermissionV2.CreateProjectGrantMembership(t, iamOwnerCtx, projectResp.GetId(), grantedProjectResp.GetGrantedOrganizationId(), projectGrantOwnerResp.GetUserId())
+ projectGrantOwnerCtx := integration.WithAuthorizationToken(EmptyCTX, projectGrantOwnerPatResp.Token)
type args struct {
ctx context.Context
@@ -615,7 +616,7 @@ func TestServer_ListAuthorizations_PermissionsV2(t *testing.T) {
},
want: &authorization.ListAuthorizationsResponse{
Pagination: &filter.PaginationResponse{
- TotalResult: 0,
+ TotalResult: 1,
AppliedLimit: 100,
},
Authorizations: []*authorization.Authorization{},
@@ -892,8 +893,8 @@ func TestServer_ListAuthorizations_PermissionsV2(t *testing.T) {
},
}
- response.Authorizations[1] = createAuthorizationForProject(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), projectResp.GetName(), projectResp.GetId())
- response.Authorizations[0] = createAuthorizationWithProjectGrant(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), grantedProjectResp.GetName(), grantedProjectResp.GetId())
+ response.Authorizations[0] = createAuthorizationForProject(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), projectResp.GetName(), projectResp.GetId())
+ createAuthorizationWithProjectGrant(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), grantedProjectResp.GetName(), grantedProjectResp.GetId())
},
req: &authorization.ListAuthorizationsRequest{
Filters: []*authorization.AuthorizationsSearchFilter{{}},
@@ -905,43 +906,40 @@ func TestServer_ListAuthorizations_PermissionsV2(t *testing.T) {
AppliedLimit: 100,
},
Authorizations: []*authorization.Authorization{
- {}, {},
+ {},
},
},
},
- /*
- TODO: correct when permission check is added for project grants https://github.com/zitadel/zitadel/issues/9972
- {
- name: "list single id, project and project grant, project grant owner",
- args: args{
- ctx: projectGrantOwnerCtx,
- dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
- userResp := InstancePermissionV2.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
+ {
+ name: "list single id, project and project grant, project grant owner",
+ args: args{
+ ctx: projectGrantOwnerCtx,
+ dep: func(request *authorization.ListAuthorizationsRequest, response *authorization.ListAuthorizationsResponse) {
+ userResp := InstancePermissionV2.CreateUserTypeHuman(iamOwnerCtx, gofakeit.Email())
- request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
- UserId: &filter.IDFilter{
- Id: userResp.GetId(),
- },
- }
+ request.Filters[0].Filter = &authorization.AuthorizationsSearchFilter_UserId{
+ UserId: &filter.IDFilter{
+ Id: userResp.GetId(),
+ },
+ }
- createAuthorizationForProject(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), projectResp.GetName(), projectResp.GetId())
- response.Authorizations[0] = createAuthorizationForProjectGrant(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), grantedProjectResp.GetName(), grantedProjectResp.GetId())
- },
- req: &authorization.ListAuthorizationsRequest{
- Filters: []*authorization.AuthorizationsSearchFilter{{}},
- },
+ createAuthorizationForProject(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), projectResp.GetName(), projectResp.GetId())
+ response.Authorizations[0] = createAuthorizationForProjectGrant(iamOwnerCtx, InstancePermissionV2, t, InstancePermissionV2.DefaultOrg.GetId(), userResp.GetId(), grantedProjectResp.GetName(), grantedProjectResp.GetId(), grantedProjectResp.GetGrantedOrganizationId())
},
- want: &authorization.ListAuthorizationsResponse{
- Pagination: &filter.PaginationResponse{
- TotalResult: 2,
- AppliedLimit: 100,
- },
- Authorizations: []*authorization.Authorization{
- {},
- },
+ req: &authorization.ListAuthorizationsRequest{
+ Filters: []*authorization.AuthorizationsSearchFilter{{}},
},
},
- */
+ want: &authorization.ListAuthorizationsResponse{
+ Pagination: &filter.PaginationResponse{
+ TotalResult: 2,
+ AppliedLimit: 100,
+ },
+ Authorizations: []*authorization.Authorization{
+ {},
+ },
+ },
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
diff --git a/internal/api/grpc/internal_permission/v2beta/integration_test/query_test.go b/internal/api/grpc/internal_permission/v2beta/integration_test/query_test.go
index 63b07c5194..84eea98992 100644
--- a/internal/api/grpc/internal_permission/v2beta/integration_test/query_test.go
+++ b/internal/api/grpc/internal_permission/v2beta/integration_test/query_test.go
@@ -17,7 +17,7 @@ import (
)
func TestServer_ListAdministrators(t *testing.T) {
- iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
+ iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
projectName := gofakeit.AppName()
projectResp := instance.CreateProject(iamOwnerCtx, t, instance.DefaultOrg.GetId(), projectName, false, false)
@@ -66,7 +66,7 @@ func TestServer_ListAdministrators(t *testing.T) {
{
name: "list by id, no permission",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission),
dep: func(request *internal_permission.ListAdministratorsRequest, response *internal_permission.ListAdministratorsResponse) {
admin := createInstanceAdministrator(iamOwnerCtx, instance, t)
request.Filters[0].Filter = &internal_permission.AdministratorSearchFilter_InUserIdsFilter{
@@ -90,7 +90,7 @@ func TestServer_ListAdministrators(t *testing.T) {
{
name: "list by id, missing permission",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *internal_permission.ListAdministratorsRequest, response *internal_permission.ListAdministratorsResponse) {
admin := createInstanceAdministrator(iamOwnerCtx, instance, t)
request.Filters[0].Filter = &internal_permission.AdministratorSearchFilter_InUserIdsFilter{
@@ -427,7 +427,7 @@ func TestServer_ListAdministrators(t *testing.T) {
{
name: "list multiple id, org owner",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *internal_permission.ListAdministratorsRequest, response *internal_permission.ListAdministratorsResponse) {
admin1 := createInstanceAdministrator(iamOwnerCtx, instance, t)
admin2 := createOrganizationAdministrator(iamOwnerCtx, instance, t)
@@ -644,8 +644,9 @@ func createProjectGrantAdministrator(ctx context.Context, instance *integration.
}
func TestServer_ListAdministrators_PermissionV2(t *testing.T) {
- ensureFeaturePermissionV2Enabled(t, instancePermissionV2)
- iamOwnerCtx := instancePermissionV2.WithAuthorization(CTX, integration.UserTypeIAMOwner)
+ // removed as permission v2 is not implemented yet for project grant level permissions
+ // ensureFeaturePermissionV2Enabled(t, instancePermissionV2)
+ iamOwnerCtx := instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
projectName := gofakeit.AppName()
projectResp := instancePermissionV2.CreateProject(iamOwnerCtx, t, instancePermissionV2.DefaultOrg.GetId(), projectName, false, false)
@@ -694,7 +695,7 @@ func TestServer_ListAdministrators_PermissionV2(t *testing.T) {
{
name: "list by id, no permission",
args: args{
- ctx: instancePermissionV2.WithAuthorization(CTX, integration.UserTypeNoPermission),
+ ctx: instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeNoPermission),
dep: func(request *internal_permission.ListAdministratorsRequest, response *internal_permission.ListAdministratorsResponse) {
admin := createInstanceAdministrator(iamOwnerCtx, instancePermissionV2, t)
request.Filters[0].Filter = &internal_permission.AdministratorSearchFilter_InUserIdsFilter{
@@ -709,7 +710,7 @@ func TestServer_ListAdministrators_PermissionV2(t *testing.T) {
},
want: &internal_permission.ListAdministratorsResponse{
Pagination: &filter.PaginationResponse{
- TotalResult: 0,
+ TotalResult: 1,
AppliedLimit: 100,
},
Administrators: []*internal_permission.Administrator{},
@@ -718,7 +719,7 @@ func TestServer_ListAdministrators_PermissionV2(t *testing.T) {
{
name: "list by id, missing permission",
args: args{
- ctx: instancePermissionV2.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *internal_permission.ListAdministratorsRequest, response *internal_permission.ListAdministratorsResponse) {
admin := createInstanceAdministrator(iamOwnerCtx, instancePermissionV2, t)
request.Filters[0].Filter = &internal_permission.AdministratorSearchFilter_InUserIdsFilter{
@@ -733,7 +734,7 @@ func TestServer_ListAdministrators_PermissionV2(t *testing.T) {
},
want: &internal_permission.ListAdministratorsResponse{
Pagination: &filter.PaginationResponse{
- TotalResult: 0,
+ TotalResult: 1,
AppliedLimit: 100,
},
Administrators: []*internal_permission.Administrator{},
@@ -1055,7 +1056,7 @@ func TestServer_ListAdministrators_PermissionV2(t *testing.T) {
{
name: "list multiple id, org owner",
args: args{
- ctx: instancePermissionV2.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *internal_permission.ListAdministratorsRequest, response *internal_permission.ListAdministratorsResponse) {
admin1 := createInstanceAdministrator(iamOwnerCtx, instancePermissionV2, t)
admin2 := createOrganizationAdministrator(iamOwnerCtx, instancePermissionV2, t)
@@ -1076,7 +1077,7 @@ func TestServer_ListAdministrators_PermissionV2(t *testing.T) {
},
want: &internal_permission.ListAdministratorsResponse{
Pagination: &filter.PaginationResponse{
- TotalResult: 3,
+ TotalResult: 4,
AppliedLimit: 100,
},
Administrators: []*internal_permission.Administrator{
@@ -1107,7 +1108,7 @@ func TestServer_ListAdministrators_PermissionV2(t *testing.T) {
},
want: &internal_permission.ListAdministratorsResponse{
Pagination: &filter.PaginationResponse{
- TotalResult: 2,
+ TotalResult: 4,
AppliedLimit: 100,
},
Administrators: []*internal_permission.Administrator{
@@ -1115,7 +1116,6 @@ func TestServer_ListAdministrators_PermissionV2(t *testing.T) {
},
},
},
- // TODO: correct when permission check is added for project grants https://github.com/zitadel/zitadel/issues/9972
{
name: "list multiple id, project grant owner",
args: args{
@@ -1130,7 +1130,7 @@ func TestServer_ListAdministrators_PermissionV2(t *testing.T) {
Ids: []string{admin1.GetUser().GetId(), admin2.GetUser().GetId(), admin3.GetUser().GetId(), admin4.GetUser().GetId()},
},
}
- // response.Administrators[0] = admin4
+ response.Administrators[0] = admin4
},
req: &internal_permission.ListAdministratorsRequest{
Filters: []*internal_permission.AdministratorSearchFilter{{}},
@@ -1138,10 +1138,10 @@ func TestServer_ListAdministrators_PermissionV2(t *testing.T) {
},
want: &internal_permission.ListAdministratorsResponse{
Pagination: &filter.PaginationResponse{
- TotalResult: 0,
+ TotalResult: 4,
AppliedLimit: 100,
},
- Administrators: []*internal_permission.Administrator{},
+ Administrators: []*internal_permission.Administrator{{}},
},
},
}
diff --git a/internal/api/grpc/project/v2beta/integration_test/query_test.go b/internal/api/grpc/project/v2beta/integration_test/query_test.go
index f8159226c7..29fa212976 100644
--- a/internal/api/grpc/project/v2beta/integration_test/query_test.go
+++ b/internal/api/grpc/project/v2beta/integration_test/query_test.go
@@ -18,7 +18,7 @@ import (
)
func TestServer_GetProject(t *testing.T) {
- iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
+ iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
type args struct {
ctx context.Context
@@ -34,7 +34,7 @@ func TestServer_GetProject(t *testing.T) {
{
name: "missing permission",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission),
dep: func(request *project.GetProjectRequest, response *project.GetProjectResponse) {
orgID := instance.DefaultOrg.GetId()
resp := createProject(iamOwnerCtx, instance, t, orgID, false, false)
@@ -48,7 +48,7 @@ func TestServer_GetProject(t *testing.T) {
{
name: "missing permission, other org owner",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.GetProjectRequest, response *project.GetProjectResponse) {
name := gofakeit.AppName()
orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
@@ -94,7 +94,7 @@ func TestServer_GetProject(t *testing.T) {
{
name: "get, ok, org owner",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.GetProjectRequest, response *project.GetProjectResponse) {
orgID := instance.DefaultOrg.GetId()
resp := createProject(iamOwnerCtx, instance, t, orgID, false, false)
@@ -147,7 +147,7 @@ func TestServer_GetProject(t *testing.T) {
}
func TestServer_ListProjects(t *testing.T) {
- iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
+ iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
userResp := instance.CreateMachineUser(iamOwnerCtx)
patResp := instance.CreatePersonalAccessToken(iamOwnerCtx, userResp.GetUserId())
@@ -190,7 +190,7 @@ func TestServer_ListProjects(t *testing.T) {
{
name: "list by id, no permission",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission),
dep: func(request *project.ListProjectsRequest, response *project.ListProjectsResponse) {
name := gofakeit.AppName()
orgID := instance.DefaultOrg.GetId()
@@ -210,7 +210,7 @@ func TestServer_ListProjects(t *testing.T) {
{
name: "list by id, missing permission",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectsRequest, response *project.ListProjectsResponse) {
name := gofakeit.AppName()
orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
@@ -349,7 +349,7 @@ func TestServer_ListProjects(t *testing.T) {
{
name: "list multiple id, limited permissions",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectsRequest, response *project.ListProjectsResponse) {
orgID := instance.DefaultOrg.GetId()
orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
@@ -505,7 +505,7 @@ func TestServer_ListProjects(t *testing.T) {
{
name: "list granted project, project id",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectsRequest, response *project.ListProjectsResponse) {
orgID := instance.DefaultOrg.GetId()
@@ -576,8 +576,9 @@ func TestServer_ListProjects(t *testing.T) {
}
func TestServer_ListProjects_PermissionV2(t *testing.T) {
- ensureFeaturePermissionV2Enabled(t, instancePermissionV2)
- iamOwnerCtx := instancePermissionV2.WithAuthorization(CTX, integration.UserTypeIAMOwner)
+ // removed as permission v2 is not implemented yet for project grant level permissions
+ // ensureFeaturePermissionV2Enabled(t, instancePermissionV2)
+ iamOwnerCtx := instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
orgID := instancePermissionV2.DefaultOrg.GetId()
type args struct {
@@ -612,7 +613,7 @@ func TestServer_ListProjects_PermissionV2(t *testing.T) {
{
name: "list by id, no permission",
args: args{
- ctx: instancePermissionV2.WithAuthorization(CTX, integration.UserTypeNoPermission),
+ ctx: instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeNoPermission),
dep: func(request *project.ListProjectsRequest, response *project.ListProjectsResponse) {
resp := createProject(iamOwnerCtx, instancePermissionV2, t, orgID, false, false)
request.Filters[0].Filter = &project.ProjectSearchFilter_InProjectIdsFilter{
@@ -630,7 +631,7 @@ func TestServer_ListProjects_PermissionV2(t *testing.T) {
{
name: "list by id, missing permission",
args: args{
- ctx: instancePermissionV2.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectsRequest, response *project.ListProjectsResponse) {
orgResp := instancePermissionV2.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
resp := createProject(iamOwnerCtx, instancePermissionV2, t, orgResp.GetOrganizationId(), false, false)
@@ -646,7 +647,7 @@ func TestServer_ListProjects_PermissionV2(t *testing.T) {
},
want: &project.ListProjectsResponse{
Pagination: &filter.PaginationResponse{
- TotalResult: 0,
+ TotalResult: 1,
AppliedLimit: 100,
},
Projects: []*project.Project{},
@@ -848,7 +849,7 @@ func TestServer_ListProjects_PermissionV2(t *testing.T) {
{
name: "list multiple id, limited permissions",
args: args{
- ctx: instancePermissionV2.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectsRequest, response *project.ListProjectsResponse) {
orgResp := instancePermissionV2.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
resp1 := createProject(iamOwnerCtx, instancePermissionV2, t, orgResp.GetOrganizationId(), false, false)
@@ -868,7 +869,7 @@ func TestServer_ListProjects_PermissionV2(t *testing.T) {
},
want: &project.ListProjectsResponse{
Pagination: &filter.PaginationResponse{
- TotalResult: 1,
+ TotalResult: 3,
AppliedLimit: 100,
},
Projects: []*project.Project{
@@ -876,11 +877,10 @@ func TestServer_ListProjects_PermissionV2(t *testing.T) {
},
},
},
- // TODO: correct when permission check is added for project grants https://github.com/zitadel/zitadel/issues/9972
{
name: "list granted project, project id",
args: args{
- ctx: instancePermissionV2.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectsRequest, response *project.ListProjectsResponse) {
orgID := instancePermissionV2.DefaultOrg.GetId()
@@ -888,28 +888,26 @@ func TestServer_ListProjects_PermissionV2(t *testing.T) {
projectName := gofakeit.AppName()
orgResp := instancePermissionV2.CreateOrganization(iamOwnerCtx, orgName, gofakeit.Email())
projectResp := instancePermissionV2.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), projectName, true, true)
- // projectGrantResp :=
- instancePermissionV2.CreateProjectGrant(iamOwnerCtx, t, projectResp.GetId(), orgID)
+ projectGrantResp := instancePermissionV2.CreateProjectGrant(iamOwnerCtx, t, projectResp.GetId(), orgID)
request.Filters[0].Filter = &project.ProjectSearchFilter_InProjectIdsFilter{
InProjectIdsFilter: &filter.InIDsFilter{Ids: []string{projectResp.GetId()}},
}
- /*
- response.Projects[0] = &project.Project{
- Id: projectResp.GetId(),
- Name: projectName,
- OrganizationId: orgResp.GetOrganizationId(),
- CreationDate: projectGrantResp.GetCreationDate(),
- ChangeDate: projectGrantResp.GetCreationDate(),
- State: 1,
- ProjectRoleAssertion: false,
- ProjectAccessRequired: true,
- AuthorizationRequired: true,
- PrivateLabelingSetting: project.PrivateLabelingSetting_PRIVATE_LABELING_SETTING_UNSPECIFIED,
- GrantedOrganizationId: gu.Ptr(orgID),
- GrantedOrganizationName: gu.Ptr(instancePermissionV2.DefaultOrg.GetName()),
- GrantedState: 1,
- }
- */
+
+ response.Projects[0] = &project.Project{
+ Id: projectResp.GetId(),
+ Name: projectName,
+ OrganizationId: orgResp.GetOrganizationId(),
+ CreationDate: projectGrantResp.GetCreationDate(),
+ ChangeDate: projectGrantResp.GetCreationDate(),
+ State: 1,
+ ProjectRoleAssertion: false,
+ ProjectAccessRequired: true,
+ AuthorizationRequired: true,
+ PrivateLabelingSetting: project.PrivateLabelingSetting_PRIVATE_LABELING_SETTING_UNSPECIFIED,
+ GrantedOrganizationId: gu.Ptr(orgID),
+ GrantedOrganizationName: gu.Ptr(instancePermissionV2.DefaultOrg.GetName()),
+ GrantedState: 1,
+ }
},
req: &project.ListProjectsRequest{
Filters: []*project.ProjectSearchFilter{{}},
@@ -917,10 +915,10 @@ func TestServer_ListProjects_PermissionV2(t *testing.T) {
},
want: &project.ListProjectsResponse{
Pagination: &filter.PaginationResponse{
- TotalResult: 0,
+ TotalResult: 2,
AppliedLimit: 100,
},
- Projects: []*project.Project{},
+ Projects: []*project.Project{{}},
},
},
}
@@ -996,7 +994,7 @@ func assertPaginationResponse(t *assert.CollectT, expected *filter.PaginationRes
}
func TestServer_ListProjectGrants(t *testing.T) {
- iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
+ iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
userResp := instance.CreateMachineUser(iamOwnerCtx)
patResp := instance.CreatePersonalAccessToken(iamOwnerCtx, userResp.GetUserId())
@@ -1042,7 +1040,7 @@ func TestServer_ListProjectGrants(t *testing.T) {
{
name: "list by id, no permission",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission),
dep: func(request *project.ListProjectGrantsRequest, response *project.ListProjectGrantsResponse) {
projectResp := instance.CreateProject(iamOwnerCtx, t, instance.DefaultOrg.GetId(), gofakeit.AppName(), false, false)
request.Filters[0].Filter = &project.ProjectGrantSearchFilter_InProjectIdsFilter{
@@ -1088,7 +1086,7 @@ func TestServer_ListProjectGrants(t *testing.T) {
{
name: "list by id",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectGrantsRequest, response *project.ListProjectGrantsResponse) {
name := gofakeit.AppName()
orgID := instance.DefaultOrg.GetId()
@@ -1118,7 +1116,7 @@ func TestServer_ListProjectGrants(t *testing.T) {
{
name: "list by id, missing permission",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectGrantsRequest, response *project.ListProjectGrantsResponse) {
name := gofakeit.AppName()
orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
@@ -1178,7 +1176,7 @@ func TestServer_ListProjectGrants(t *testing.T) {
{
name: "list multiple id, limited permissions",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectGrantsRequest, response *project.ListProjectGrantsResponse) {
name1 := gofakeit.AppName()
name2 := gofakeit.AppName()
@@ -1342,8 +1340,9 @@ func TestServer_ListProjectGrants(t *testing.T) {
}
func TestServer_ListProjectGrants_PermissionV2(t *testing.T) {
- ensureFeaturePermissionV2Enabled(t, instancePermissionV2)
- iamOwnerCtx := instancePermissionV2.WithAuthorization(CTX, integration.UserTypeIAMOwner)
+ // removed as permission v2 is not implemented yet for project grant level permissions
+ // ensureFeaturePermissionV2Enabled(t, instancePermissionV2)
+ iamOwnerCtx := instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
type args struct {
ctx context.Context
@@ -1383,7 +1382,7 @@ func TestServer_ListProjectGrants_PermissionV2(t *testing.T) {
{
name: "list by id, no permission",
args: args{
- ctx: instancePermissionV2.WithAuthorization(CTX, integration.UserTypeNoPermission),
+ ctx: instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeNoPermission),
dep: func(request *project.ListProjectGrantsRequest, response *project.ListProjectGrantsResponse) {
projectResp := instancePermissionV2.CreateProject(iamOwnerCtx, t, instancePermissionV2.DefaultOrg.GetId(), gofakeit.AppName(), false, false)
request.Filters[0].Filter = &project.ProjectGrantSearchFilter_InProjectIdsFilter{
@@ -1407,7 +1406,7 @@ func TestServer_ListProjectGrants_PermissionV2(t *testing.T) {
{
name: "list by id",
args: args{
- ctx: instancePermissionV2.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectGrantsRequest, response *project.ListProjectGrantsResponse) {
name := gofakeit.AppName()
orgID := instancePermissionV2.DefaultOrg.GetId()
@@ -1437,7 +1436,7 @@ func TestServer_ListProjectGrants_PermissionV2(t *testing.T) {
{
name: "list by id, missing permission",
args: args{
- ctx: instancePermissionV2.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectGrantsRequest, response *project.ListProjectGrantsResponse) {
name := gofakeit.AppName()
orgResp := instancePermissionV2.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
@@ -1456,7 +1455,7 @@ func TestServer_ListProjectGrants_PermissionV2(t *testing.T) {
},
want: &project.ListProjectGrantsResponse{
Pagination: &filter.PaginationResponse{
- TotalResult: 0,
+ TotalResult: 1,
AppliedLimit: 100,
},
ProjectGrants: []*project.ProjectGrant{},
@@ -1497,7 +1496,7 @@ func TestServer_ListProjectGrants_PermissionV2(t *testing.T) {
{
name: "list multiple id, limited permissions",
args: args{
- ctx: instancePermissionV2.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectGrantsRequest, response *project.ListProjectGrantsResponse) {
name1 := gofakeit.AppName()
name2 := gofakeit.AppName()
@@ -1523,7 +1522,7 @@ func TestServer_ListProjectGrants_PermissionV2(t *testing.T) {
},
want: &project.ListProjectGrantsResponse{
Pagination: &filter.PaginationResponse{
- TotalResult: 1,
+ TotalResult: 3,
AppliedLimit: 100,
},
ProjectGrants: []*project.ProjectGrant{
@@ -1578,7 +1577,7 @@ func createProjectGrant(ctx context.Context, instance *integration.Instance, t *
}
func TestServer_ListProjectRoles(t *testing.T) {
- iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
+ iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
type args struct {
ctx context.Context
dep func(*project.ListProjectRolesRequest, *project.ListProjectRolesResponse)
@@ -1609,7 +1608,7 @@ func TestServer_ListProjectRoles(t *testing.T) {
{
name: "list by id, no permission",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission),
dep: func(request *project.ListProjectRolesRequest, response *project.ListProjectRolesResponse) {
projectResp := instance.CreateProject(iamOwnerCtx, t, instance.DefaultOrg.GetId(), gofakeit.AppName(), false, false)
@@ -1640,7 +1639,7 @@ func TestServer_ListProjectRoles(t *testing.T) {
{
name: "list single id, missing permission",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectRolesRequest, response *project.ListProjectRolesResponse) {
orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
projectResp := instance.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
@@ -1661,7 +1660,7 @@ func TestServer_ListProjectRoles(t *testing.T) {
{
name: "list single id",
args: args{
- ctx: instance.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectRolesRequest, response *project.ListProjectRolesResponse) {
orgID := instance.DefaultOrg.GetId()
projectResp := instance.CreateProject(iamOwnerCtx, t, orgID, gofakeit.AppName(), false, false)
@@ -1736,7 +1735,7 @@ func TestServer_ListProjectRoles(t *testing.T) {
func TestServer_ListProjectRoles_PermissionV2(t *testing.T) {
ensureFeaturePermissionV2Enabled(t, instancePermissionV2)
- iamOwnerCtx := instancePermissionV2.WithAuthorization(CTX, integration.UserTypeIAMOwner)
+ iamOwnerCtx := instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
type args struct {
ctx context.Context
@@ -1768,7 +1767,7 @@ func TestServer_ListProjectRoles_PermissionV2(t *testing.T) {
{
name: "list by id, no permission",
args: args{
- ctx: instancePermissionV2.WithAuthorization(CTX, integration.UserTypeNoPermission),
+ ctx: instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeNoPermission),
dep: func(request *project.ListProjectRolesRequest, response *project.ListProjectRolesResponse) {
projectResp := instancePermissionV2.CreateProject(iamOwnerCtx, t, instancePermissionV2.DefaultOrg.GetId(), gofakeit.AppName(), false, false)
@@ -1799,7 +1798,7 @@ func TestServer_ListProjectRoles_PermissionV2(t *testing.T) {
{
name: "list single id, missing permission",
args: args{
- ctx: instancePermissionV2.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectRolesRequest, response *project.ListProjectRolesResponse) {
orgResp := instancePermissionV2.CreateOrganization(iamOwnerCtx, gofakeit.AppName(), gofakeit.Email())
projectResp := instancePermissionV2.CreateProject(iamOwnerCtx, t, orgResp.GetOrganizationId(), gofakeit.AppName(), false, false)
@@ -1820,7 +1819,7 @@ func TestServer_ListProjectRoles_PermissionV2(t *testing.T) {
{
name: "list single id",
args: args{
- ctx: instancePermissionV2.WithAuthorization(CTX, integration.UserTypeOrgOwner),
+ ctx: instancePermissionV2.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
dep: func(request *project.ListProjectRolesRequest, response *project.ListProjectRolesResponse) {
orgID := instancePermissionV2.DefaultOrg.GetId()
projectResp := instancePermissionV2.CreateProject(iamOwnerCtx, t, orgID, gofakeit.AppName(), false, false)
diff --git a/internal/api/grpc/settings/v2/server.go b/internal/api/grpc/settings/v2/server.go
index bfaec17fc2..3f7c2a02ec 100644
--- a/internal/api/grpc/settings/v2/server.go
+++ b/internal/api/grpc/settings/v2/server.go
@@ -19,8 +19,9 @@ import (
var _ settingsconnect.SettingsServiceHandler = (*Server)(nil)
type Server struct {
- command *command.Commands
- query *query.Queries
+ command *command.Commands
+ query *query.Queries
+
assetsAPIDomain func(context.Context) string
}
diff --git a/internal/api/grpc/settings/v2beta/integration_test/query_test.go b/internal/api/grpc/settings/v2beta/integration_test/query_test.go
new file mode 100644
index 0000000000..7886bac539
--- /dev/null
+++ b/internal/api/grpc/settings/v2beta/integration_test/query_test.go
@@ -0,0 +1,281 @@
+//go:build integration
+
+package settings_test
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "github.com/brianvoe/gofakeit/v6"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/zitadel/zitadel/internal/integration"
+ "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
+ "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
+)
+
+func TestServer_ListOrganizationSettings(t *testing.T) {
+ instance := integration.NewInstance(CTX)
+ iamOwnerCtx := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
+
+ type args struct {
+ ctx context.Context
+ dep func(*settings.ListOrganizationSettingsRequest, *settings.ListOrganizationSettingsResponse)
+ req *settings.ListOrganizationSettingsRequest
+ }
+ tests := []struct {
+ name string
+ args args
+ want *settings.ListOrganizationSettingsResponse
+ wantErr bool
+ }{
+ {
+ name: "list by id, unauthenticated",
+ args: args{
+ ctx: CTX,
+ dep: func(request *settings.ListOrganizationSettingsRequest, response *settings.ListOrganizationSettingsResponse) {
+ orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.Company(), gofakeit.Email())
+ instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp.GetOrganizationId(), true)
+
+ request.Filters[0].Filter = &settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter{
+ InOrganizationIdsFilter: &filter.InIDsFilter{
+ Ids: []string{orgResp.GetOrganizationId()},
+ },
+ }
+ },
+ req: &settings.ListOrganizationSettingsRequest{
+ Filters: []*settings.OrganizationSettingsSearchFilter{{}},
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "list by id, no permission",
+ args: args{
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission),
+ dep: func(request *settings.ListOrganizationSettingsRequest, response *settings.ListOrganizationSettingsResponse) {
+ orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.Company(), gofakeit.Email())
+ instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp.GetOrganizationId(), true)
+
+ request.Filters[0].Filter = &settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter{
+ InOrganizationIdsFilter: &filter.InIDsFilter{
+ Ids: []string{orgResp.GetOrganizationId()},
+ },
+ }
+ },
+ req: &settings.ListOrganizationSettingsRequest{
+ Filters: []*settings.OrganizationSettingsSearchFilter{{}},
+ },
+ },
+ want: &settings.ListOrganizationSettingsResponse{
+ Pagination: &filter.PaginationResponse{
+ TotalResult: 1,
+ AppliedLimit: 100,
+ },
+ OrganizationSettings: []*settings.OrganizationSettings{},
+ },
+ },
+ {
+ name: "list by id, missing permission",
+ args: args{
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
+ dep: func(request *settings.ListOrganizationSettingsRequest, response *settings.ListOrganizationSettingsResponse) {
+ orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.Company(), gofakeit.Email())
+ instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp.GetOrganizationId(), true)
+
+ request.Filters[0].Filter = &settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter{
+ InOrganizationIdsFilter: &filter.InIDsFilter{
+ Ids: []string{orgResp.GetOrganizationId()},
+ },
+ }
+ },
+ req: &settings.ListOrganizationSettingsRequest{
+ Filters: []*settings.OrganizationSettingsSearchFilter{{}},
+ },
+ },
+ want: &settings.ListOrganizationSettingsResponse{
+ Pagination: &filter.PaginationResponse{
+ TotalResult: 1,
+ AppliedLimit: 100,
+ },
+ OrganizationSettings: []*settings.OrganizationSettings{},
+ },
+ },
+ {
+ name: "list, not found",
+ args: args{
+ ctx: iamOwnerCtx,
+
+ req: &settings.ListOrganizationSettingsRequest{
+ Filters: []*settings.OrganizationSettingsSearchFilter{{
+ Filter: &settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter{
+ InOrganizationIdsFilter: &filter.InIDsFilter{
+ Ids: []string{"notexisting"},
+ },
+ },
+ }},
+ },
+ },
+ want: &settings.ListOrganizationSettingsResponse{
+ Pagination: &filter.PaginationResponse{
+ TotalResult: 0,
+ AppliedLimit: 100,
+ },
+ OrganizationSettings: []*settings.OrganizationSettings{},
+ },
+ },
+ {
+ name: "list single id",
+ args: args{
+ ctx: iamOwnerCtx,
+ dep: func(request *settings.ListOrganizationSettingsRequest, response *settings.ListOrganizationSettingsResponse) {
+ orgResp := instance.CreateOrganization(iamOwnerCtx, gofakeit.Company(), gofakeit.Email())
+ settingsResp := instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp.GetOrganizationId(), true)
+
+ request.Filters[0].Filter = &settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter{
+ InOrganizationIdsFilter: &filter.InIDsFilter{
+ Ids: []string{orgResp.GetOrganizationId()},
+ },
+ }
+ response.OrganizationSettings[0] = &settings.OrganizationSettings{
+ OrganizationId: orgResp.GetOrganizationId(),
+ CreationDate: settingsResp.GetSetDate(),
+ ChangeDate: settingsResp.GetSetDate(),
+ OrganizationScopedUsernames: true,
+ }
+ },
+ req: &settings.ListOrganizationSettingsRequest{
+ Filters: []*settings.OrganizationSettingsSearchFilter{{}},
+ },
+ },
+ want: &settings.ListOrganizationSettingsResponse{
+ Pagination: &filter.PaginationResponse{
+ TotalResult: 1,
+ AppliedLimit: 100,
+ },
+ OrganizationSettings: []*settings.OrganizationSettings{{}},
+ },
+ },
+ {
+ name: "list multiple id",
+ args: args{
+ ctx: iamOwnerCtx,
+ dep: func(request *settings.ListOrganizationSettingsRequest, response *settings.ListOrganizationSettingsResponse) {
+ orgResp1 := instance.CreateOrganization(iamOwnerCtx, gofakeit.Company(), gofakeit.Email())
+ settingsResp1 := instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp1.GetOrganizationId(), true)
+ orgResp2 := instance.CreateOrganization(iamOwnerCtx, gofakeit.Company(), gofakeit.Email())
+ settingsResp2 := instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp2.GetOrganizationId(), true)
+ orgResp3 := instance.CreateOrganization(iamOwnerCtx, gofakeit.Company(), gofakeit.Email())
+ settingsResp3 := instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp3.GetOrganizationId(), true)
+
+ request.Filters[0].Filter = &settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter{
+ InOrganizationIdsFilter: &filter.InIDsFilter{
+ Ids: []string{orgResp1.GetOrganizationId(), orgResp2.GetOrganizationId(), orgResp3.GetOrganizationId()},
+ },
+ }
+ response.OrganizationSettings[2] = &settings.OrganizationSettings{
+ OrganizationId: orgResp1.GetOrganizationId(),
+ CreationDate: settingsResp1.GetSetDate(),
+ ChangeDate: settingsResp1.GetSetDate(),
+ OrganizationScopedUsernames: true,
+ }
+ response.OrganizationSettings[1] = &settings.OrganizationSettings{
+ OrganizationId: orgResp2.GetOrganizationId(),
+ CreationDate: settingsResp2.GetSetDate(),
+ ChangeDate: settingsResp2.GetSetDate(),
+ OrganizationScopedUsernames: true,
+ }
+ response.OrganizationSettings[0] = &settings.OrganizationSettings{
+ OrganizationId: orgResp3.GetOrganizationId(),
+ CreationDate: settingsResp3.GetSetDate(),
+ ChangeDate: settingsResp3.GetSetDate(),
+ OrganizationScopedUsernames: true,
+ }
+ },
+ req: &settings.ListOrganizationSettingsRequest{
+ Filters: []*settings.OrganizationSettingsSearchFilter{{}},
+ },
+ },
+ want: &settings.ListOrganizationSettingsResponse{
+ Pagination: &filter.PaginationResponse{
+ TotalResult: 3,
+ AppliedLimit: 100,
+ },
+ OrganizationSettings: []*settings.OrganizationSettings{{}, {}, {}},
+ },
+ },
+ {
+ name: "list multiple id, only org scoped usernames",
+ args: args{
+ ctx: iamOwnerCtx,
+ dep: func(request *settings.ListOrganizationSettingsRequest, response *settings.ListOrganizationSettingsResponse) {
+ orgResp1 := instance.CreateOrganization(iamOwnerCtx, gofakeit.Company(), gofakeit.Email())
+ instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp1.GetOrganizationId(), false)
+ orgResp2 := instance.CreateOrganization(iamOwnerCtx, gofakeit.Company(), gofakeit.Email())
+ settingsResp2 := instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp2.GetOrganizationId(), true)
+ orgResp3 := instance.CreateOrganization(iamOwnerCtx, gofakeit.Company(), gofakeit.Email())
+ instance.SetOrganizationSettings(iamOwnerCtx, t, orgResp3.GetOrganizationId(), false)
+
+ request.Filters[0].Filter = &settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter{
+ InOrganizationIdsFilter: &filter.InIDsFilter{
+ Ids: []string{orgResp1.GetOrganizationId(), orgResp2.GetOrganizationId(), orgResp3.GetOrganizationId()},
+ },
+ }
+ request.Filters[1].Filter = &settings.OrganizationSettingsSearchFilter_OrganizationScopedUsernamesFilter{
+ OrganizationScopedUsernamesFilter: &settings.OrganizationScopedUsernamesFilter{
+ OrganizationScopedUsernames: true,
+ },
+ }
+ response.OrganizationSettings[0] = &settings.OrganizationSettings{
+ OrganizationId: orgResp2.GetOrganizationId(),
+ CreationDate: settingsResp2.GetSetDate(),
+ ChangeDate: settingsResp2.GetSetDate(),
+ OrganizationScopedUsernames: true,
+ }
+ },
+ req: &settings.ListOrganizationSettingsRequest{
+ Filters: []*settings.OrganizationSettingsSearchFilter{{}, {}},
+ },
+ },
+ want: &settings.ListOrganizationSettingsResponse{
+ Pagination: &filter.PaginationResponse{
+ TotalResult: 1,
+ AppliedLimit: 100,
+ },
+ OrganizationSettings: []*settings.OrganizationSettings{{}},
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if tt.args.dep != nil {
+ tt.args.dep(tt.args.req, tt.want)
+ }
+
+ retryDuration, tick := integration.WaitForAndTickWithMaxDuration(iamOwnerCtx, time.Minute)
+ require.EventuallyWithT(t, func(ttt *assert.CollectT) {
+ got, listErr := instance.Client.SettingsV2beta.ListOrganizationSettings(tt.args.ctx, tt.args.req)
+ if tt.wantErr {
+ require.Error(ttt, listErr)
+ return
+ }
+ require.NoError(ttt, listErr)
+
+ // always first check length, otherwise its failed anyway
+ if assert.Len(ttt, got.OrganizationSettings, len(tt.want.OrganizationSettings)) {
+ for i := range tt.want.OrganizationSettings {
+ assert.EqualExportedValues(ttt, tt.want.OrganizationSettings[i], got.OrganizationSettings[i])
+ }
+ }
+ assertPaginationResponse(ttt, tt.want.Pagination, got.Pagination)
+ }, retryDuration, tick, "timeout waiting for expected execution result")
+ })
+ }
+}
+
+func assertPaginationResponse(t *assert.CollectT, expected *filter.PaginationResponse, actual *filter.PaginationResponse) {
+ assert.Equal(t, expected.AppliedLimit, actual.AppliedLimit)
+ assert.Equal(t, expected.TotalResult, actual.TotalResult)
+}
diff --git a/internal/api/grpc/settings/v2beta/integration_test/settings_test.go b/internal/api/grpc/settings/v2beta/integration_test/settings_test.go
index d5c1914ba9..4e028fb6d5 100644
--- a/internal/api/grpc/settings/v2beta/integration_test/settings_test.go
+++ b/internal/api/grpc/settings/v2beta/integration_test/settings_test.go
@@ -7,6 +7,8 @@ import (
"testing"
"time"
+ "github.com/brianvoe/gofakeit/v6"
+ "github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -178,3 +180,279 @@ func TestServer_SetSecuritySettings(t *testing.T) {
})
}
}
+
+func TestServer_SetOrganizationSettings(t *testing.T) {
+ instance := integration.NewInstance(CTX)
+ iamOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
+
+ type args struct {
+ ctx context.Context
+ req *settings.SetOrganizationSettingsRequest
+ }
+ type want struct {
+ set bool
+ setDate bool
+ }
+ tests := []struct {
+ name string
+ prepare func(req *settings.SetOrganizationSettingsRequest)
+ args args
+ want want
+ wantErr bool
+ }{
+ {
+ name: "permission error",
+ args: args{
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
+ req: &settings.SetOrganizationSettingsRequest{
+ OrganizationId: Instance.DefaultOrg.GetId(),
+ OrganizationScopedUsernames: gu.Ptr(true),
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "org not provided",
+ args: args{
+ ctx: iamOwnerCTX,
+ req: &settings.SetOrganizationSettingsRequest{
+ OrganizationId: "",
+ OrganizationScopedUsernames: gu.Ptr(true),
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "org not existing",
+ args: args{
+ ctx: iamOwnerCTX,
+ req: &settings.SetOrganizationSettingsRequest{
+ OrganizationId: "notexisting",
+ OrganizationScopedUsernames: gu.Ptr(true),
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "success no changes",
+ prepare: func(req *settings.SetOrganizationSettingsRequest) {
+ orgResp := instance.CreateOrganization(iamOwnerCTX, gofakeit.Company(), gofakeit.Email())
+ req.OrganizationId = orgResp.GetOrganizationId()
+ },
+ args: args{
+ ctx: iamOwnerCTX,
+ req: &settings.SetOrganizationSettingsRequest{},
+ },
+ want: want{
+ set: false,
+ setDate: true,
+ },
+ },
+ {
+ name: "success user uniqueness",
+ prepare: func(req *settings.SetOrganizationSettingsRequest) {
+ orgResp := instance.CreateOrganization(iamOwnerCTX, gofakeit.Company(), gofakeit.Email())
+ req.OrganizationId = orgResp.GetOrganizationId()
+ },
+ args: args{
+ ctx: iamOwnerCTX,
+ req: &settings.SetOrganizationSettingsRequest{
+ OrganizationScopedUsernames: gu.Ptr(true),
+ },
+ },
+ want: want{
+ set: true,
+ setDate: true,
+ },
+ },
+ {
+ name: "success no change",
+ prepare: func(req *settings.SetOrganizationSettingsRequest) {
+ orgResp := instance.CreateOrganization(iamOwnerCTX, gofakeit.Company(), gofakeit.Email())
+ req.OrganizationId = orgResp.GetOrganizationId()
+ },
+ args: args{
+ ctx: iamOwnerCTX,
+ req: &settings.SetOrganizationSettingsRequest{
+ OrganizationScopedUsernames: gu.Ptr(false),
+ },
+ },
+ want: want{
+ set: false,
+ setDate: true,
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ creationDate := time.Now().UTC()
+ if tt.prepare != nil {
+ tt.prepare(tt.args.req)
+ }
+
+ got, err := instance.Client.SettingsV2beta.SetOrganizationSettings(tt.args.ctx, tt.args.req)
+ if tt.wantErr {
+ assert.Error(t, err)
+ return
+ }
+ setDate := time.Time{}
+ if tt.want.set {
+ setDate = time.Now().UTC()
+ }
+ assert.NoError(t, err)
+ assertOrganizationSettingsResponse(t, creationDate, setDate, tt.want.setDate, got)
+ })
+ }
+}
+
+func assertOrganizationSettingsResponse(t *testing.T, creationDate, setDate time.Time, expectedSetDate bool, actualResp *settings.SetOrganizationSettingsResponse) {
+ if expectedSetDate {
+ if !setDate.IsZero() {
+ assert.WithinRange(t, actualResp.GetSetDate().AsTime(), creationDate, setDate)
+ } else {
+ assert.WithinRange(t, actualResp.GetSetDate().AsTime(), creationDate, time.Now().UTC())
+ }
+ } else {
+ assert.Nil(t, actualResp.SetDate)
+ }
+}
+
+func TestServer_DeleteOrganizationSettings(t *testing.T) {
+ instance := integration.NewInstance(CTX)
+ iamOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
+
+ type args struct {
+ ctx context.Context
+ req *settings.DeleteOrganizationSettingsRequest
+ }
+ type want struct {
+ deletion bool
+ deletionDate bool
+ }
+ tests := []struct {
+ name string
+ prepare func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest)
+ args args
+ want want
+ wantErr bool
+ }{
+ {
+ name: "permission error",
+ prepare: func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest) {
+ orgResp := instance.CreateOrganization(iamOwnerCTX, gofakeit.Company(), gofakeit.Email())
+ req.OrganizationId = orgResp.GetOrganizationId()
+ instance.SetOrganizationSettings(iamOwnerCTX, t, orgResp.GetOrganizationId(), true)
+ },
+ args: args{
+ ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
+ req: &settings.DeleteOrganizationSettingsRequest{
+ OrganizationId: Instance.DefaultOrg.GetId(),
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "org not provided",
+ args: args{
+ ctx: iamOwnerCTX,
+ req: &settings.DeleteOrganizationSettingsRequest{
+ OrganizationId: "",
+ },
+ },
+ wantErr: true,
+ },
+ {
+ name: "org not existing",
+ args: args{
+ ctx: iamOwnerCTX,
+ req: &settings.DeleteOrganizationSettingsRequest{
+ OrganizationId: "notexisting",
+ },
+ },
+ want: want{
+ deletion: false,
+ deletionDate: false,
+ },
+ },
+ {
+ name: "success user uniqueness",
+ prepare: func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest) {
+ orgResp := instance.CreateOrganization(iamOwnerCTX, gofakeit.Company(), gofakeit.Email())
+ req.OrganizationId = orgResp.GetOrganizationId()
+ instance.SetOrganizationSettings(iamOwnerCTX, t, orgResp.GetOrganizationId(), true)
+ },
+ args: args{
+ ctx: iamOwnerCTX,
+ req: &settings.DeleteOrganizationSettingsRequest{},
+ },
+ want: want{
+ deletion: true,
+ deletionDate: true,
+ },
+ },
+ {
+ name: "success no existing",
+ prepare: func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest) {
+ orgResp := instance.CreateOrganization(iamOwnerCTX, gofakeit.Company(), gofakeit.Email())
+ req.OrganizationId = orgResp.GetOrganizationId()
+ },
+ args: args{
+ ctx: iamOwnerCTX,
+ req: &settings.DeleteOrganizationSettingsRequest{},
+ },
+ want: want{
+ deletion: false,
+ deletionDate: true,
+ },
+ },
+ {
+ name: "success already deleted",
+ prepare: func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest) {
+ orgResp := instance.CreateOrganization(iamOwnerCTX, gofakeit.Company(), gofakeit.Email())
+ req.OrganizationId = orgResp.GetOrganizationId()
+ instance.SetOrganizationSettings(iamOwnerCTX, t, orgResp.GetOrganizationId(), true)
+ instance.DeleteOrganizationSettings(iamOwnerCTX, t, orgResp.GetOrganizationId())
+ },
+ args: args{
+ ctx: iamOwnerCTX,
+ req: &settings.DeleteOrganizationSettingsRequest{},
+ },
+ want: want{
+ deletion: false,
+ deletionDate: true,
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ creationDate := time.Now().UTC()
+ if tt.prepare != nil {
+ tt.prepare(t, tt.args.req)
+ }
+
+ got, err := instance.Client.SettingsV2beta.DeleteOrganizationSettings(tt.args.ctx, tt.args.req)
+ if tt.wantErr {
+ assert.Error(t, err)
+ return
+ }
+ deletionDate := time.Time{}
+ if tt.want.deletion {
+ deletionDate = time.Now().UTC()
+ }
+ assert.NoError(t, err)
+ assertDeleteOrganizationSettingsResponse(t, creationDate, deletionDate, tt.want.deletionDate, got)
+ })
+ }
+}
+
+func assertDeleteOrganizationSettingsResponse(t *testing.T, creationDate, deletionDate time.Time, expectedDeletionDate bool, actualResp *settings.DeleteOrganizationSettingsResponse) {
+ if expectedDeletionDate {
+ if !deletionDate.IsZero() {
+ assert.WithinRange(t, actualResp.GetDeletionDate().AsTime(), creationDate, deletionDate)
+ } else {
+ assert.WithinRange(t, actualResp.GetDeletionDate().AsTime(), creationDate, time.Now().UTC())
+ }
+ } else {
+ assert.Nil(t, actualResp.DeletionDate)
+ }
+}
diff --git a/internal/api/grpc/settings/v2beta/query.go b/internal/api/grpc/settings/v2beta/query.go
new file mode 100644
index 0000000000..ac07031ed9
--- /dev/null
+++ b/internal/api/grpc/settings/v2beta/query.go
@@ -0,0 +1,114 @@
+package settings
+
+import (
+ "context"
+
+ "connectrpc.com/connect"
+ "google.golang.org/protobuf/types/known/timestamppb"
+
+ "github.com/zitadel/zitadel/internal/api/grpc/filter/v2beta"
+ "github.com/zitadel/zitadel/internal/query"
+ "github.com/zitadel/zitadel/internal/zerrors"
+ filter_pb "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
+ "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
+)
+
+func (s *Server) ListOrganizationSettings(ctx context.Context, req *connect.Request[settings.ListOrganizationSettingsRequest]) (*connect.Response[settings.ListOrganizationSettingsResponse], error) {
+ queries, err := s.listOrganizationSettingsRequestToModel(req.Msg)
+ if err != nil {
+ return nil, err
+ }
+ resp, err := s.query.SearchOrganizationSettings(ctx, queries, s.checkPermission)
+ if err != nil {
+ return nil, err
+ }
+ return connect.NewResponse(&settings.ListOrganizationSettingsResponse{
+ OrganizationSettings: organizationSettingsListToPb(resp.OrganizationSettingsList),
+ Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
+ }), nil
+}
+
+func (s *Server) listOrganizationSettingsRequestToModel(req *settings.ListOrganizationSettingsRequest) (*query.OrganizationSettingsSearchQueries, error) {
+ offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
+ if err != nil {
+ return nil, err
+ }
+ queries, err := organizationSettingsFiltersToQuery(req.Filters)
+ if err != nil {
+ return nil, err
+ }
+ return &query.OrganizationSettingsSearchQueries{
+ SearchRequest: query.SearchRequest{
+ Offset: offset,
+ Limit: limit,
+ Asc: asc,
+ SortingColumn: organizationSettingsFieldNameToSortingColumn(req.SortingColumn),
+ },
+ Queries: queries,
+ }, nil
+}
+
+func organizationSettingsFieldNameToSortingColumn(field *settings.OrganizationSettingsFieldName) query.Column {
+ if field == nil {
+ return query.OrganizationSettingsColumnCreationDate
+ }
+ switch *field {
+ case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_CREATION_DATE:
+ return query.OrganizationSettingsColumnCreationDate
+ case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_ORGANIZATION_ID:
+ return query.OrganizationSettingsColumnID
+ case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_CHANGE_DATE:
+ return query.OrganizationSettingsColumnChangeDate
+ case settings.OrganizationSettingsFieldName_ORGANIZATION_SETTINGS_FIELD_NAME_UNSPECIFIED:
+ return query.OrganizationSettingsColumnCreationDate
+ default:
+ return query.OrganizationSettingsColumnCreationDate
+ }
+}
+
+func organizationSettingsFiltersToQuery(queries []*settings.OrganizationSettingsSearchFilter) (_ []query.SearchQuery, err error) {
+ q := make([]query.SearchQuery, len(queries))
+ for i, qry := range queries {
+ q[i], err = organizationSettingsToModel(qry)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return q, nil
+}
+
+func organizationSettingsToModel(filter *settings.OrganizationSettingsSearchFilter) (query.SearchQuery, error) {
+ switch q := filter.Filter.(type) {
+ case *settings.OrganizationSettingsSearchFilter_InOrganizationIdsFilter:
+ return organizationInIDsFilterToQuery(q.InOrganizationIdsFilter)
+ case *settings.OrganizationSettingsSearchFilter_OrganizationScopedUsernamesFilter:
+ return organizationScopedUsernamesFilterToQuery(q.OrganizationScopedUsernamesFilter)
+ default:
+ return nil, zerrors.ThrowInvalidArgument(nil, "SETTINGS-uvTDqZHlvS", "List.Query.Invalid")
+ }
+}
+
+func organizationInIDsFilterToQuery(q *filter_pb.InIDsFilter) (query.SearchQuery, error) {
+ return query.NewOrganizationSettingsOrganizationIDSearchQuery(q.Ids)
+}
+
+func organizationScopedUsernamesFilterToQuery(q *settings.OrganizationScopedUsernamesFilter) (query.SearchQuery, error) {
+ return query.NewOrganizationSettingsOrganizationScopedUsernamesSearchQuery(q.OrganizationScopedUsernames)
+}
+
+func organizationSettingsListToPb(settingsList []*query.OrganizationSettings) []*settings.OrganizationSettings {
+ o := make([]*settings.OrganizationSettings, len(settingsList))
+ for i, organizationSettings := range settingsList {
+ o[i] = organizationSettingsToPb(organizationSettings)
+ }
+ return o
+}
+
+func organizationSettingsToPb(organizationSettings *query.OrganizationSettings) *settings.OrganizationSettings {
+ return &settings.OrganizationSettings{
+ OrganizationId: organizationSettings.ID,
+ CreationDate: timestamppb.New(organizationSettings.CreationDate),
+ ChangeDate: timestamppb.New(organizationSettings.ChangeDate),
+ OrganizationScopedUsernames: organizationSettings.OrganizationScopedUsernames,
+ }
+}
diff --git a/internal/api/grpc/settings/v2beta/server.go b/internal/api/grpc/settings/v2beta/server.go
index a8200a7216..3eea310006 100644
--- a/internal/api/grpc/settings/v2beta/server.go
+++ b/internal/api/grpc/settings/v2beta/server.go
@@ -11,6 +11,8 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/server"
"github.com/zitadel/zitadel/internal/command"
+ "github.com/zitadel/zitadel/internal/config/systemdefaults"
+ "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/settings/v2beta/settingsconnect"
@@ -19,20 +21,27 @@ import (
var _ settingsconnect.SettingsServiceHandler = (*Server)(nil)
type Server struct {
- command *command.Commands
- query *query.Queries
+ systemDefaults systemdefaults.SystemDefaults
+ command *command.Commands
+ query *query.Queries
+
+ checkPermission domain.PermissionCheck
assetsAPIDomain func(context.Context) string
}
type Config struct{}
func CreateServer(
+ systemDefaults systemdefaults.SystemDefaults,
command *command.Commands,
query *query.Queries,
+ checkPermission domain.PermissionCheck,
) *Server {
return &Server{
+ systemDefaults: systemDefaults,
command: command,
query: query,
+ checkPermission: checkPermission,
assetsAPIDomain: assets.AssetAPI(),
}
}
diff --git a/internal/api/grpc/settings/v2beta/settings.go b/internal/api/grpc/settings/v2beta/settings.go
index 53d2c37c32..7388c0449a 100644
--- a/internal/api/grpc/settings/v2beta/settings.go
+++ b/internal/api/grpc/settings/v2beta/settings.go
@@ -167,3 +167,31 @@ func (s *Server) SetSecuritySettings(ctx context.Context, req *connect.Request[s
Details: object.DomainToDetailsPb(details),
}), nil
}
+
+func (s *Server) SetOrganizationSettings(ctx context.Context, req *connect.Request[settings.SetOrganizationSettingsRequest]) (*connect.Response[settings.SetOrganizationSettingsResponse], error) {
+ details, err := s.command.SetOrganizationSettings(ctx, organizationSettingsToCommand(req.Msg))
+ if err != nil {
+ return nil, err
+ }
+ var setDate *timestamppb.Timestamp
+ if !details.EventDate.IsZero() {
+ setDate = timestamppb.New(details.EventDate)
+ }
+ return connect.NewResponse(&settings.SetOrganizationSettingsResponse{
+ SetDate: setDate,
+ }), nil
+}
+
+func (s *Server) DeleteOrganizationSettings(ctx context.Context, req *connect.Request[settings.DeleteOrganizationSettingsRequest]) (*connect.Response[settings.DeleteOrganizationSettingsResponse], error) {
+ details, err := s.command.DeleteOrganizationSettings(ctx, req.Msg.GetOrganizationId())
+ if err != nil {
+ return nil, err
+ }
+ var deletionDate *timestamppb.Timestamp
+ if !details.EventDate.IsZero() {
+ deletionDate = timestamppb.New(details.EventDate)
+ }
+ return connect.NewResponse(&settings.DeleteOrganizationSettingsResponse{
+ DeletionDate: deletionDate,
+ }), nil
+}
diff --git a/internal/api/grpc/settings/v2beta/settings_converter.go b/internal/api/grpc/settings/v2beta/settings_converter.go
index 2b20e738e1..ad4ad2ebab 100644
--- a/internal/api/grpc/settings/v2beta/settings_converter.go
+++ b/internal/api/grpc/settings/v2beta/settings_converter.go
@@ -243,3 +243,10 @@ func securitySettingsToCommand(req *settings.SetSecuritySettingsRequest) *comman
EnableImpersonation: req.GetEnableImpersonation(),
}
}
+
+func organizationSettingsToCommand(req *settings.SetOrganizationSettingsRequest) *command.SetOrganizationSettings {
+ return &command.SetOrganizationSettings{
+ OrganizationID: req.OrganizationId,
+ OrganizationScopedUsernames: req.OrganizationScopedUsernames,
+ }
+}
diff --git a/internal/api/oidc/auth_request.go b/internal/api/oidc/auth_request.go
index b29e157fc2..152176a59c 100644
--- a/internal/api/oidc/auth_request.go
+++ b/internal/api/oidc/auth_request.go
@@ -13,6 +13,7 @@ import (
"github.com/zitadel/logging"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
+ "golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/api/authz"
http_utils "github.com/zitadel/zitadel/internal/api/http"
@@ -30,6 +31,8 @@ import (
const (
LoginClientHeader = "x-zitadel-login-client"
LoginPostLogoutRedirectParam = "post_logout_redirect"
+ LoginLogoutHintParam = "logout_hint"
+ LoginUILocalesParam = "ui_locales"
LoginPath = "/login"
LogoutPath = "/logout"
LogoutDonePath = "/logout/done"
@@ -283,14 +286,19 @@ func (o *OPStorage) TerminateSessionFromRequest(ctx context.Context, endSessionR
// we'll redirect to the UI (V2) and let it decide which session to terminate
//
// If there's no id_token_hint and for v1 logins, we handle them separately
- if endSessionRequest.IDTokenHintClaims == nil &&
- (authz.GetFeatures(ctx).LoginV2.Required || headers.Get(LoginClientHeader) != "") {
+ if endSessionRequest.IDTokenHintClaims == nil && (authz.GetFeatures(ctx).LoginV2.Required || headers.Get(LoginClientHeader) != "") {
redirectURI := v2PostLogoutRedirectURI(endSessionRequest.RedirectURI)
- // if no base uri is set, fallback to the default configured in the runtime config
- if authz.GetFeatures(ctx).LoginV2.BaseURI == nil || authz.GetFeatures(ctx).LoginV2.BaseURI.String() == "" {
- return o.defaultLogoutURLV2 + redirectURI, nil
+ logoutURI := authz.GetFeatures(ctx).LoginV2.BaseURI
+ // if no logout uri is set, fallback to the default configured in the runtime config
+ if logoutURI == nil || logoutURI.String() == "" {
+ logoutURI, err = url.Parse(o.defaultLogoutURLV2)
+ if err != nil {
+ return "", err
+ }
+ } else {
+ logoutURI = logoutURI.JoinPath(LogoutPath)
}
- return buildLoginV2LogoutURL(authz.GetFeatures(ctx).LoginV2.BaseURI, redirectURI), nil
+ return buildLoginV2LogoutURL(logoutURI, redirectURI, endSessionRequest.LogoutHint, endSessionRequest.UILocales), nil
}
// V1:
@@ -367,12 +375,25 @@ func (o *OPStorage) federatedLogout(ctx context.Context, sessionID string, postL
return login.ExternalLogoutPath(sessionID)
}
-func buildLoginV2LogoutURL(baseURI *url.URL, redirectURI string) string {
- baseURI.JoinPath(LogoutPath)
- q := baseURI.Query()
+func buildLoginV2LogoutURL(logoutURI *url.URL, redirectURI, logoutHint string, uiLocales []language.Tag) string {
+ if strings.HasSuffix(logoutURI.Path, "/") && len(logoutURI.Path) > 1 {
+ logoutURI.Path = strings.TrimSuffix(logoutURI.Path, "/")
+ }
+
+ q := logoutURI.Query()
q.Set(LoginPostLogoutRedirectParam, redirectURI)
- baseURI.RawQuery = q.Encode()
- return baseURI.String()
+ if logoutHint != "" {
+ q.Set(LoginLogoutHintParam, logoutHint)
+ }
+ if len(uiLocales) > 0 {
+ locales := make([]string, len(uiLocales))
+ for i, locale := range uiLocales {
+ locales[i] = locale.String()
+ }
+ q.Set(LoginUILocalesParam, strings.Join(locales, " "))
+ }
+ logoutURI.RawQuery = q.Encode()
+ return logoutURI.String()
}
// v2PostLogoutRedirectURI will take care that the post_logout_redirect_uri is correctly set for v2 logins.
diff --git a/internal/api/oidc/auth_request_test.go b/internal/api/oidc/auth_request_test.go
new file mode 100644
index 0000000000..0210ead49e
--- /dev/null
+++ b/internal/api/oidc/auth_request_test.go
@@ -0,0 +1,98 @@
+package oidc
+
+import (
+ "net/url"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "golang.org/x/text/language"
+)
+
+func TestBuildLoginV2LogoutURL(t *testing.T) {
+ t.Parallel()
+
+ tt := []struct {
+ testName string
+ logoutURIStr string
+ redirectURI string
+ logoutHint string
+ uiLocales []language.Tag
+ expectedParams map[string]string
+ }{
+ {
+ testName: "basic with only redirectURI",
+ logoutURIStr: "https://example.com/logout",
+ redirectURI: "https://client/cb",
+ expectedParams: map[string]string{
+ "post_logout_redirect": "https://client/cb",
+ },
+ },
+ {
+ testName: "with logout hint",
+ logoutURIStr: "https://example.com/logout",
+ redirectURI: "https://client/cb",
+ logoutHint: "user@example.com",
+ expectedParams: map[string]string{
+ "post_logout_redirect": "https://client/cb",
+ "logout_hint": "user@example.com",
+ },
+ },
+ {
+ testName: "with ui_locales",
+ logoutURIStr: "https://example.com/logout",
+ redirectURI: "https://client/cb",
+ uiLocales: []language.Tag{language.English, language.Italian},
+ expectedParams: map[string]string{
+ "post_logout_redirect": "https://client/cb",
+ "ui_locales": "en it",
+ },
+ },
+ {
+ testName: "with all params",
+ logoutURIStr: "https://example.com/logout",
+ redirectURI: "https://client/cb",
+ logoutHint: "logoutme",
+ uiLocales: []language.Tag{language.Make("de-CH"), language.Make("fr")},
+ expectedParams: map[string]string{
+ "post_logout_redirect": "https://client/cb",
+ "logout_hint": "logoutme",
+ "ui_locales": "de-CH fr",
+ },
+ },
+ {
+ testName: "base with trailing slash",
+ logoutURIStr: "https://example.com/logout/",
+ redirectURI: "https://client/cb",
+ expectedParams: map[string]string{
+ "post_logout_redirect": "https://client/cb",
+ },
+ },
+ }
+
+ for _, tc := range tt {
+ t.Run(tc.testName, func(t *testing.T) {
+ // t.Parallel()
+
+ // Given
+ logoutURI, err := url.Parse(tc.logoutURIStr)
+ require.NoError(t, err)
+
+ // When
+ got := buildLoginV2LogoutURL(logoutURI, tc.redirectURI, tc.logoutHint, tc.uiLocales)
+
+ // Then
+ gotURL, err := url.Parse(got)
+ require.NoError(t, err)
+ require.NotContains(t, gotURL.String(), "/logout/")
+
+ q := gotURL.Query()
+ // Ensure no unexpected params
+ require.Len(t, q, len(tc.expectedParams))
+
+ for k, v := range tc.expectedParams {
+ assert.Equal(t, v, q.Get(k))
+ }
+ })
+ }
+}
diff --git a/internal/api/oidc/integration_test/auth_request_test.go b/internal/api/oidc/integration_test/auth_request_test.go
index ad78184a04..77e389f7be 100644
--- a/internal/api/oidc/integration_test/auth_request_test.go
+++ b/internal/api/oidc/integration_test/auth_request_test.go
@@ -498,7 +498,7 @@ func TestOPStorage_TerminateSession(t *testing.T) {
_, err = rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.Subject, provider)
require.NoError(t, err)
- postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state")
+ postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state", "", nil)
require.NoError(t, err)
assert.Equal(t, logoutRedirectURI+"?state=state", postLogoutRedirect.String())
@@ -535,7 +535,7 @@ func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) {
_, err = rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.Subject, provider)
require.NoError(t, err)
- postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state")
+ postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state", "", nil)
require.NoError(t, err)
assert.Equal(t, logoutRedirectURI+"?state=state", postLogoutRedirect.String())
@@ -551,6 +551,17 @@ func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) {
require.NoError(t, err)
}
+func buildLogoutURL(origin, logoutURLV2 string, redirectURI string, extraParams map[string]string) string {
+ u, _ := url.Parse(origin + logoutURLV2 + redirectURI)
+ q := u.Query()
+ for k, v := range extraParams {
+ q.Set(k, v)
+ }
+ u.RawQuery = q.Encode()
+ // Append the redirect URI as a URL-escaped string
+ return u.String()
+}
+
func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) {
tests := []struct {
name string
@@ -565,7 +576,7 @@ func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) {
return clientID
}(),
authRequestID: createAuthRequest,
- logoutURL: http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure) + Instance.Config.LogoutURLV2 + logoutRedirectURI + "?state=state",
+ logoutURL: buildLogoutURL(http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure), Instance.Config.LogoutURLV2, logoutRedirectURI+"?state=state", map[string]string{"logout_hint": "hint", "ui_locales": "it-IT en-US"}),
},
{
name: "login v2 config",
@@ -574,7 +585,7 @@ func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) {
return clientID
}(),
authRequestID: createAuthRequestNoLoginClientHeader,
- logoutURL: http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure) + Instance.Config.LogoutURLV2 + logoutRedirectURI + "?state=state",
+ logoutURL: buildLogoutURL(http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure), Instance.Config.LogoutURLV2, logoutRedirectURI+"?state=state", map[string]string{"logout_hint": "hint", "ui_locales": "it-IT en-US"}),
},
}
for _, tt := range tests {
@@ -601,7 +612,7 @@ func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) {
assertTokens(t, tokens, false)
assertIDTokenClaims(t, tokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID)
- postLogoutRedirect, err := rp.EndSession(CTX, provider, "", logoutRedirectURI, "state")
+ postLogoutRedirect, err := rp.EndSession(CTX, provider, "", logoutRedirectURI, "state", "hint", oidc.ParseLocales([]string{"it-IT", "en-US"}))
require.NoError(t, err)
assert.Equal(t, tt.logoutURL, postLogoutRedirect.String())
diff --git a/internal/api/oidc/integration_test/oidc_test.go b/internal/api/oidc/integration_test/oidc_test.go
index 2b43154743..d3d80b0557 100644
--- a/internal/api/oidc/integration_test/oidc_test.go
+++ b/internal/api/oidc/integration_test/oidc_test.go
@@ -311,7 +311,7 @@ func Test_ZITADEL_API_terminated_session(t *testing.T) {
require.Equal(t, User.GetUserId(), myUserResp.GetUser().GetId())
// end session
- postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state")
+ postLogoutRedirect, err := rp.EndSession(CTX, provider, tokens.IDToken, logoutRedirectURI, "state", "", nil)
require.NoError(t, err)
assert.Equal(t, logoutRedirectURI+"?state=state", postLogoutRedirect.String())
diff --git a/internal/api/oidc/introspect.go b/internal/api/oidc/introspect.go
index e5479a4683..6ce5d72e24 100644
--- a/internal/api/oidc/introspect.go
+++ b/internal/api/oidc/introspect.go
@@ -100,6 +100,7 @@ func (s *Server) Introspect(ctx context.Context, r *op.Request[op.IntrospectionR
token.userID,
token.scope,
client.projectID,
+ client.clientID,
client.projectRoleAssertion,
true,
true,
diff --git a/internal/api/oidc/token.go b/internal/api/oidc/token.go
index 2efc0fb583..d7a258259a 100644
--- a/internal/api/oidc/token.go
+++ b/internal/api/oidc/token.go
@@ -31,7 +31,7 @@ for example the v2 code exchange and refresh token.
*/
func (s *Server) accessTokenResponseFromSession(ctx context.Context, client op.Client, session *command.OIDCSession, state, projectID string, projectRoleAssertion, accessTokenRoleAssertion, idTokenRoleAssertion, userInfoAssertion bool) (_ *oidc.AccessTokenResponse, err error) {
- getUserInfo := s.getUserInfo(session.UserID, projectID, projectRoleAssertion, userInfoAssertion, session.Scope)
+ getUserInfo := s.getUserInfo(session.UserID, projectID, client.GetID(), projectRoleAssertion, userInfoAssertion, session.Scope)
getSigner := s.getSignerOnce()
resp := &oidc.AccessTokenResponse{
@@ -113,8 +113,8 @@ type userInfoFunc func(ctx context.Context, roleAssertion bool, triggerType doma
// getUserInfo returns a function which retrieves userinfo from the database once.
// However, each time, role claims are asserted and also action flows will trigger.
-func (s *Server) getUserInfo(userID, projectID string, projectRoleAssertion, userInfoAssertion bool, scope []string) userInfoFunc {
- userInfo := s.userInfo(userID, scope, projectID, projectRoleAssertion, userInfoAssertion, false)
+func (s *Server) getUserInfo(userID, projectID, clientID string, projectRoleAssertion, userInfoAssertion bool, scope []string) userInfoFunc {
+ userInfo := s.userInfo(userID, scope, projectID, clientID, projectRoleAssertion, userInfoAssertion, false)
return func(ctx context.Context, roleAssertion bool, triggerType domain.TriggerType) (*oidc.UserInfo, error) {
return userInfo(ctx, roleAssertion, triggerType)
}
diff --git a/internal/api/oidc/token_exchange.go b/internal/api/oidc/token_exchange.go
index 030066ea1c..8cb087e760 100644
--- a/internal/api/oidc/token_exchange.go
+++ b/internal/api/oidc/token_exchange.go
@@ -218,7 +218,7 @@ func validateTokenExchangeAudience(requestedAudience, subjectAudience, actorAudi
// Both tokens may point to the same object (subjectToken) in case of a regular Token Exchange.
// When the subject and actor Tokens point to different objects, the new tokens will be for impersonation / delegation.
func (s *Server) createExchangeTokens(ctx context.Context, tokenType oidc.TokenType, client *Client, subjectToken, actorToken *exchangeToken, audience, scopes []string) (_ *oidc.TokenExchangeResponse, err error) {
- getUserInfo := s.getUserInfo(subjectToken.userID, client.client.ProjectID, client.client.ProjectRoleAssertion, client.IDTokenUserinfoClaimsAssertion(), scopes)
+ getUserInfo := s.getUserInfo(subjectToken.userID, client.client.ProjectID, client.GetID(), client.client.ProjectRoleAssertion, client.IDTokenUserinfoClaimsAssertion(), scopes)
getSigner := s.getSignerOnce()
resp := &oidc.TokenExchangeResponse{
diff --git a/internal/api/oidc/userinfo.go b/internal/api/oidc/userinfo.go
index 170ff49c94..833c7a6ee4 100644
--- a/internal/api/oidc/userinfo.go
+++ b/internal/api/oidc/userinfo.go
@@ -54,6 +54,7 @@ func (s *Server) UserInfo(ctx context.Context, r *op.Request[oidc.UserInfoReques
token.userID,
token.scope,
projectID,
+ token.clientID,
assertion,
true,
false,
@@ -86,6 +87,7 @@ func (s *Server) userInfo(
userID string,
scope []string,
projectID string,
+ clientID string,
projectRoleAssertion, userInfoAssertion, currentProjectOnly bool,
) func(ctx context.Context, roleAssertion bool, triggerType domain.TriggerType) (_ *oidc.UserInfo, err error) {
var (
@@ -120,7 +122,7 @@ func (s *Server) userInfo(
Claims: maps.Clone(rawUserInfo.Claims),
}
assertRoles(projectID, qu, roleAudience, requestedRoles, roleAssertion, userInfo)
- return userInfo, s.userinfoFlows(ctx, qu, userInfo, triggerType)
+ return userInfo, s.userinfoFlows(ctx, qu, userInfo, triggerType, clientID)
}
}
@@ -285,7 +287,8 @@ func setUserInfoRoleClaims(userInfo *oidc.UserInfo, roles *projectsRoles) {
}
}
-func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, userInfo *oidc.UserInfo, triggerType domain.TriggerType) (err error) {
+//nolint:gocognit
+func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, userInfo *oidc.UserInfo, triggerType domain.TriggerType, clientID string) (err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -319,6 +322,13 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user
}
}),
),
+ actions.SetFields("application",
+ actions.SetFields("getClientId", func(c *actions.FieldConfig) interface{} {
+ return func(goja.FunctionCall) goja.Value {
+ return c.Runtime.ToValue(clientID)
+ }
+ }),
+ ),
),
)
@@ -427,6 +437,7 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user
User: qu.User,
UserMetadata: qu.Metadata,
Org: qu.Org,
+ Application: &ContextInfoApplication{ClientID: clientID},
UserGrants: qu.UserGrants,
}
@@ -463,13 +474,17 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user
}
type ContextInfo struct {
- Function string `json:"function,omitempty"`
- UserInfo *oidc.UserInfo `json:"userinfo,omitempty"`
- User *query.User `json:"user,omitempty"`
- UserMetadata []query.UserMetadata `json:"user_metadata,omitempty"`
- Org *query.UserInfoOrg `json:"org,omitempty"`
- UserGrants []query.UserGrant `json:"user_grants,omitempty"`
- Response *ContextInfoResponse `json:"response,omitempty"`
+ Function string `json:"function,omitempty"`
+ UserInfo *oidc.UserInfo `json:"userinfo,omitempty"`
+ User *query.User `json:"user,omitempty"`
+ UserMetadata []query.UserMetadata `json:"user_metadata,omitempty"`
+ Org *query.UserInfoOrg `json:"org,omitempty"`
+ UserGrants []query.UserGrant `json:"user_grants,omitempty"`
+ Application *ContextInfoApplication `json:"application,omitempty"`
+ Response *ContextInfoResponse `json:"response,omitempty"`
+}
+type ContextInfoApplication struct {
+ ClientID string `json:"client_id,omitempty"`
}
type ContextInfoResponse struct {
diff --git a/internal/command/instance_policy_domain.go b/internal/command/instance_policy_domain.go
index 969bc219fe..449475f8d6 100644
--- a/internal/command/instance_policy_domain.go
+++ b/internal/command/instance_policy_domain.go
@@ -130,11 +130,20 @@ func prepareChangeDefaultDomainPolicy(
// loop over all found organisations to get their usernames
// and to compute the username changed events
for _, orgID := range orgsWriteModel.OrgIDs {
+ organizationScopedUsernames, err := checkOrganizationScopedUsernames(ctx, filter, a.ID, nil)
+ if err != nil {
+ return nil, err
+ }
+
usersWriteModel, err := domainPolicyUsernames(ctx, filter, orgID)
if err != nil {
return nil, err
}
- cmds = append(cmds, usersWriteModel.NewUsernameChangedEvents(ctx, userLoginMustBeDomain)...)
+ cmds = append(cmds, usersWriteModel.NewUsernameChangedEvents(ctx,
+ userLoginMustBeDomain,
+ organizationScopedUsernames,
+ writeModel.UserLoginMustBeDomain,
+ )...)
}
return cmds, nil
}, nil
diff --git a/internal/command/instance_policy_domain_test.go b/internal/command/instance_policy_domain_test.go
index 745d4a7efe..17c975b800 100644
--- a/internal/command/instance_policy_domain_test.go
+++ b/internal/command/instance_policy_domain_test.go
@@ -19,7 +19,7 @@ import (
func TestCommandSide_AddDefaultDomainPolicy(t *testing.T) {
type fields struct {
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@@ -40,8 +40,7 @@ func TestCommandSide_AddDefaultDomainPolicy(t *testing.T) {
{
name: "domain policy already existing, already exists error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewDomainPolicyAddedEvent(context.Background(),
@@ -67,8 +66,7 @@ func TestCommandSide_AddDefaultDomainPolicy(t *testing.T) {
{
name: "add policy,ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(),
expectPush(
instance.NewDomainPolicyAddedEvent(context.Background(),
@@ -96,7 +94,7 @@ func TestCommandSide_AddDefaultDomainPolicy(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
}
got, err := r.AddDefaultDomainPolicy(tt.args.ctx, tt.args.userLoginMustBeDomain, tt.args.validateOrgDomains, tt.args.smtpSenderAddressMatchesInstanceDomain)
if tt.res.err == nil {
@@ -114,7 +112,7 @@ func TestCommandSide_AddDefaultDomainPolicy(t *testing.T) {
func TestCommandSide_ChangeDefaultDomainPolicy(t *testing.T) {
type fields struct {
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@@ -135,8 +133,7 @@ func TestCommandSide_ChangeDefaultDomainPolicy(t *testing.T) {
{
name: "domain policy not existing, not found error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(),
),
},
@@ -153,8 +150,7 @@ func TestCommandSide_ChangeDefaultDomainPolicy(t *testing.T) {
{
name: "no changes, precondition error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewDomainPolicyAddedEvent(context.Background(),
@@ -180,8 +176,7 @@ func TestCommandSide_ChangeDefaultDomainPolicy(t *testing.T) {
{
name: "change, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewDomainPolicyAddedEvent(context.Background(),
@@ -236,6 +231,7 @@ func TestCommandSide_ChangeDefaultDomainPolicy(t *testing.T) {
),
// domainPolicyUsernames for each org
// org1
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewDomainPrimarySetEvent(
@@ -266,6 +262,7 @@ func TestCommandSide_ChangeDefaultDomainPolicy(t *testing.T) {
),
),
// org3
+ expectFilterOrganizationSettings("org3", false, false),
expectFilter(
eventFromEventPusher(
org.NewDomainPrimarySetEvent(
@@ -302,14 +299,16 @@ func TestCommandSide_ChangeDefaultDomainPolicy(t *testing.T) {
"user1",
"user1@org1.com",
false,
- user.UsernameChangedEventWithPolicyChange(),
+ false,
+ user.UsernameChangedEventWithPolicyChange(true),
),
user.NewUsernameChangedEvent(context.Background(),
&user.NewAggregate("user1", "org3").Aggregate,
"user1",
"user1@org3.com",
false,
- user.UsernameChangedEventWithPolicyChange(),
+ false,
+ user.UsernameChangedEventWithPolicyChange(true),
),
),
),
@@ -326,11 +325,315 @@ func TestCommandSide_ChangeDefaultDomainPolicy(t *testing.T) {
},
},
},
+ {
+ name: "change, organization scoped usernames, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ instance.NewDomainPolicyAddedEvent(context.Background(),
+ &instance.NewAggregate("INSTANCE").Aggregate,
+ true,
+ true,
+ true,
+ ),
+ ),
+ ),
+ // domainPolicyOrgs
+ expectFilter(
+ eventFromEventPusher(
+ org.NewOrgAddedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org1",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewOrgAddedEvent(context.Background(),
+ &org.NewAggregate("org2").Aggregate,
+ "org2",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &org.NewAggregate("org2").Aggregate,
+ false,
+ false,
+ false,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewOrgAddedEvent(context.Background(),
+ &org.NewAggregate("org3").Aggregate,
+ "org3",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &org.NewAggregate("org3").Aggregate,
+ false,
+ false,
+ false,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPolicyRemovedEvent(context.Background(),
+ &org.NewAggregate("org3").Aggregate,
+ ),
+ ),
+ ),
+ // domainPolicyUsernames for each org
+ // org1
+ expectFilterOrganizationSettings("org1", true, true),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(
+ context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org1.com",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org1.com",
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.English,
+ domain.GenderUnspecified,
+ "user1@org1.com",
+ false,
+ ),
+ ),
+ ),
+ // org3
+ expectFilterOrganizationSettings("org3", true, true),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(
+ context.Background(),
+ &org.NewAggregate("org3").Aggregate,
+ "org3.com",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(context.Background(),
+ &org.NewAggregate("org3").Aggregate,
+ "org3.com",
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org3").Aggregate,
+ "user1",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.English,
+ domain.GenderUnspecified,
+ "user1@org3.com",
+ false,
+ ),
+ ),
+ ),
+ expectPush(
+ newDefaultDomainPolicyChangedEvent(context.Background(), false, false, false),
+ user.NewUsernameChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1",
+ "user1@org1.com",
+ false,
+ true,
+ user.UsernameChangedEventWithPolicyChange(true),
+ ),
+ user.NewUsernameChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org3").Aggregate,
+ "user1",
+ "user1@org3.com",
+ false,
+ true,
+ user.UsernameChangedEventWithPolicyChange(true),
+ ),
+ ),
+ ),
+ },
+ args: args{
+ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
+ userLoginMustBeDomain: false,
+ validateOrgDomains: false,
+ smtpSenderAddressMatchesInstanceDomain: false,
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "INSTANCE",
+ },
+ },
+ },
+ {
+ name: "change, organization scoped usernames, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ instance.NewDomainPolicyAddedEvent(context.Background(),
+ &instance.NewAggregate("INSTANCE").Aggregate,
+ false,
+ true,
+ true,
+ ),
+ ),
+ ),
+ // domainPolicyOrgs
+ expectFilter(
+ eventFromEventPusher(
+ org.NewOrgAddedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org1",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewOrgAddedEvent(context.Background(),
+ &org.NewAggregate("org2").Aggregate,
+ "org2",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &org.NewAggregate("org2").Aggregate,
+ true,
+ false,
+ false,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewOrgAddedEvent(context.Background(),
+ &org.NewAggregate("org3").Aggregate,
+ "org3",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &org.NewAggregate("org3").Aggregate,
+ true,
+ false,
+ false,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPolicyRemovedEvent(context.Background(),
+ &org.NewAggregate("org3").Aggregate,
+ ),
+ ),
+ ),
+ // domainPolicyUsernames for each org
+ // org1
+ expectFilterOrganizationSettings("org1", true, true),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(
+ context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org1.com",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org1.com",
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1@org1.com",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.English,
+ domain.GenderUnspecified,
+ "user1@org1.com",
+ false,
+ ),
+ ),
+ ),
+ // org3
+ expectFilterOrganizationSettings("org3", true, true),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(
+ context.Background(),
+ &org.NewAggregate("org3").Aggregate,
+ "org3.com",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(context.Background(),
+ &org.NewAggregate("org3").Aggregate,
+ "org3.com",
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org3").Aggregate,
+ "user1@org3.com",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.English,
+ domain.GenderUnspecified,
+ "user1@org3.com",
+ true,
+ ),
+ ),
+ ),
+ expectPush(
+ newDefaultDomainPolicyChangedEvent(context.Background(), true, false, false),
+ user.NewUsernameChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1@org1.com",
+ "user1@org1.com",
+ true,
+ true,
+ user.UsernameChangedEventWithPolicyChange(false),
+ ),
+ user.NewUsernameChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org3").Aggregate,
+ "user1@org3.com",
+ "user1@org3.com",
+ true,
+ true,
+ user.UsernameChangedEventWithPolicyChange(true),
+ ),
+ ),
+ ),
+ },
+ args: args{
+ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
+ userLoginMustBeDomain: true,
+ validateOrgDomains: false,
+ smtpSenderAddressMatchesInstanceDomain: false,
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "INSTANCE",
+ },
+ },
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
}
got, err := r.ChangeDefaultDomainPolicy(tt.args.ctx, tt.args.userLoginMustBeDomain, tt.args.validateOrgDomains, tt.args.smtpSenderAddressMatchesInstanceDomain)
if tt.res.err == nil {
diff --git a/internal/command/instance_test.go b/internal/command/instance_test.go
index b40bba19af..0eca521a7f 100644
--- a/internal/command/instance_test.go
+++ b/internal/command/instance_test.go
@@ -478,6 +478,7 @@ func humanFilters(orgID string) []expect {
true,
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
org.NewPasswordComplexityPolicyAddedEvent(
context.Background(),
@@ -519,6 +520,7 @@ func machineFilters(orgID string, pat bool) []expect {
true,
),
),
+ expectFilterOrganizationSettings("org1", false, false),
}
if pat {
filters = append(filters,
@@ -562,6 +564,7 @@ func loginClientFilters(orgID string, pat bool) []expect {
true,
),
),
+ expectFilterOrganizationSettings("org1", false, false),
}
if pat {
filters = append(filters,
diff --git a/internal/command/org.go b/internal/command/org.go
index 215fe0b5cc..6505c8eef5 100644
--- a/internal/command/org.go
+++ b/internal/command/org.go
@@ -522,10 +522,16 @@ func (c *Commands) prepareRemoveOrg(a *org.Aggregate) preparation.Validation {
return nil, zerrors.ThrowNotFound(nil, "COMMA-aps2n", "Errors.Org.NotFound")
}
- domainPolicy, err := c.domainPolicyWriteModel(ctx, a.ID)
+ domainPolicy, err := domainPolicyWriteModel(ctx, filter, a.ID)
if err != nil {
return nil, err
}
+
+ organizationScopedUsername, err := checkOrganizationScopedUsernames(ctx, filter, a.ID, nil)
+ if err != nil {
+ return nil, err
+ }
+
usernames, err := OrgUsers(ctx, filter, a.ID)
if err != nil {
return nil, err
@@ -542,7 +548,7 @@ func (c *Commands) prepareRemoveOrg(a *org.Aggregate) preparation.Validation {
if err != nil {
return nil, err
}
- return []eventstore.Command{org.NewOrgRemovedEvent(ctx, &a.Aggregate, writeModel.Name, usernames, domainPolicy.UserLoginMustBeDomain, domains, links, entityIds)}, nil
+ return []eventstore.Command{org.NewOrgRemovedEvent(ctx, &a.Aggregate, writeModel.Name, usernames, domainPolicy.UserLoginMustBeDomain || organizationScopedUsername, domains, links, entityIds)}, nil
}, nil
}
}
diff --git a/internal/command/org_domain_test.go b/internal/command/org_domain_test.go
index df79710955..6aaee20c2a 100644
--- a/internal/command/org_domain_test.go
+++ b/internal/command/org_domain_test.go
@@ -1050,6 +1050,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org2").Aggregate,
false, false, false))),
+ expectFilterOrganizationSettings("org2", false, false),
expectPush(
org.NewDomainVerifiedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
@@ -1084,6 +1085,93 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
},
},
},
+ {
+ name: "domain verification, claimed users, orgScopedUsername, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ org.NewOrgAddedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "name",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainAddedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "domain.ch",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainVerificationAddedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "domain.ch",
+ domain.OrgDomainValidationTypeDNS,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("a"),
+ },
+ ),
+ ),
+ ),
+ expectFilter(
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org2").Aggregate,
+ "username@domain.ch",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.German,
+ domain.GenderUnspecified,
+ "email",
+ true,
+ ),
+ ),
+ ),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &org.NewAggregate("org2").Aggregate,
+ false, false, false))),
+ expectFilterOrganizationSettings("org2", true, true),
+ expectPush(
+ org.NewDomainVerifiedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "domain.ch",
+ ),
+ user.NewDomainClaimedEvent(http.WithRequestedHost(context.Background(), "zitadel.ch"),
+ &user.NewAggregate("user1", "org2").Aggregate,
+ "tempid@temporary.zitadel.ch",
+ "username@domain.ch",
+ true,
+ ),
+ ),
+ ),
+ alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ domainValidationFunc: validDomainVerification,
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "tempid"),
+ },
+ args: args{
+ ctx: context.Background(),
+ domain: &domain.OrgDomain{
+ ObjectRoot: models.ObjectRoot{
+ AggregateID: "org1",
+ },
+ Domain: "domain.ch",
+ ValidationType: domain.OrgDomainValidationTypeDNS,
+ },
+ claimedUserIDs: []string{"user1"},
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
diff --git a/internal/command/org_policy_domain.go b/internal/command/org_policy_domain.go
index c9a4fd547c..3729031629 100644
--- a/internal/command/org_policy_domain.go
+++ b/internal/command/org_policy_domain.go
@@ -124,13 +124,23 @@ func prepareAddOrgDomainPolicy(
if instancePolicy.UserLoginMustBeDomain == userLoginMustBeDomain {
return cmds, nil
}
+
+ organizationScopedUsernames, err := checkOrganizationScopedUsernames(ctx, filter, a.ID, nil)
+ if err != nil {
+ return nil, err
+ }
+
// the UserLoginMustBeDomain setting will be different from the instance
// therefore get all usernames and the current primary domain
usersWriteModel, err := domainPolicyUsernames(ctx, filter, a.ID)
if err != nil {
return nil, err
}
- return append(cmds, usersWriteModel.NewUsernameChangedEvents(ctx, userLoginMustBeDomain)...), nil
+ return append(cmds, usersWriteModel.NewUsernameChangedEvents(ctx,
+ userLoginMustBeDomain,
+ organizationScopedUsernames,
+ instancePolicy.UserLoginMustBeDomain,
+ )...), nil
}, nil
}
}
@@ -163,13 +173,22 @@ func prepareChangeOrgDomainPolicy(
if !usernameChange {
return cmds, err
}
+
+ organizationScopedUsernames, err := checkOrganizationScopedUsernames(ctx, filter, a.ID, nil)
+ if err != nil {
+ return nil, err
+ }
// get all usernames and the primary domain
usersWriteModel, err := domainPolicyUsernames(ctx, filter, a.ID)
if err != nil {
return nil, err
}
// to compute the username changed events
- return append(cmds, usersWriteModel.NewUsernameChangedEvents(ctx, userLoginMustBeDomain)...), nil
+ return append(cmds, usersWriteModel.NewUsernameChangedEvents(ctx,
+ userLoginMustBeDomain,
+ organizationScopedUsernames,
+ writeModel.UserLoginMustBeDomain,
+ )...), nil
}, nil
}
}
@@ -190,13 +209,20 @@ func prepareRemoveOrgDomainPolicy(
if err != nil {
return nil, err
}
+ policyChange := org.NewDomainPolicyRemovedEvent(ctx, &a.Aggregate)
cmds := []eventstore.Command{
- org.NewDomainPolicyRemovedEvent(ctx, &a.Aggregate),
+ policyChange,
}
+
+ organizationScopedUsernames, err := checkOrganizationScopedUsernames(ctx, filter, a.ID, nil)
+ if err != nil {
+ return nil, err
+ }
+
// regardless if the UserLoginMustBeDomain setting is true or false,
// if it will be the same value as currently on the instance,
// then there no further changes are needed
- if instancePolicy.UserLoginMustBeDomain == writeModel.UserLoginMustBeDomain {
+ if writeModel.UserLoginMustBeDomain == instancePolicy.UserLoginMustBeDomain {
return cmds, nil
}
// get all usernames and the primary domain
@@ -205,7 +231,11 @@ func prepareRemoveOrgDomainPolicy(
return nil, err
}
// to compute the username changed events
- return append(cmds, usersWriteModel.NewUsernameChangedEvents(ctx, instancePolicy.UserLoginMustBeDomain)...), nil
+ return append(cmds, usersWriteModel.NewUsernameChangedEvents(ctx,
+ instancePolicy.UserLoginMustBeDomain,
+ organizationScopedUsernames,
+ writeModel.UserLoginMustBeDomain,
+ )...), nil
}, nil
}
}
diff --git a/internal/command/org_policy_domain_test.go b/internal/command/org_policy_domain_test.go
index 24020d8026..a48f7eb794 100644
--- a/internal/command/org_policy_domain_test.go
+++ b/internal/command/org_policy_domain_test.go
@@ -18,7 +18,7 @@ import (
func TestCommandSide_AddDomainPolicy(t *testing.T) {
type fields struct {
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@@ -40,9 +40,7 @@ func TestCommandSide_AddDomainPolicy(t *testing.T) {
{
name: "org id missing, invalid argument error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
+ eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -57,8 +55,7 @@ func TestCommandSide_AddDomainPolicy(t *testing.T) {
{
name: "policy already existing, already exists error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@@ -85,8 +82,7 @@ func TestCommandSide_AddDomainPolicy(t *testing.T) {
{
name: "add policy, no userLoginMustBeDomain change, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(),
expectFilter(
eventFromEventPusher(
@@ -124,8 +120,7 @@ func TestCommandSide_AddDomainPolicy(t *testing.T) {
{
name: "add policy, userLoginMustBeDomain changed, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(),
expectFilter(
eventFromEventPusher(
@@ -137,6 +132,7 @@ func TestCommandSide_AddDomainPolicy(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewDomainVerifiedEvent(
@@ -170,7 +166,7 @@ func TestCommandSide_AddDomainPolicy(t *testing.T) {
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
- "user1@org.com",
+ "user1",
"firstname",
"lastname",
"nickname",
@@ -205,17 +201,19 @@ func TestCommandSide_AddDomainPolicy(t *testing.T) {
),
user.NewUsernameChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
- "user1@org.com",
+ "user1",
"user1",
true,
- user.UsernameChangedEventWithPolicyChange(),
+ false,
+ user.UsernameChangedEventWithPolicyChange(false),
),
user.NewUsernameChangedEvent(context.Background(),
&user.NewAggregate("user2", "org1").Aggregate,
"user@test.com",
"user@test.com",
true,
- user.UsernameChangedEventWithPolicyChange(),
+ false,
+ user.UsernameChangedEventWithPolicyChange(false),
),
),
),
@@ -233,11 +231,239 @@ func TestCommandSide_AddDomainPolicy(t *testing.T) {
},
},
},
+ {
+ name: "add policy, userLoginMustBeDomain changed, org scoped usernames, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(),
+ expectFilter(
+ eventFromEventPusher(
+ instance.NewDomainPolicyAddedEvent(context.Background(),
+ &instance.NewAggregate("instanceID").Aggregate,
+ false,
+ false,
+ false,
+ ),
+ ),
+ ),
+ expectFilterOrganizationSettings("org1", true, true),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainVerifiedEvent(
+ context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org.com",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainVerifiedEvent(
+ context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "test.com",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainRemovedEvent(
+ context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "test.com",
+ true,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(
+ context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org.com",
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.English,
+ domain.GenderUnspecified,
+ "user1@org.com",
+ false,
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user2", "org1").Aggregate,
+ "user@test.com",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.English,
+ domain.GenderUnspecified,
+ "user@test.com",
+ false,
+ ),
+ ),
+ ),
+ expectPush(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ true,
+ true,
+ true,
+ ),
+ user.NewUsernameChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1",
+ "user1",
+ true,
+ true,
+ user.UsernameChangedEventWithPolicyChange(false),
+ ),
+ user.NewUsernameChangedEvent(context.Background(),
+ &user.NewAggregate("user2", "org1").Aggregate,
+ "user@test.com",
+ "user@test.com",
+ true,
+ true,
+ user.UsernameChangedEventWithPolicyChange(false),
+ ),
+ ),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ userLoginMustBeDomain: true,
+ validateOrgDomains: true,
+ smtpSenderAddressMatchesInstanceDomain: true,
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
+ {
+ name: "add policy, userLoginMustBeDomain removed, org scoped usernames, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(),
+ expectFilter(
+ eventFromEventPusher(
+ instance.NewDomainPolicyAddedEvent(context.Background(),
+ &instance.NewAggregate("instanceID").Aggregate,
+ true,
+ false,
+ false,
+ ),
+ ),
+ ),
+ expectFilterOrganizationSettings("org1", true, true),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainVerifiedEvent(
+ context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org.com",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainVerifiedEvent(
+ context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "test.com",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainRemovedEvent(
+ context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "test.com",
+ true,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(
+ context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org.com",
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.English,
+ domain.GenderUnspecified,
+ "user1@org.com",
+ true,
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user2", "org1").Aggregate,
+ "user@test.com",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.English,
+ domain.GenderUnspecified,
+ "user@test.com",
+ true,
+ ),
+ ),
+ ),
+ expectPush(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ false,
+ true,
+ true,
+ ),
+ user.NewUsernameChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1",
+ "user1@org.com",
+ false,
+ true,
+ user.UsernameChangedEventWithPolicyChange(true),
+ ),
+ user.NewUsernameChangedEvent(context.Background(),
+ &user.NewAggregate("user2", "org1").Aggregate,
+ "user@test.com",
+ "user@test.com@org.com",
+ false,
+ true,
+ user.UsernameChangedEventWithPolicyChange(true),
+ ),
+ ),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ userLoginMustBeDomain: false,
+ validateOrgDomains: true,
+ smtpSenderAddressMatchesInstanceDomain: true,
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
}
got, err := r.AddOrgDomainPolicy(tt.args.ctx, tt.args.orgID, tt.args.userLoginMustBeDomain, tt.args.validateOrgDomains, tt.args.smtpSenderAddressMatchesInstanceDomain)
if tt.res.err == nil {
@@ -255,7 +481,7 @@ func TestCommandSide_AddDomainPolicy(t *testing.T) {
func TestCommandSide_ChangeDomainPolicy(t *testing.T) {
type fields struct {
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@@ -277,9 +503,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) {
{
name: "org id missing, invalid argument error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
+ eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -294,8 +518,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) {
{
name: "policy not existing, not found error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(),
),
},
@@ -313,8 +536,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) {
{
name: "no changes, precondition error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@@ -341,8 +563,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) {
{
name: "change, no userLoginMustBeDomain change, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@@ -377,8 +598,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) {
{
name: "change, userLoginMustBeDomain changed, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@@ -389,6 +609,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewDomainPrimarySetEvent(
@@ -429,7 +650,156 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) {
"user1",
"user1@org.com",
false,
- user.UsernameChangedEventWithPolicyChange(),
+ false,
+ user.UsernameChangedEventWithPolicyChange(true),
+ ),
+ ),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ userLoginMustBeDomain: false,
+ validateOrgDomains: false,
+ smtpSenderAddressMatchesInstanceDomain: false,
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
+ {
+ name: "change, userLoginMustBeDomain changed, org scoped usernames, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ false,
+ true,
+ true,
+ ),
+ ),
+ ),
+ expectFilterOrganizationSettings("org1", true, true),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(
+ context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org.com",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org.com",
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.English,
+ domain.GenderUnspecified,
+ "user1@org.com",
+ false,
+ ),
+ ),
+ ),
+ expectPush(
+ newDomainPolicyChangedEvent(context.Background(), "org1",
+ policy.ChangeUserLoginMustBeDomain(true),
+ policy.ChangeValidateOrgDomains(false),
+ policy.ChangeSMTPSenderAddressMatchesInstanceDomain(false),
+ ),
+ user.NewUsernameChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1",
+ "user1",
+ true,
+ true,
+ user.UsernameChangedEventWithPolicyChange(false),
+ ),
+ ),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ userLoginMustBeDomain: true,
+ validateOrgDomains: false,
+ smtpSenderAddressMatchesInstanceDomain: false,
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
+ {
+ name: "change, userLoginMustBeDomain removed, org scoped usernames, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ true,
+ true,
+ true,
+ ),
+ ),
+ ),
+ expectFilterOrganizationSettings("org1", true, true),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(
+ context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org.com",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org.com",
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.English,
+ domain.GenderUnspecified,
+ "user1@org.com",
+ true,
+ ),
+ ),
+ ),
+ expectPush(
+ newDomainPolicyChangedEvent(context.Background(), "org1",
+ policy.ChangeUserLoginMustBeDomain(false),
+ policy.ChangeValidateOrgDomains(false),
+ policy.ChangeSMTPSenderAddressMatchesInstanceDomain(false),
+ ),
+ user.NewUsernameChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1",
+ "user1@org.com",
+ false,
+ true,
+ user.UsernameChangedEventWithPolicyChange(true),
),
),
),
@@ -451,7 +821,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
}
got, err := r.ChangeOrgDomainPolicy(tt.args.ctx, tt.args.orgID, tt.args.userLoginMustBeDomain, tt.args.validateOrgDomains, tt.args.smtpSenderAddressMatchesInstanceDomain)
if tt.res.err == nil {
@@ -469,7 +839,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) {
func TestCommandSide_RemoveDomainPolicy(t *testing.T) {
type fields struct {
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@@ -488,9 +858,7 @@ func TestCommandSide_RemoveDomainPolicy(t *testing.T) {
{
name: "org id missing, invalid argument error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
+ eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -502,8 +870,7 @@ func TestCommandSide_RemoveDomainPolicy(t *testing.T) {
{
name: "policy not existing, not found error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(),
),
},
@@ -518,8 +885,7 @@ func TestCommandSide_RemoveDomainPolicy(t *testing.T) {
{
name: "remove, no userLoginMustBeDomain change, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@@ -540,6 +906,7 @@ func TestCommandSide_RemoveDomainPolicy(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
org.NewDomainPolicyRemovedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate),
@@ -559,8 +926,7 @@ func TestCommandSide_RemoveDomainPolicy(t *testing.T) {
{
name: "remove, userLoginMustBeDomain changed, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
@@ -581,6 +947,7 @@ func TestCommandSide_RemoveDomainPolicy(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewDomainPrimarySetEvent(
@@ -619,7 +986,166 @@ func TestCommandSide_RemoveDomainPolicy(t *testing.T) {
"user1",
"user1@org.com",
false,
- user.UsernameChangedEventWithPolicyChange(),
+ false,
+ user.UsernameChangedEventWithPolicyChange(true),
+ ),
+ ),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
+ {
+ name: "remove, userLoginMustBeDomain removed, org scoped usernames, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ true,
+ true,
+ true,
+ ),
+ ),
+ ),
+ expectFilter(
+ eventFromEventPusher(
+ instance.NewDomainPolicyAddedEvent(context.Background(),
+ &instance.NewAggregate("instanceID").Aggregate,
+ false,
+ true,
+ true,
+ ),
+ ),
+ ),
+ expectFilterOrganizationSettings("org1", true, true),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(
+ context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org.com",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org.com",
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.English,
+ domain.GenderUnspecified,
+ "user1@org.com",
+ false,
+ ),
+ ),
+ ),
+ expectPush(
+ org.NewDomainPolicyRemovedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ ),
+ user.NewUsernameChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1",
+ "user1@org.com",
+ false,
+ true,
+ user.UsernameChangedEventWithPolicyChange(true),
+ ),
+ ),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
+ {
+ name: "remove, userLoginMustBeDomain changed, org scoped usernames, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ false,
+ true,
+ true,
+ ),
+ ),
+ ),
+ expectFilter(
+ eventFromEventPusher(
+ instance.NewDomainPolicyAddedEvent(context.Background(),
+ &instance.NewAggregate("instanceID").Aggregate,
+ true,
+ true,
+ true,
+ ),
+ ),
+ ),
+ expectFilterOrganizationSettings("org1", true, true),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(
+ context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org.com",
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewDomainPrimarySetEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ "org.com",
+ ),
+ ),
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.English,
+ domain.GenderUnspecified,
+ "user1@org.com",
+ true,
+ ),
+ ),
+ ),
+ expectPush(
+ org.NewDomainPolicyRemovedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ ),
+ user.NewUsernameChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "user1",
+ "user1",
+ true,
+ true,
+ user.UsernameChangedEventWithPolicyChange(false),
),
),
),
@@ -638,7 +1164,7 @@ func TestCommandSide_RemoveDomainPolicy(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
}
got, err := r.RemoveOrgDomainPolicy(tt.args.ctx, tt.args.orgID)
if tt.res.err == nil {
diff --git a/internal/command/org_test.go b/internal/command/org_test.go
index c07d5f7678..a2c2713874 100644
--- a/internal/command/org_test.go
+++ b/internal/command/org_test.go
@@ -1101,6 +1101,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(),
expectFilter(),
expectFilter(),
@@ -1143,6 +1144,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(),
expectFilter(),
expectFilter(),
@@ -1183,6 +1185,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1397,6 +1400,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(), // org member check
expectFilter(
eventFromEventPusher(
@@ -1706,6 +1710,7 @@ func TestCommandSide_SetUpOrg(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(),
expectFilter(),
expectFilter(), // org member check
diff --git a/internal/command/organization_settings.go b/internal/command/organization_settings.go
new file mode 100644
index 0000000000..2459fd60d8
--- /dev/null
+++ b/internal/command/organization_settings.go
@@ -0,0 +1,140 @@
+package command
+
+import (
+ "context"
+
+ "github.com/zitadel/zitadel/internal/command/preparation"
+ "github.com/zitadel/zitadel/internal/domain"
+ "github.com/zitadel/zitadel/internal/telemetry/tracing"
+ "github.com/zitadel/zitadel/internal/zerrors"
+)
+
+type SetOrganizationSettings struct {
+ OrganizationID string
+
+ OrganizationScopedUsernames *bool
+}
+
+func (e *SetOrganizationSettings) IsValid() error {
+ if e.OrganizationID == "" {
+ return zerrors.ThrowInvalidArgument(nil, "COMMAND-zI4z7cLLRJ", "Errors.Org.Settings.Invalid")
+ }
+ return nil
+}
+
+func (c *Commands) SetOrganizationSettings(ctx context.Context, set *SetOrganizationSettings) (_ *domain.ObjectDetails, err error) {
+ if err := set.IsValid(); err != nil {
+ return nil, err
+ }
+ wm, err := c.getOrganizationSettingsWriteModelByID(ctx, set.OrganizationID)
+ if err != nil {
+ return nil, err
+ }
+ if !wm.OrganizationState.Exists() {
+ return nil, zerrors.ThrowNotFound(nil, "COMMAND-oDzwP5kmdP", "Errors.NotFound")
+ }
+
+ domainPolicy, err := c.domainPolicyWriteModel(ctx, wm.AggregateID)
+ if err != nil {
+ return nil, err
+ }
+
+ events, err := wm.NewSet(ctx,
+ set.OrganizationScopedUsernames,
+ domainPolicy.UserLoginMustBeDomain,
+ c.getOrganizationScopedUsernames,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return c.pushAppendAndReduceDetails(ctx, wm, events...)
+}
+
+func (c *Commands) DeleteOrganizationSettings(ctx context.Context, id string) (*domain.ObjectDetails, error) {
+ if id == "" {
+ return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-eU5hkMy3Pf", "Errors.IDMissing")
+ }
+ wm, err := c.getOrganizationSettingsWriteModelByID(ctx, id)
+ if err != nil {
+ return nil, err
+ }
+ if !wm.State.Exists() {
+ return writeModelToObjectDetails(wm.GetWriteModel()), nil
+ }
+
+ domainPolicy, err := c.domainPolicyWriteModel(ctx, wm.AggregateID)
+ if err != nil {
+ return nil, err
+ }
+
+ events, err := wm.NewRemoved(ctx,
+ domainPolicy.UserLoginMustBeDomain,
+ c.getOrganizationScopedUsernames,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return c.pushAppendAndReduceDetails(ctx, wm, events...)
+}
+
+func checkOrganizationScopedUsernames(ctx context.Context, filter preparation.FilterToQueryReducer, id string, checkPermission domain.PermissionCheck) (_ bool, err error) {
+ wm := NewOrganizationSettingsWriteModel(id, checkPermission)
+ events, err := filter(ctx, wm.Query())
+ if err != nil {
+ return false, err
+ }
+ if len(events) == 0 {
+ return false, nil
+ }
+ wm.AppendEvents(events...)
+ err = wm.Reduce()
+ if err != nil {
+ return false, err
+ }
+
+ return wm.State.Exists() && wm.OrganizationScopedUsernames, nil
+}
+
+func (c *Commands) getOrganizationSettingsWriteModelByID(ctx context.Context, id string) (*OrganizationSettingsWriteModel, error) {
+ wm := NewOrganizationSettingsWriteModel(id, c.checkPermission)
+ err := c.eventstore.FilterToQueryReducer(ctx, wm)
+ if err != nil {
+ return nil, err
+ }
+ return wm, nil
+}
+
+func (c *Commands) checkOrganizationScopedUsernames(ctx context.Context, orgID string) (_ bool, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ wm, err := c.getOrganizationSettingsWriteModelByID(ctx, orgID)
+ if err != nil {
+ return false, err
+ }
+
+ return wm.State.Exists() && wm.OrganizationScopedUsernames, nil
+}
+
+func (c *Commands) getOrganizationScopedUsernamesWriteModelByID(ctx context.Context, id string) (*OrganizationScopedUsernamesWriteModel, error) {
+ wm := NewOrganizationScopedUsernamesWriteModel(id)
+ err := c.eventstore.FilterToQueryReducer(ctx, wm)
+ if err != nil {
+ return nil, err
+ }
+ return wm, nil
+}
+
+func (c *Commands) getOrganizationScopedUsernames(ctx context.Context, id string) ([]string, error) {
+ wm, err := c.getOrganizationScopedUsernamesWriteModelByID(ctx, id)
+ if err != nil {
+ return nil, err
+ }
+ usernames := make([]string, len(wm.Users))
+ for i, user := range wm.Users {
+ usernames[i] = user.username
+ }
+ return usernames, nil
+}
diff --git a/internal/command/organization_settings_model.go b/internal/command/organization_settings_model.go
new file mode 100644
index 0000000000..890cf671e5
--- /dev/null
+++ b/internal/command/organization_settings_model.go
@@ -0,0 +1,245 @@
+package command
+
+import (
+ "context"
+
+ "github.com/zitadel/zitadel/internal/domain"
+ "github.com/zitadel/zitadel/internal/eventstore"
+ "github.com/zitadel/zitadel/internal/repository/org"
+ settings "github.com/zitadel/zitadel/internal/repository/organization_settings"
+ "github.com/zitadel/zitadel/internal/repository/user"
+ "github.com/zitadel/zitadel/internal/zerrors"
+)
+
+type OrganizationSettingsWriteModel struct {
+ eventstore.WriteModel
+
+ OrganizationScopedUsernames bool
+
+ OrganizationState domain.OrgState
+
+ State domain.OrganizationSettingsState
+ checkPermission domain.PermissionCheck
+}
+
+func (wm *OrganizationSettingsWriteModel) GetWriteModel() *eventstore.WriteModel {
+ return &wm.WriteModel
+}
+
+func (wm *OrganizationSettingsWriteModel) checkPermissionWrite(
+ ctx context.Context,
+ resourceOwner string,
+ aggregateID string,
+) error {
+ if wm.checkPermission == nil {
+ return zerrors.ThrowPermissionDenied(nil, "COMMAND-8Dttuyj0B4", "Permission check not defined")
+ }
+ return wm.checkPermission(ctx, domain.PermissionIAMPolicyWrite, resourceOwner, aggregateID)
+}
+
+func (wm *OrganizationSettingsWriteModel) checkPermissionDelete(
+ ctx context.Context,
+ resourceOwner string,
+ aggregateID string,
+) error {
+ if wm.checkPermission == nil {
+ return zerrors.ThrowPermissionDenied(nil, "COMMAND-6R54f4vWqv", "Permission check not defined")
+ }
+ return wm.checkPermission(ctx, domain.PermissionIAMPolicyDelete, resourceOwner, aggregateID)
+}
+
+func NewOrganizationSettingsWriteModel(id string, checkPermission domain.PermissionCheck) *OrganizationSettingsWriteModel {
+ return &OrganizationSettingsWriteModel{
+ WriteModel: eventstore.WriteModel{
+ AggregateID: id,
+ ResourceOwner: id,
+ },
+ checkPermission: checkPermission,
+ }
+}
+
+func (wm *OrganizationSettingsWriteModel) Reduce() error {
+ for _, event := range wm.Events {
+ switch e := event.(type) {
+ case *settings.OrganizationSettingsSetEvent:
+ wm.OrganizationScopedUsernames = e.OrganizationScopedUsernames
+ wm.State = domain.OrganizationSettingsStateActive
+ case *settings.OrganizationSettingsRemovedEvent:
+ wm.OrganizationScopedUsernames = false
+ wm.State = domain.OrganizationSettingsStateRemoved
+ case *org.OrgAddedEvent:
+ wm.OrganizationState = domain.OrgStateActive
+ wm.OrganizationScopedUsernames = false
+ case *org.OrgRemovedEvent:
+ wm.OrganizationState = domain.OrgStateRemoved
+ wm.OrganizationScopedUsernames = false
+ }
+ }
+ return wm.WriteModel.Reduce()
+}
+
+func (wm *OrganizationSettingsWriteModel) Query() *eventstore.SearchQueryBuilder {
+ return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
+ ResourceOwner(wm.ResourceOwner).
+ AddQuery().
+ AggregateTypes(settings.AggregateType).
+ AggregateIDs(wm.AggregateID).
+ EventTypes(settings.OrganizationSettingsSetEventType,
+ settings.OrganizationSettingsRemovedEventType).
+ Or().
+ AggregateTypes(org.AggregateType).
+ AggregateIDs(wm.AggregateID).
+ EventTypes(org.OrgAddedEventType,
+ org.OrgRemovedEventType).
+ Builder()
+}
+
+func (wm *OrganizationSettingsWriteModel) NewSet(
+ ctx context.Context,
+ organizationScopedUsernames *bool,
+ userLoginMustBeDomain bool,
+ usernamesF func(ctx context.Context, orgID string) ([]string, error),
+) (_ []eventstore.Command, err error) {
+ if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
+ return nil, err
+ }
+ // no changes
+ if organizationScopedUsernames == nil || *organizationScopedUsernames == wm.OrganizationScopedUsernames {
+ return nil, nil
+ }
+
+ var usernames []string
+ if (wm.OrganizationScopedUsernames || userLoginMustBeDomain) != (*organizationScopedUsernames || userLoginMustBeDomain) {
+ usernames, err = usernamesF(ctx, wm.AggregateID)
+ if err != nil {
+ return nil, err
+ }
+ }
+ events := []eventstore.Command{
+ settings.NewOrganizationSettingsAddedEvent(ctx,
+ SettingsAggregateFromWriteModel(&wm.WriteModel),
+ usernames,
+ *organizationScopedUsernames || userLoginMustBeDomain,
+ wm.OrganizationScopedUsernames || userLoginMustBeDomain,
+ ),
+ }
+ return events, nil
+}
+
+func (wm *OrganizationSettingsWriteModel) NewRemoved(
+ ctx context.Context,
+ userLoginMustBeDomain bool,
+ usernamesF func(ctx context.Context, orgID string) ([]string, error),
+) (_ []eventstore.Command, err error) {
+ if err := wm.checkPermissionDelete(ctx, wm.ResourceOwner, wm.AggregateID); err != nil {
+ return nil, err
+ }
+
+ var usernames []string
+ if userLoginMustBeDomain != wm.OrganizationScopedUsernames {
+ usernames, err = usernamesF(ctx, wm.AggregateID)
+ if err != nil {
+ return nil, err
+ }
+ }
+ events := []eventstore.Command{
+ settings.NewOrganizationSettingsRemovedEvent(ctx,
+ SettingsAggregateFromWriteModel(&wm.WriteModel),
+ usernames,
+ userLoginMustBeDomain,
+ wm.OrganizationScopedUsernames || userLoginMustBeDomain,
+ ),
+ }
+ return events, nil
+}
+
+func SettingsAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
+ return &eventstore.Aggregate{
+ ID: wm.AggregateID,
+ Type: settings.AggregateType,
+ ResourceOwner: wm.ResourceOwner,
+ InstanceID: wm.InstanceID,
+ Version: settings.AggregateVersion,
+ }
+}
+
+type OrganizationScopedUsernamesWriteModel struct {
+ eventstore.WriteModel
+
+ Users []*organizationScopedUser
+}
+
+type organizationScopedUser struct {
+ id string
+ username string
+}
+
+func NewOrganizationScopedUsernamesWriteModel(orgID string) *OrganizationScopedUsernamesWriteModel {
+ return &OrganizationScopedUsernamesWriteModel{
+ WriteModel: eventstore.WriteModel{
+ ResourceOwner: orgID,
+ },
+ Users: make([]*organizationScopedUser, 0),
+ }
+}
+
+func (wm *OrganizationScopedUsernamesWriteModel) AppendEvents(events ...eventstore.Event) {
+ wm.WriteModel.AppendEvents(events...)
+}
+
+func (wm *OrganizationScopedUsernamesWriteModel) Reduce() error {
+ for _, event := range wm.Events {
+ switch e := event.(type) {
+ case *user.HumanAddedEvent:
+ wm.Users = append(wm.Users, &organizationScopedUser{id: e.Aggregate().ID, username: e.UserName})
+ case *user.HumanRegisteredEvent:
+ wm.Users = append(wm.Users, &organizationScopedUser{id: e.Aggregate().ID, username: e.UserName})
+ case *user.MachineAddedEvent:
+ wm.Users = append(wm.Users, &organizationScopedUser{id: e.Aggregate().ID, username: e.UserName})
+ case *user.UsernameChangedEvent:
+ for _, user := range wm.Users {
+ if user.id == e.Aggregate().ID {
+ user.username = e.UserName
+ break
+ }
+ }
+ case *user.DomainClaimedEvent:
+ for _, user := range wm.Users {
+ if user.id == e.Aggregate().ID {
+ user.username = e.UserName
+ break
+ }
+ }
+ case *user.UserRemovedEvent:
+ wm.removeUser(e.Aggregate().ID)
+ }
+ }
+ return wm.WriteModel.Reduce()
+}
+
+func (wm *OrganizationScopedUsernamesWriteModel) removeUser(userID string) {
+ for i, user := range wm.Users {
+ if user.id == userID {
+ wm.Users[i] = wm.Users[len(wm.Users)-1]
+ wm.Users[len(wm.Users)-1] = nil
+ wm.Users = wm.Users[:len(wm.Users)-1]
+ return
+ }
+ }
+}
+
+func (wm *OrganizationScopedUsernamesWriteModel) Query() *eventstore.SearchQueryBuilder {
+ return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
+ ResourceOwner(wm.ResourceOwner).
+ AddQuery().
+ AggregateTypes(user.AggregateType).
+ EventTypes(
+ user.HumanAddedType,
+ user.HumanRegisteredType,
+ user.MachineAddedEventType,
+ user.UserUserNameChangedType,
+ user.UserDomainClaimedType,
+ user.UserRemovedType,
+ ).
+ Builder()
+}
diff --git a/internal/command/organization_settings_test.go b/internal/command/organization_settings_test.go
new file mode 100644
index 0000000000..f6317fd35f
--- /dev/null
+++ b/internal/command/organization_settings_test.go
@@ -0,0 +1,542 @@
+package command
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "golang.org/x/text/language"
+
+ "github.com/zitadel/zitadel/internal/domain"
+ "github.com/zitadel/zitadel/internal/eventstore"
+ "github.com/zitadel/zitadel/internal/repository/org"
+ settings "github.com/zitadel/zitadel/internal/repository/organization_settings"
+ "github.com/zitadel/zitadel/internal/repository/user"
+ "github.com/zitadel/zitadel/internal/zerrors"
+)
+
+func TestCommandSide_SetSettingsOrganization(t *testing.T) {
+ type fields struct {
+ eventstore func(t *testing.T) *eventstore.Eventstore
+ checkPermission domain.PermissionCheck
+ }
+ type args struct {
+ ctx context.Context
+ settings *SetOrganizationSettings
+ }
+ type res struct {
+ want *domain.ObjectDetails
+ err func(error) bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ res res
+ }{
+ {
+ name: "org id missing, invalid argument error",
+ fields: fields{
+ eventstore: expectEventstore(),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ settings: &SetOrganizationSettings{
+ OrganizationID: "",
+ OrganizationScopedUsernames: boolPtr(true),
+ },
+ },
+ res: res{
+ err: zerrors.IsErrorInvalidArgument,
+ },
+ },
+ {
+ name: "org not found, not found error",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ settings: &SetOrganizationSettings{
+ OrganizationID: "org1",
+ OrganizationScopedUsernames: boolPtr(true),
+ },
+ },
+ res: res{
+ err: zerrors.IsNotFound,
+ },
+ },
+ {
+ name: "settings already existing, no changes",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilterPreOrganizationSettings("org1", true, true, true),
+ expectFilterOrgDomainPolicy(false),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ settings: &SetOrganizationSettings{
+ OrganizationID: "org1",
+ OrganizationScopedUsernames: boolPtr(true),
+ },
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ ID: "org1",
+ },
+ },
+ },
+ {
+ name: "settings set, new",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilterPreOrganizationSettings("org1", true, false, false),
+ expectFilterOrgDomainPolicy(false),
+ expectFilterOrganizationScopedUsernames(false, "username1", "username2", "username3"),
+ expectPush(
+ settings.NewOrganizationSettingsAddedEvent(context.Background(),
+ &settings.NewAggregate("org1", "org1").Aggregate,
+ []string{"username1", "username2", "username3"},
+ true,
+ false,
+ ),
+ ),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ settings: &SetOrganizationSettings{
+ OrganizationID: "org1",
+ OrganizationScopedUsernames: boolPtr(true),
+ },
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ ID: "org1",
+ },
+ },
+ },
+ {
+ name: "settings set, no permission",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilterPreOrganizationSettings("org1", true, false, true),
+ expectFilterOrgDomainPolicy(false),
+ ),
+ checkPermission: newMockPermissionCheckNotAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ settings: &SetOrganizationSettings{
+ OrganizationID: "org1",
+ OrganizationScopedUsernames: boolPtr(true),
+ },
+ },
+ res: res{
+ err: zerrors.IsPermissionDenied,
+ },
+ },
+ {
+ name: "settings set, changed",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilterPreOrganizationSettings("org1", true, true, false),
+ expectFilterOrgDomainPolicy(false),
+ expectFilterOrganizationScopedUsernames(false, "username1", "username2", "username3"),
+ expectPush(
+ settings.NewOrganizationSettingsAddedEvent(context.Background(),
+ &settings.NewAggregate("org1", "org1").Aggregate,
+ []string{"username1", "username2", "username3"},
+ true,
+ false,
+ ),
+ ),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ settings: &SetOrganizationSettings{
+ OrganizationID: "org1",
+ OrganizationScopedUsernames: boolPtr(true),
+ },
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ ID: "org1",
+ },
+ },
+ },
+ {
+ name: "settings not set, not existing",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilterPreOrganizationSettings("org1", true, false, false),
+ expectFilterOrgDomainPolicy(false),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ settings: &SetOrganizationSettings{
+ OrganizationID: "org1",
+ OrganizationScopedUsernames: boolPtr(false),
+ },
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ ID: "org1",
+ },
+ },
+ },
+ {
+ name: "settings set, changed, usernameMustBeDomain set",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilterPreOrganizationSettings("org1", true, true, false),
+ expectFilterOrgDomainPolicy(true),
+ expectPush(
+ settings.NewOrganizationSettingsAddedEvent(context.Background(),
+ &settings.NewAggregate("org1", "org1").Aggregate,
+ []string{"username1", "username2", "username3"},
+ true,
+ true,
+ ),
+ ),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ settings: &SetOrganizationSettings{
+ OrganizationID: "org1",
+ OrganizationScopedUsernames: boolPtr(true),
+ },
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ ID: "org1",
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ r := &Commands{
+ eventstore: tt.fields.eventstore(t),
+ checkPermission: tt.fields.checkPermission,
+ }
+ got, err := r.SetOrganizationSettings(tt.args.ctx, tt.args.settings)
+ if tt.res.err == nil {
+ assert.NoError(t, err)
+ }
+ if tt.res.err != nil && !tt.res.err(err) {
+ t.Errorf("got wrong err: %v ", err)
+ }
+ if tt.res.err == nil {
+ assert.Equal(t, tt.res.want, got)
+ }
+ })
+ }
+}
+
+func TestCommandSide_DeleteSettingsOrganization(t *testing.T) {
+ type fields struct {
+ eventstore func(t *testing.T) *eventstore.Eventstore
+ checkPermission domain.PermissionCheck
+ }
+ type args struct {
+ ctx context.Context
+ orgID string
+ }
+ type res struct {
+ want *domain.ObjectDetails
+ err func(error) bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ res res
+ }{
+ {
+ name: "org id missing, invalid argument error",
+ fields: fields{
+ eventstore: expectEventstore(),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "",
+ },
+ res: res{
+ err: zerrors.IsErrorInvalidArgument,
+ },
+ },
+ {
+ name: "settings delete, no change",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ ID: "org1",
+ },
+ },
+ },
+ {
+ name: "settings delete, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilterOrganizationSettings("org1", true, true),
+ expectFilterOrgDomainPolicy(false),
+ expectFilterOrganizationScopedUsernames(false, "username1", "username2", "username3"),
+ expectPush(
+ settings.NewOrganizationSettingsRemovedEvent(context.Background(),
+ &settings.NewAggregate("org1", "org1").Aggregate,
+ []string{"username1", "username2", "username3"},
+ false,
+ true,
+ ),
+ ),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ ID: "org1",
+ },
+ },
+ },
+ {
+ name: "settings delete, unset, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilterOrganizationSettings("org1", true, false),
+ expectFilterOrgDomainPolicy(false),
+ expectPush(
+ settings.NewOrganizationSettingsRemovedEvent(context.Background(),
+ &settings.NewAggregate("org1", "org1").Aggregate,
+ []string{"username1", "username2", "username3"},
+ false,
+ false,
+ ),
+ ),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ ID: "org1",
+ },
+ },
+ },
+ {
+ name: "settings delete, unset, usernameMustBeDomain set",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilterOrganizationSettings("org1", true, false),
+ expectFilterOrgDomainPolicy(true),
+ expectFilterOrganizationScopedUsernames(true, "username1", "username2", "username3"),
+ expectPush(
+ settings.NewOrganizationSettingsRemovedEvent(context.Background(),
+ &settings.NewAggregate("org1", "org1").Aggregate,
+ []string{"username1", "username2", "username3"},
+ true,
+ true,
+ ),
+ ),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ ID: "org1",
+ },
+ },
+ },
+ {
+ name: "settings delete, set, usernameMustBeDomain set",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilterOrganizationSettings("org1", true, true),
+ expectFilterOrgDomainPolicy(true),
+ expectPush(
+ settings.NewOrganizationSettingsRemovedEvent(context.Background(),
+ &settings.NewAggregate("org1", "org1").Aggregate,
+ []string{"username1", "username2", "username3"},
+ true,
+ true,
+ ),
+ ),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ ID: "org1",
+ },
+ },
+ },
+ {
+ name: "settings delete, no permission",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilterOrganizationSettings("org1", true, true),
+ expectFilterOrgDomainPolicy(true),
+ ),
+ checkPermission: newMockPermissionCheckNotAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ },
+ res: res{
+ err: zerrors.IsPermissionDenied,
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ r := &Commands{
+ eventstore: tt.fields.eventstore(t),
+ checkPermission: tt.fields.checkPermission,
+ }
+ got, err := r.DeleteOrganizationSettings(tt.args.ctx, tt.args.orgID)
+ if tt.res.err == nil {
+ assert.NoError(t, err)
+ }
+ if tt.res.err != nil && !tt.res.err(err) {
+ t.Errorf("got wrong err: %v ", err)
+ }
+ if tt.res.err == nil {
+ assert.Equal(t, tt.res.want, got)
+ }
+ })
+ }
+}
+
+func expectFilterPreOrganizationSettings(orgID string, orgExisting, settingExisting, orgScopedUsernames bool) expect {
+ var events []eventstore.Event
+ events = append(events,
+ expectFilterPreOrganizationSettingsEvents(context.Background(), orgID, orgExisting)...,
+ )
+ events = append(events,
+ expectFilterOrganizationSettingsEvents(context.Background(), orgID, settingExisting, orgScopedUsernames)...,
+ )
+ return expectFilter(
+ events...,
+ )
+}
+
+func expectFilterPreOrganizationSettingsEvents(ctx context.Context, orgID string, orgExisting bool) []eventstore.Event {
+ var events []eventstore.Event
+ if orgExisting {
+ events = append(events,
+ eventFromEventPusher(
+ org.NewOrgAddedEvent(ctx,
+ &org.NewAggregate(orgID).Aggregate,
+ "org",
+ ),
+ ),
+ )
+ }
+ return events
+}
+
+func expectFilterOrganizationSettings(orgID string, settingExisting, orgScopedUsernames bool) expect {
+ return expectFilter(
+ expectFilterOrganizationSettingsEvents(context.Background(), orgID, settingExisting, orgScopedUsernames)...,
+ )
+}
+
+func expectFilterOrganizationSettingsEvents(ctx context.Context, orgID string, settingExisting, orgScopedUsernames bool) []eventstore.Event {
+ var events []eventstore.Event
+ if settingExisting {
+ events = append(events,
+ eventFromEventPusher(
+ settings.NewOrganizationSettingsAddedEvent(ctx,
+ &settings.NewAggregate(orgID, orgID).Aggregate,
+ []string{},
+ orgScopedUsernames,
+ !orgScopedUsernames,
+ ),
+ ),
+ )
+ }
+ return events
+}
+
+func expectFilterOrgDomainPolicy(userLoginMustBeDomain bool) expect {
+ return expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &org.NewAggregate("org1").Aggregate,
+ userLoginMustBeDomain, false, false,
+ ),
+ ),
+ )
+}
+
+func expectFilterOrganizationScopedUsernames(userMustBeDomain bool, usernames ...string) expect {
+ events := make([]eventstore.Event, len(usernames))
+ for i, username := range usernames {
+ events[i] = eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate(username, "org1").Aggregate,
+ username,
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.German,
+ domain.GenderUnspecified,
+ "email@test.ch",
+ userMustBeDomain,
+ ),
+ )
+ }
+ return expectFilter(
+ events...,
+ )
+}
diff --git a/internal/command/policy_org_model.go b/internal/command/policy_org_model.go
index cc0c8bcd6f..9bf56aa981 100644
--- a/internal/command/policy_org_model.go
+++ b/internal/command/policy_org_model.go
@@ -148,7 +148,7 @@ func (wm *DomainPolicyUsernamesWriteModel) Query() *eventstore.SearchQueryBuilde
Builder()
}
-func (wm *DomainPolicyUsernamesWriteModel) NewUsernameChangedEvents(ctx context.Context, userLoginMustBeDomain bool) []eventstore.Command {
+func (wm *DomainPolicyUsernamesWriteModel) NewUsernameChangedEvents(ctx context.Context, userLoginMustBeDomain, organizationScopedUsernames, oldUserLoginMustBeDomain bool) []eventstore.Command {
events := make([]eventstore.Command, 0, len(wm.Users))
for _, changeUser := range wm.Users {
events = append(events, user.NewUsernameChangedEvent(ctx,
@@ -156,12 +156,21 @@ func (wm *DomainPolicyUsernamesWriteModel) NewUsernameChangedEvents(ctx context.
changeUser.username,
wm.newUsername(changeUser.username, userLoginMustBeDomain),
userLoginMustBeDomain,
- user.UsernameChangedEventWithPolicyChange()),
- )
+ organizationScopedUsernames,
+ user.UsernameChangedEventWithPolicyChange(oldUserLoginMustBeDomain),
+ ))
}
return events
}
+func (wm *DomainPolicyUsernamesWriteModel) Usernames() []string {
+ usernames := make([]string, 0, len(wm.Users))
+ for i, user := range wm.Users {
+ usernames[i] = user.username
+ }
+ return usernames
+}
+
func (wm *DomainPolicyUsernamesWriteModel) newUsername(username string, userLoginMustBeDomain bool) string {
if !userLoginMustBeDomain {
// if the UserLoginMustBeDomain will be false, then it's currently true
diff --git a/internal/command/user.go b/internal/command/user.go
index d834169f8a..b803c5496e 100644
--- a/internal/command/user.go
+++ b/internal/command/user.go
@@ -43,10 +43,15 @@ func (c *Commands) ChangeUsername(ctx context.Context, orgID, userID, userName s
if err = c.userValidateDomain(ctx, orgID, userName, domainPolicy.UserLoginMustBeDomain); err != nil {
return nil, err
}
+ orgScopedUsernames, err := c.checkOrganizationScopedUsernames(ctx, orgID)
+ if err != nil {
+ return nil, err
+ }
+
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx,
- user.NewUsernameChangedEvent(ctx, userAgg, existingUser.UserName, userName, domainPolicy.UserLoginMustBeDomain))
+ user.NewUsernameChangedEvent(ctx, userAgg, existingUser.UserName, userName, domainPolicy.UserLoginMustBeDomain, orgScopedUsernames))
if err != nil {
return nil, err
}
@@ -189,9 +194,13 @@ func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string,
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.DomainPolicy.NotExisting")
}
+ orgScopedUsername, err := c.checkOrganizationScopedUsernames(ctx, existingUser.ResourceOwner)
+ if err != nil {
+ return nil, err
+ }
var events []eventstore.Command
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
- events = append(events, user.NewUserRemovedEvent(ctx, userAgg, existingUser.UserName, existingUser.IDPLinks, domainPolicy.UserLoginMustBeDomain))
+ events = append(events, user.NewUserRemovedEvent(ctx, userAgg, existingUser.UserName, existingUser.IDPLinks, domainPolicy.UserLoginMustBeDomain || orgScopedUsername))
for _, grantID := range cascadingGrantIDs {
removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true, false, nil)
@@ -269,6 +278,11 @@ func (c *Commands) userDomainClaimed(ctx context.Context, userID string) (events
return nil, nil, err
}
+ organizationScopedUsername, err := c.checkOrganizationScopedUsernames(ctx, existingUser.ResourceOwner)
+ if err != nil {
+ return nil, nil, err
+ }
+
id, err := c.idGenerator.Next()
if err != nil {
return nil, nil, err
@@ -279,7 +293,8 @@ func (c *Commands) userDomainClaimed(ctx context.Context, userID string) (events
userAgg,
fmt.Sprintf("%s@temporary.%s", id, http_util.DomainContext(ctx).RequestedDomain()),
existingUser.UserName,
- domainPolicy.UserLoginMustBeDomain),
+ domainPolicy.UserLoginMustBeDomain || organizationScopedUsername,
+ ),
}, changedUserGrant, nil
}
@@ -295,6 +310,12 @@ func (c *Commands) prepareUserDomainClaimed(ctx context.Context, filter preparat
if err != nil {
return nil, err
}
+
+ organizationScopedUsername, err := checkOrganizationScopedUsernames(ctx, filter, userWriteModel.ResourceOwner, nil)
+ if err != nil {
+ return nil, err
+ }
+
userAgg := UserAggregateFromWriteModel(&userWriteModel.WriteModel)
id, err := c.idGenerator.Next()
@@ -307,7 +328,8 @@ func (c *Commands) prepareUserDomainClaimed(ctx context.Context, filter preparat
userAgg,
fmt.Sprintf("%s@temporary.%s", id, http_util.DomainContext(ctx).RequestedDomain()),
userWriteModel.UserName,
- domainPolicy.UserLoginMustBeDomain), nil
+ domainPolicy.UserLoginMustBeDomain || organizationScopedUsername,
+ ), nil
}
func (c *Commands) UserDomainClaimedSent(ctx context.Context, orgID, userID string) (err error) {
diff --git a/internal/command/user_human.go b/internal/command/user_human.go
index 07628b9e19..e4a6148159 100644
--- a/internal/command/user_human.go
+++ b/internal/command/user_human.go
@@ -199,6 +199,11 @@ func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto
return nil, err
}
+ organizationScopedUsername, err := checkOrganizationScopedUsernames(ctx, filter, a.ResourceOwner, nil)
+ if err != nil {
+ return nil, err
+ }
+
var createCmd humanCreationCommand
if human.Register {
createCmd = user.NewHumanRegisteredEvent(
@@ -212,7 +217,7 @@ func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto
human.PreferredLanguage,
human.Gender,
human.Email.Address,
- domainPolicy.UserLoginMustBeDomain,
+ domainPolicy.UserLoginMustBeDomain || organizationScopedUsername,
"", // no user agent id available
)
} else {
@@ -227,7 +232,7 @@ func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto
human.PreferredLanguage,
human.Gender,
human.Email.Address,
- domainPolicy.UserLoginMustBeDomain,
+ domainPolicy.UserLoginMustBeDomain || organizationScopedUsername,
)
}
@@ -439,6 +444,12 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
if err != nil {
return nil, nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-2N9fs", "Errors.Org.DomainPolicy.NotFound")
}
+
+ organizationScopedUsername, err := c.checkOrganizationScopedUsernames(ctx, orgID)
+ if err != nil {
+ return nil, nil, err
+ }
+
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, orgID)
if err != nil {
return nil, nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-4N8gs", "Errors.Org.PasswordComplexityPolicy.NotFound")
@@ -455,7 +466,7 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
}
}
- events, userAgg, addedHuman, addedCode, code, err := c.importHuman(ctx, orgID, human, passwordless, links, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator)
+ events, userAgg, addedHuman, addedCode, code, err := c.importHuman(ctx, orgID, human, passwordless, links, domainPolicy, organizationScopedUsername, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator)
if err != nil {
return nil, nil, err
}
@@ -501,7 +512,7 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
return writeModelToHuman(addedHuman), passwordlessCode, nil
}
-func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, links []*domain.UserIDPLink, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, userAgg *eventstore.Aggregate, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) {
+func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, links []*domain.UserIDPLink, domainPolicy *domain.DomainPolicy, orgScopedUsername bool, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, userAgg *eventstore.Aggregate, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -511,7 +522,7 @@ func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.
if err = human.Normalize(); err != nil {
return nil, nil, nil, nil, "", err
}
- events, userAgg, humanWriteModel, err = c.createHuman(ctx, orgID, human, links, passwordless, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
+ events, userAgg, humanWriteModel, err = c.createHuman(ctx, orgID, human, links, passwordless, domainPolicy, orgScopedUsername, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
if err != nil {
return nil, nil, nil, nil, "", err
}
@@ -526,7 +537,7 @@ func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.
return events, userAgg, humanWriteModel, passwordlessCodeWriteModel, code, nil
}
-func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, links []*domain.UserIDPLink, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) (events []eventstore.Command, userAgg *eventstore.Aggregate, addedHuman *HumanWriteModel, err error) {
+func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, links []*domain.UserIDPLink, passwordless bool, domainPolicy *domain.DomainPolicy, orgScopedUsername bool, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) (events []eventstore.Command, userAgg *eventstore.Aggregate, addedHuman *HumanWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -559,7 +570,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
// TODO: adlerhurst maybe we could simplify the code below
userAgg = UserAggregateFromWriteModelCtx(ctx, &addedHuman.WriteModel)
- events = append(events, createAddHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
+ events = append(events, createAddHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain, orgScopedUsername))
for _, link := range links {
event, err := c.addUserIDPLink(ctx, userAgg, link, false)
@@ -619,7 +630,7 @@ func (c *Commands) HumanSkipMFAInit(ctx context.Context, userID, resourceowner s
}
// TODO: adlerhurst maybe we can simplify createAddHumanEvent and createRegisterHumanEvent
-func createAddHumanEvent(ctx context.Context, aggregate *eventstore.Aggregate, human *domain.Human, userLoginMustBeDomain bool) *user.HumanAddedEvent {
+func createAddHumanEvent(ctx context.Context, aggregate *eventstore.Aggregate, human *domain.Human, userLoginMustBeDomain, orgScopedUsername bool) *user.HumanAddedEvent {
addEvent := user.NewHumanAddedEvent(
ctx,
aggregate,
@@ -631,7 +642,7 @@ func createAddHumanEvent(ctx context.Context, aggregate *eventstore.Aggregate, h
human.PreferredLanguage,
human.Gender,
human.EmailAddress,
- userLoginMustBeDomain,
+ userLoginMustBeDomain || orgScopedUsername,
)
if human.Phone != nil {
addEvent.AddPhoneData(human.PhoneNumber)
diff --git a/internal/command/user_human_test.go b/internal/command/user_human_test.go
index 1ef3e2aab6..a5641d69c0 100644
--- a/internal/command/user_human_test.go
+++ b/internal/command/user_human_test.go
@@ -190,6 +190,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(),
expectFilter(),
),
@@ -231,6 +232,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewHumanAddedEvent(context.Background(),
&userAgg.Aggregate,
@@ -299,6 +301,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewHumanAddedEvent(context.Background(),
&userAgg.Aggregate,
@@ -368,6 +371,77 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
+ expectPush(
+ user.NewHumanAddedEvent(context.Background(),
+ &userAgg.Aggregate,
+ "username",
+ "firstname",
+ "lastname",
+ "",
+ "firstname lastname",
+ AllowedLanguage,
+ domain.GenderUnspecified,
+ "email@test.ch",
+ true,
+ ),
+ user.NewHumanInitialCodeAddedEvent(context.Background(),
+ &userAgg.Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("userinit"),
+ },
+ time.Hour*1,
+ "",
+ ),
+ ),
+ ),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
+ codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ newCode: mockEncryptedCode("userinit", time.Hour),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ human: &AddHuman{
+ Username: "username",
+ FirstName: "firstname",
+ LastName: "lastname",
+ Email: Email{
+ Address: "email@test.ch",
+ },
+ PreferredLanguage: AllowedLanguage,
+ },
+ secretGenerator: GetMockSecretGenerator(t),
+ allowInitMail: true,
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ Sequence: 0,
+ EventDate: time.Time{},
+ ResourceOwner: "org1",
+ },
+ wantID: "user1",
+ },
+ },
+ {
+ name: "add human (with initial code), orgScopedUsername, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &userAgg.Aggregate,
+ false,
+ true,
+ true,
+ ),
+ ),
+ ),
+ expectFilterOrganizationSettings("org1", true, true),
expectPush(
user.NewHumanAddedEvent(context.Background(),
&userAgg.Aggregate,
@@ -437,6 +511,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -507,6 +582,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -580,6 +656,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -654,6 +731,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -714,6 +792,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -777,6 +856,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -840,6 +920,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -963,6 +1044,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -1042,6 +1124,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -1151,6 +1234,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
newAddHumanEvent("", false, true, "+41711234567", AllowedLanguage),
user.NewHumanInitialCodeAddedEvent(
@@ -1216,6 +1300,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -1326,6 +1411,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
newAddHumanEvent("", false, true, "", AllowedLanguage),
user.NewHumanInitialCodeAddedEvent(
@@ -1518,6 +1604,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(),
expectFilter(),
),
@@ -1557,6 +1644,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -1602,6 +1690,94 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ 1,
+ false,
+ false,
+ false,
+ false,
+ ),
+ ),
+ ),
+ expectPush(
+ newAddHumanEvent("$plain$x$password", true, true, "", AllowedLanguage),
+ user.NewHumanInitialCodeAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("a"),
+ },
+ time.Hour*1,
+ "",
+ ),
+ ),
+ ),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
+ userPasswordHasher: mockPasswordHasher("x"),
+ },
+ args{
+ ctx: context.Background(),
+ orgID: "org1",
+ human: &domain.Human{
+ Username: "username",
+ Password: &domain.Password{
+ SecretString: "password",
+ ChangeRequired: true,
+ },
+ Profile: &domain.Profile{
+ FirstName: "firstname",
+ LastName: "lastname",
+ PreferredLanguage: AllowedLanguage,
+ },
+ Email: &domain.Email{
+ EmailAddress: "email@test.ch",
+ },
+ },
+ secretGenerator: GetMockSecretGenerator(t),
+ }
+ },
+ res: res{
+ wantHuman: &domain.Human{
+ ObjectRoot: models.ObjectRoot{
+ AggregateID: "user1",
+ ResourceOwner: "org1",
+ },
+ Username: "username",
+ Profile: &domain.Profile{
+ FirstName: "firstname",
+ LastName: "lastname",
+ DisplayName: "firstname lastname",
+ PreferredLanguage: AllowedLanguage,
+ },
+ Email: &domain.Email{
+ EmailAddress: "email@test.ch",
+ },
+ State: domain.UserStateInitial,
+ },
+ },
+ },
+ {
+ name: "add human (with password and initial code), orgScopedUsername, ok",
+ given: func(t *testing.T) (fields, args) {
+ return fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ false,
+ true,
+ true,
+ ),
+ ),
+ ),
+ expectFilterOrganizationSettings("org1", true, true),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -1688,6 +1864,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -1768,6 +1945,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -1867,6 +2045,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -1970,6 +2149,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -2105,6 +2285,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -2200,6 +2381,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -2285,6 +2467,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -2371,6 +2554,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -2516,6 +2700,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -2659,6 +2844,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -2831,6 +3017,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -3003,6 +3190,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -3182,6 +3370,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -3829,6 +4018,10 @@ func TestAddHumanCommand(t *testing.T) {
),
}, nil
}).
+ Append(
+ func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
+ return expectFilterOrganizationSettingsEvents(ctx, "org1", false, false), nil
+ }).
Append(
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
@@ -3882,6 +4075,87 @@ func TestAddHumanCommand(t *testing.T) {
),
}, nil
}).
+ Append(
+ func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
+ return expectFilterOrganizationSettingsEvents(ctx, "org1", false, false), nil
+ }).
+ Append(
+ func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
+ return []eventstore.Event{
+ org.NewPasswordComplexityPolicyAddedEvent(
+ ctx,
+ &org.NewAggregate("id").Aggregate,
+ 2,
+ false,
+ false,
+ false,
+ false,
+ ),
+ }, nil
+ }).
+ Filter(),
+ },
+ want: Want{
+ Commands: []eventstore.Command{
+ func() *user.HumanAddedEvent {
+ event := user.NewHumanAddedEvent(
+ context.Background(),
+ &agg.Aggregate,
+ "username",
+ "gigi",
+ "giraffe",
+ "",
+ "gigi giraffe",
+ AllowedLanguage,
+ 0,
+ "support@zitadel.com",
+ true,
+ )
+ event.AddPasswordData("$plain$x$password", false)
+ return event
+ }(),
+ user.NewHumanEmailVerifiedEvent(context.Background(), &agg.Aggregate),
+ },
+ },
+ },
+ {
+ name: "correct, orgScopedUsername",
+ fields: fields{
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id"),
+ },
+ args: args{
+ human: &AddHuman{
+ Email: Email{Address: "support@zitadel.com", Verified: true},
+ FirstName: "gigi",
+ LastName: "giraffe",
+ Password: "password",
+ Username: "username",
+ PreferredLanguage: AllowedLanguage,
+ },
+ orgID: "ro",
+ hasher: mockPasswordHasher("x"),
+ codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ filter: NewMultiFilter().Append(
+ func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
+ return []eventstore.Event{}, nil
+ }).
+ Append(
+ func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
+ return []eventstore.Event{
+ org.NewDomainPolicyAddedEvent(
+ ctx,
+ &org.NewAggregate("id").Aggregate,
+ true,
+ true,
+ true,
+ ),
+ }, nil
+ }).
+ Append(
+ func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
+ // never set, as only used in creation of instance and org
+ return expectFilterOrganizationSettingsEvents(ctx, "org1", false, false), nil
+ }).
Append(
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
@@ -3953,6 +4227,10 @@ func TestAddHumanCommand(t *testing.T) {
),
}, nil
}).
+ Append(
+ func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
+ return expectFilterOrganizationSettingsEvents(ctx, "org1", false, false), nil
+ }).
Append(
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
@@ -4025,6 +4303,10 @@ func TestAddHumanCommand(t *testing.T) {
),
}, nil
}).
+ Append(
+ func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
+ return expectFilterOrganizationSettingsEvents(ctx, "org1", false, false), nil
+ }).
Append(
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
@@ -4097,6 +4379,10 @@ func TestAddHumanCommand(t *testing.T) {
),
}, nil
}).
+ Append(
+ func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
+ return expectFilterOrganizationSettingsEvents(ctx, "org1", false, false), nil
+ }).
Append(
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
diff --git a/internal/command/user_machine.go b/internal/command/user_machine.go
index 75ed43ee69..c281617000 100644
--- a/internal/command/user_machine.go
+++ b/internal/command/user_machine.go
@@ -61,8 +61,12 @@ func AddMachineCommand(a *user.Aggregate, machine *Machine) preparation.Validati
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.DomainPolicy.NotFound")
}
+ orgScopedUsername, err := checkOrganizationScopedUsernames(ctx, filter, a.ResourceOwner, nil)
+ if err != nil {
+ return nil, err
+ }
return []eventstore.Command{
- user.NewMachineAddedEvent(ctx, &a.Aggregate, machine.Username, machine.Name, machine.Description, domainPolicy.UserLoginMustBeDomain, machine.AccessTokenType),
+ user.NewMachineAddedEvent(ctx, &a.Aggregate, machine.Username, machine.Name, machine.Description, domainPolicy.UserLoginMustBeDomain || orgScopedUsername, machine.AccessTokenType),
}, nil
}, nil
}
diff --git a/internal/command/user_machine_test.go b/internal/command/user_machine_test.go
index 6d94154a42..32664b5c1b 100644
--- a/internal/command/user_machine_test.go
+++ b/internal/command/user_machine_test.go
@@ -121,6 +121,54 @@ func TestCommandSide_AddMachine(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
+ expectPush(
+ user.NewMachineAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "username",
+ "name",
+ "description",
+ true,
+ domain.OIDCTokenTypeBearer,
+ ),
+ ),
+ ),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
+ },
+ args: args{
+ ctx: context.Background(),
+ machine: &Machine{
+ ObjectRoot: models.ObjectRoot{
+ ResourceOwner: "org1",
+ },
+ Description: "description",
+ Name: "name",
+ Username: "username",
+ },
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
+ {
+ name: "add machine, orgScopedUsername, ok",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ expectFilter(),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ false,
+ true,
+ true,
+ ),
+ ),
+ ),
+ expectFilterOrganizationSettings("org1", true, true),
expectPush(
user.NewMachineAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
@@ -167,6 +215,7 @@ func TestCommandSide_AddMachine(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewMachineAddedEvent(context.Background(),
&user.NewAggregate("optionalID1", "org1").Aggregate,
@@ -214,6 +263,7 @@ func TestCommandSide_AddMachine(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewMachineAddedEvent(context.Background(),
&user.NewAggregate("aggregateID", "org1").Aggregate,
@@ -264,6 +314,7 @@ func TestCommandSide_AddMachine(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewMachineAddedEvent(context.Background(),
&user.NewAggregate("aggregateID", "org1").Aggregate,
@@ -312,6 +363,7 @@ func TestCommandSide_AddMachine(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewMachineAddedEvent(context.Background(),
&user.NewAggregate("aggregateID", "org1").Aggregate,
@@ -361,6 +413,7 @@ func TestCommandSide_AddMachine(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewMachineAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
@@ -436,6 +489,7 @@ func TestCommandSide_AddMachine(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewMachineAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
@@ -489,6 +543,7 @@ func TestCommandSide_AddMachine(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewMachineAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
diff --git a/internal/command/user_test.go b/internal/command/user_test.go
index 6a1597fc8b..c971f0939d 100644
--- a/internal/command/user_test.go
+++ b/internal/command/user_test.go
@@ -295,12 +295,14 @@ func TestCommandSide_UsernameChange(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewUsernameChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"test@test.ch",
true,
+ false,
),
),
),
@@ -317,6 +319,61 @@ func TestCommandSide_UsernameChange(t *testing.T) {
},
},
},
+ {
+ name: "email as username, orgScopedUsername, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "username",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.German,
+ domain.GenderUnspecified,
+ "email@test.ch",
+ true,
+ ),
+ ),
+ ),
+ expectFilter(),
+ expectFilter(
+ eventFromEventPusher(
+ instance.NewDomainPolicyAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ false,
+ true,
+ true,
+ ),
+ ),
+ ),
+ expectFilterOrganizationSettings("org1", true, true),
+ expectPush(
+ user.NewUsernameChangedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "username",
+ "test",
+ false,
+ true,
+ ),
+ ),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ userID: "user1",
+ username: "test",
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
{
name: "email as username, verified domain, ok",
fields: fields{
@@ -348,6 +405,7 @@ func TestCommandSide_UsernameChange(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewDomainVerifiedEvent(context.Background(),
@@ -362,6 +420,7 @@ func TestCommandSide_UsernameChange(t *testing.T) {
"username",
"test@test.ch",
false,
+ false,
),
),
),
@@ -409,12 +468,14 @@ func TestCommandSide_UsernameChange(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewUsernameChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"username1",
true,
+ false,
),
),
),
@@ -462,11 +523,13 @@ func TestCommandSide_UsernameChange(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewUsernameChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"username1",
+ false,
true,
),
),
@@ -506,7 +569,7 @@ func TestCommandSide_UsernameChange(t *testing.T) {
func TestCommandSide_DeactivateUser(t *testing.T) {
type fields struct {
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
}
type (
args struct {
@@ -528,9 +591,7 @@ func TestCommandSide_DeactivateUser(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
+ eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -544,8 +605,7 @@ func TestCommandSide_DeactivateUser(t *testing.T) {
{
name: "user not existing, not found error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(),
),
},
@@ -561,8 +621,7 @@ func TestCommandSide_DeactivateUser(t *testing.T) {
{
name: "user already inactive, precondition error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -598,8 +657,7 @@ func TestCommandSide_DeactivateUser(t *testing.T) {
{
name: "deactivate user, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -638,7 +696,7 @@ func TestCommandSide_DeactivateUser(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
}
got, err := r.DeactivateUser(tt.args.ctx, tt.args.userID, tt.args.orgID)
if tt.res.err == nil {
@@ -656,7 +714,7 @@ func TestCommandSide_DeactivateUser(t *testing.T) {
func TestCommandSide_ReactivateUser(t *testing.T) {
type fields struct {
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
}
type (
args struct {
@@ -678,9 +736,7 @@ func TestCommandSide_ReactivateUser(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
+ eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -694,8 +750,7 @@ func TestCommandSide_ReactivateUser(t *testing.T) {
{
name: "user not existing, not found error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(),
),
},
@@ -711,8 +766,7 @@ func TestCommandSide_ReactivateUser(t *testing.T) {
{
name: "user already active, precondition error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -743,8 +797,7 @@ func TestCommandSide_ReactivateUser(t *testing.T) {
{
name: "reactivate user, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -787,7 +840,7 @@ func TestCommandSide_ReactivateUser(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
}
got, err := r.ReactivateUser(tt.args.ctx, tt.args.userID, tt.args.orgID)
if tt.res.err == nil {
@@ -805,7 +858,7 @@ func TestCommandSide_ReactivateUser(t *testing.T) {
func TestCommandSide_LockUser(t *testing.T) {
type fields struct {
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
}
type (
args struct {
@@ -827,9 +880,7 @@ func TestCommandSide_LockUser(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
+ eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -843,8 +894,7 @@ func TestCommandSide_LockUser(t *testing.T) {
{
name: "user not existing, not found error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(),
),
},
@@ -860,8 +910,7 @@ func TestCommandSide_LockUser(t *testing.T) {
{
name: "user already locked, precondition error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -897,8 +946,7 @@ func TestCommandSide_LockUser(t *testing.T) {
{
name: "lock user, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -937,7 +985,7 @@ func TestCommandSide_LockUser(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
}
got, err := r.LockUser(tt.args.ctx, tt.args.userID, tt.args.orgID)
if tt.res.err == nil {
@@ -955,7 +1003,7 @@ func TestCommandSide_LockUser(t *testing.T) {
func TestCommandSide_UnlockUser(t *testing.T) {
type fields struct {
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
}
type (
args struct {
@@ -977,9 +1025,7 @@ func TestCommandSide_UnlockUser(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
+ eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -993,8 +1039,7 @@ func TestCommandSide_UnlockUser(t *testing.T) {
{
name: "user not existing, not found error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(),
),
},
@@ -1010,8 +1055,7 @@ func TestCommandSide_UnlockUser(t *testing.T) {
{
name: "user already active, precondition error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1042,8 +1086,7 @@ func TestCommandSide_UnlockUser(t *testing.T) {
{
name: "unlock user, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1086,7 +1129,7 @@ func TestCommandSide_UnlockUser(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
}
got, err := r.UnlockUser(tt.args.ctx, tt.args.userID, tt.args.orgID)
if tt.res.err == nil {
@@ -1104,7 +1147,7 @@ func TestCommandSide_UnlockUser(t *testing.T) {
func TestCommandSide_RemoveUser(t *testing.T) {
type fields struct {
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
}
type (
args struct {
@@ -1129,9 +1172,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
+ eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -1145,8 +1186,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
{
name: "user not existing, not found error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(),
),
},
@@ -1162,8 +1202,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
{
name: "org iam policy not found, precondition error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1196,8 +1235,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
{
name: "remove user, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1225,6 +1263,60 @@ func TestCommandSide_RemoveUser(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
+ expectPush(
+ user.NewUserRemovedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "username",
+ nil,
+ true,
+ ),
+ ),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ userID: "user1",
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
+ {
+ name: "remove user, orgScopedUsername, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ user.NewHumanAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ "username",
+ "firstname",
+ "lastname",
+ "nickname",
+ "displayname",
+ language.German,
+ domain.GenderUnspecified,
+ "email@test.ch",
+ true,
+ ),
+ ),
+ ),
+ expectFilter(),
+ expectFilter(
+ eventFromEventPusher(
+ instance.NewDomainPolicyAddedEvent(context.Background(),
+ &user.NewAggregate("user1", "org1").Aggregate,
+ false,
+ true,
+ true,
+ ),
+ ),
+ ),
+ expectFilterOrganizationSettings("org1", true, true),
expectPush(
user.NewUserRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
@@ -1249,8 +1341,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
{
name: "remove user with erxternal idp, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1286,6 +1377,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewUserRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
@@ -1315,8 +1407,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
{
name: "remove user with user memberships, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1344,6 +1435,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewUserRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
@@ -1418,7 +1510,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
}
got, err := r.RemoveUser(tt.args.ctx, tt.args.userID, tt.args.orgID, tt.args.cascadeUserMemberships, tt.args.cascadeUserGrants...)
if tt.res.err == nil {
@@ -1434,7 +1526,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
func TestCommands_RevokeAccessToken(t *testing.T) {
type fields struct {
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@@ -1455,7 +1547,7 @@ func TestCommands_RevokeAccessToken(t *testing.T) {
{
"id missing error",
fields{
- eventstoreExpect(t),
+ expectEventstore(),
},
args{
context.Background(),
@@ -1471,7 +1563,7 @@ func TestCommands_RevokeAccessToken(t *testing.T) {
{
"not active error",
fields{
- eventstoreExpect(t,
+ expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewUserTokenAddedEvent(context.Background(),
@@ -1507,7 +1599,7 @@ func TestCommands_RevokeAccessToken(t *testing.T) {
{
"active ok",
fields{
- eventstoreExpect(t,
+ expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewUserTokenAddedEvent(context.Background(),
@@ -1552,7 +1644,7 @@ func TestCommands_RevokeAccessToken(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
}
got, err := c.RevokeAccessToken(tt.args.ctx, tt.args.userID, tt.args.orgID, tt.args.tokenID)
if tt.res.err == nil {
@@ -1570,7 +1662,7 @@ func TestCommands_RevokeAccessToken(t *testing.T) {
func TestCommandSide_UserDomainClaimedSent(t *testing.T) {
type fields struct {
- eventstore *eventstore.Eventstore
+ eventstore func(t *testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@@ -1589,9 +1681,7 @@ func TestCommandSide_UserDomainClaimedSent(t *testing.T) {
{
name: "userid missing, invalid argument error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
+ eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -1604,8 +1694,7 @@ func TestCommandSide_UserDomainClaimedSent(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(),
),
},
@@ -1621,8 +1710,7 @@ func TestCommandSide_UserDomainClaimedSent(t *testing.T) {
{
name: "code sent, ok",
fields: fields{
- eventstore: eventstoreExpect(
- t,
+ eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
@@ -1657,7 +1745,7 @@ func TestCommandSide_UserDomainClaimedSent(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
- eventstore: tt.fields.eventstore,
+ eventstore: tt.fields.eventstore(t),
}
err := r.UserDomainClaimedSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
if tt.res.err == nil {
diff --git a/internal/command/user_v2.go b/internal/command/user_v2.go
index d6c5e7de53..028fda7f4b 100644
--- a/internal/command/user_v2.go
+++ b/internal/command/user_v2.go
@@ -2,6 +2,7 @@ package command
import (
"context"
+
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/domain"
@@ -145,8 +146,13 @@ func (c *Commands) RemoveUserV2(ctx context.Context, userID, resourceOwner strin
if err != nil {
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-l40ykb3xh2", "Errors.Org.DomainPolicy.NotExisting")
}
+ organizationScopedUsername, err := c.checkOrganizationScopedUsernames(ctx, existingUser.ResourceOwner)
+ if err != nil {
+ return nil, err
+ }
+
var events []eventstore.Command
- events = append(events, user.NewUserRemovedEvent(ctx, &existingUser.Aggregate().Aggregate, existingUser.UserName, existingUser.IDPLinks, domainPolicy.UserLoginMustBeDomain))
+ events = append(events, user.NewUserRemovedEvent(ctx, &existingUser.Aggregate().Aggregate, existingUser.UserName, existingUser.IDPLinks, domainPolicy.UserLoginMustBeDomain || organizationScopedUsername))
for _, grantID := range cascadingGrantIDs {
removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true, true, nil)
diff --git a/internal/command/user_v2_human.go b/internal/command/user_v2_human.go
index 0945ae7578..3bb54c6d07 100644
--- a/internal/command/user_v2_human.go
+++ b/internal/command/user_v2_human.go
@@ -165,6 +165,11 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
return err
}
+ organizationScopedUsername, err := c.checkOrganizationScopedUsernames(ctx, resourceOwner)
+ if err != nil {
+ return err
+ }
+
var createCmd humanCreationCommand
if human.Register {
createCmd = user.NewHumanRegisteredEvent(
@@ -178,7 +183,7 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
human.PreferredLanguage,
human.Gender,
human.Email.Address,
- domainPolicy.UserLoginMustBeDomain,
+ domainPolicy.UserLoginMustBeDomain || organizationScopedUsername,
human.UserAgentID,
)
} else {
@@ -193,7 +198,7 @@ func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human
human.PreferredLanguage,
human.Gender,
human.Email.Address,
- domainPolicy.UserLoginMustBeDomain,
+ domainPolicy.UserLoginMustBeDomain || organizationScopedUsername,
)
}
diff --git a/internal/command/user_v2_human_test.go b/internal/command/user_v2_human_test.go
index e44e182b92..afa570cd58 100644
--- a/internal/command/user_v2_human_test.go
+++ b/internal/command/user_v2_human_test.go
@@ -60,6 +60,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
}
userAgg := user.NewAggregate("user1", "org1")
+ orgAgg := org.NewAggregate("org1")
cryptoAlg := crypto.CreateMockEncryptionAlg(gomock.NewController(t))
totpSecret := "TOTPSecret"
@@ -191,7 +192,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
true,
true,
true,
@@ -199,6 +200,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
expectFilter(),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(),
),
checkPermission: newMockPermissionCheckAllowed(),
@@ -233,13 +235,14 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewHumanRegisteredEvent(context.Background(),
&userAgg.Aggregate,
@@ -335,13 +338,14 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewHumanAddedEvent(context.Background(),
&userAgg.Aggregate,
@@ -412,6 +416,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -483,6 +488,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
newAddHumanEvent("", false, true, "", language.English),
user.NewHumanEmailCodeAddedEventV2(context.Background(),
@@ -542,6 +548,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -616,6 +623,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -684,13 +692,14 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -748,13 +757,14 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -812,13 +822,14 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
false,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -876,7 +887,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
false,
true,
true,
@@ -929,7 +940,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
false,
true,
true,
@@ -944,6 +955,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -1017,13 +1029,14 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -1127,13 +1140,14 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -1232,13 +1246,14 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
newAddHumanEvent("", false, true, "+41711234567", language.English),
user.NewHumanInitialCodeAddedEvent(
@@ -1305,6 +1320,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
@@ -1409,13 +1425,14 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
newAddHumanEvent("", false, true, "", language.English),
user.NewHumanInitialCodeAddedEvent(
@@ -1479,13 +1496,14 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewGoogleIDPAddedEvent(context.Background(),
@@ -1566,13 +1584,14 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectFilter(
eventFromEventPusher(
org.NewGoogleIDPAddedEvent(context.Background(),
@@ -1646,13 +1665,98 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
+ expectPush(
+ user.NewHumanRegisteredEvent(context.Background(),
+ &userAgg.Aggregate,
+ "username",
+ "firstname",
+ "lastname",
+ "",
+ "firstname lastname",
+ language.English,
+ domain.GenderUnspecified,
+ "email@test.ch",
+ true,
+ "userAgentID",
+ ),
+ user.NewHumanInitialCodeAddedEvent(context.Background(),
+ &userAgg.Aggregate,
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("userinit"),
+ },
+ time.Hour*1,
+ "authRequestID",
+ ),
+ user.NewHumanOTPAddedEvent(context.Background(),
+ &userAgg.Aggregate,
+ totpSecretEnc,
+ ),
+ user.NewHumanOTPVerifiedEvent(context.Background(),
+ &userAgg.Aggregate,
+ "",
+ ),
+ ),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
+ newCode: mockEncryptedCode("userinit", time.Hour),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ human: &AddHuman{
+ Username: "username",
+ FirstName: "firstname",
+ LastName: "lastname",
+ Email: Email{
+ Address: "email@test.ch",
+ },
+ PreferredLanguage: language.English,
+ Register: true,
+ UserAgentID: "userAgentID",
+ AuthRequestID: "authRequestID",
+ TOTPSecret: totpSecret,
+ },
+ secretGenerator: GetMockSecretGenerator(t),
+ allowInitMail: true,
+ codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ Sequence: 0,
+ EventDate: time.Time{},
+ ResourceOwner: "org1",
+ },
+ wantID: "user1",
+ },
+ },
+ {
+ name: "register human with TOTPSecret, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &orgAgg.Aggregate,
+ true,
+ true,
+ true,
+ ),
+ ),
+ ),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewHumanRegisteredEvent(context.Background(),
&userAgg.Aggregate,
@@ -1729,7 +1833,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
false,
true,
true,
@@ -1775,14 +1879,14 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
},
},
{
- name: "register human (validate domain), ok",
+ name: "register human (validate domain), orgScopedUsername, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(),
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
false,
true,
true,
@@ -1797,6 +1901,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", true, true),
expectPush(
user.NewHumanRegisteredEvent(context.Background(),
&userAgg.Aggregate,
@@ -1808,7 +1913,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
language.English,
domain.GenderUnspecified,
"email@example.com",
- false,
+ true,
"userAgentID",
),
user.NewHumanInitialCodeAddedEvent(context.Background(),
@@ -1864,7 +1969,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
false,
true,
true,
@@ -1886,6 +1991,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewHumanRegisteredEvent(context.Background(),
&userAgg.Aggregate,
@@ -1953,7 +2059,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
false,
true,
true,
@@ -1979,6 +2085,7 @@ func TestCommandSide_AddUserHuman(t *testing.T) {
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewHumanRegisteredEvent(context.Background(),
&userAgg.Aggregate,
@@ -2107,6 +2214,7 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
}
userAgg := user.NewAggregate("user1", "org1")
+ orgAgg := user.NewAggregate("org1", "org1")
tests := []struct {
name string
@@ -2199,19 +2307,68 @@ func TestCommandSide_ChangeUserHuman(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewUsernameChangedEvent(context.Background(),
&userAgg.Aggregate,
"username",
"changed",
true,
+ false,
+ ),
+ ),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ human: &ChangeHuman{
+ Username: gu.Ptr("changed"),
+ },
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ Sequence: 0,
+ EventDate: time.Time{},
+ ResourceOwner: "org1",
+ },
+ },
+ },
+ {
+ name: "change human username, orgScopedUsername, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(
+ newAddHumanEvent("$plain$x$password", true, false, "", language.English),
+ ),
+ ),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &orgAgg.Aggregate,
+ false,
+ true,
+ true,
+ ),
+ ),
+ ),
+ expectFilterOrganizationSettings("org1", true, true),
+ expectPush(
+ user.NewUsernameChangedEvent(context.Background(),
+ &userAgg.Aggregate,
+ "username",
+ "changed",
+ false,
+ true,
),
),
),
diff --git a/internal/command/user_v2_machine_test.go b/internal/command/user_v2_machine_test.go
index 14df4bfae7..c5883286b8 100644
--- a/internal/command/user_v2_machine_test.go
+++ b/internal/command/user_v2_machine_test.go
@@ -32,6 +32,8 @@ func TestCommandSide_ChangeUserMachine(t *testing.T) {
}
userAgg := user.NewAggregate("user1", "org1")
+ orgAgg := org.NewAggregate("org1")
+
userAddedEvent := user.NewMachineAddedEvent(context.Background(),
&userAgg.Aggregate,
"username",
@@ -101,19 +103,65 @@ func TestCommandSide_ChangeUserMachine(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(context.Background(),
- &userAgg.Aggregate,
+ &orgAgg.Aggregate,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewUsernameChangedEvent(context.Background(),
&userAgg.Aggregate,
"username",
"changed",
true,
+ false,
+ ),
+ ),
+ ),
+ checkPermission: newMockPermissionCheckAllowed(),
+ },
+ args: args{
+ ctx: context.Background(),
+ orgID: "org1",
+ machine: &ChangeMachine{
+ Username: gu.Ptr("changed"),
+ },
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ Sequence: 0,
+ EventDate: time.Time{},
+ ResourceOwner: "org1",
+ },
+ },
+ }, {
+ name: "change machine username, orgScopedUsername, ok",
+ fields: fields{
+ eventstore: expectEventstore(
+ expectFilter(
+ eventFromEventPusher(userAddedEvent),
+ ),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ &orgAgg.Aggregate,
+ false,
+ true,
+ true,
+ ),
+ ),
+ ),
+ expectFilterOrganizationSettings("org1", true, true),
+ expectPush(
+ user.NewUsernameChangedEvent(context.Background(),
+ &userAgg.Aggregate,
+ "username",
+ "changed",
+ false,
+ true,
),
),
),
diff --git a/internal/command/user_v2_model_test.go b/internal/command/user_v2_model_test.go
index cb12c8fcda..ecaa2db400 100644
--- a/internal/command/user_v2_model_test.go
+++ b/internal/command/user_v2_model_test.go
@@ -343,6 +343,7 @@ func TestCommandSide_userExistsWriteModel(t *testing.T) {
"username",
"changed",
true,
+ false,
),
),
),
diff --git a/internal/command/user_v2_test.go b/internal/command/user_v2_test.go
index 685ad95253..f878569a06 100644
--- a/internal/command/user_v2_test.go
+++ b/internal/command/user_v2_test.go
@@ -18,6 +18,7 @@ import (
)
func TestCommandSide_LockUserV2(t *testing.T) {
+ userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
@@ -79,7 +80,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -93,7 +94,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewUserLockedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
@@ -117,7 +118,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"name",
"description",
@@ -127,7 +128,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewUserLockedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
@@ -151,7 +152,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -166,7 +167,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
),
expectPush(
user.NewUserLockedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
@@ -189,7 +190,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -222,7 +223,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"name",
"description",
@@ -233,7 +234,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
),
expectPush(
user.NewUserLockedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
@@ -271,6 +272,7 @@ func TestCommandSide_LockUserV2(t *testing.T) {
}
func TestCommandSide_UnlockUserV2(t *testing.T) {
+ userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
@@ -332,7 +334,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -364,7 +366,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
eventstore: expectEventstore(
expectFilter(
user.NewMachineAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"name",
"description",
@@ -392,7 +394,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -406,12 +408,12 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewUserLockedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate),
+ userAgg),
),
),
expectPush(
user.NewUserUnlockedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
@@ -434,7 +436,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -448,7 +450,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewUserLockedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate),
+ userAgg),
),
),
),
@@ -471,7 +473,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"name",
"description",
@@ -481,12 +483,12 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewUserLockedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate),
+ userAgg),
),
),
expectPush(
user.NewUserUnlockedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
@@ -524,6 +526,7 @@ func TestCommandSide_UnlockUserV2(t *testing.T) {
}
func TestCommandSide_DeactivateUserV2(t *testing.T) {
+ userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
@@ -585,7 +588,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -599,7 +602,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
nil, time.Hour*1,
"",
),
@@ -625,7 +628,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -639,7 +642,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewUserDeactivatedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
@@ -663,7 +666,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -677,13 +680,13 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewHumanInitializedCheckSucceededEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
expectPush(
user.NewUserDeactivatedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
@@ -706,7 +709,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -720,7 +723,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewHumanInitializedCheckSucceededEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
@@ -744,7 +747,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"name",
"description",
@@ -754,7 +757,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewUserDeactivatedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
@@ -778,7 +781,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"name",
"description",
@@ -789,7 +792,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
),
expectPush(
user.NewUserDeactivatedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
@@ -827,6 +830,7 @@ func TestCommandSide_DeactivateUserV2(t *testing.T) {
}
func TestCommandSide_ReactivateUserV2(t *testing.T) {
+ userAgg := &user.NewAggregate("user1", "org1").Aggregate
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
@@ -888,7 +892,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -921,7 +925,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"name",
"description",
@@ -950,7 +954,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -964,12 +968,12 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewUserDeactivatedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate),
+ userAgg),
),
),
expectPush(
user.NewUserReactivatedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
@@ -992,7 +996,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -1006,7 +1010,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewUserDeactivatedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate),
+ userAgg),
),
),
),
@@ -1029,7 +1033,7 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"name",
"description",
@@ -1039,12 +1043,12 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewUserDeactivatedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate),
+ userAgg),
),
),
expectPush(
user.NewUserReactivatedEvent(context.Background(),
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
@@ -1084,6 +1088,8 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) {
func TestCommandSide_RemoveUserV2(t *testing.T) {
ctxUserID := "ctxUserID"
ctx := authz.SetCtxData(context.Background(), authz.CtxData{UserID: ctxUserID})
+ userAgg := &user.NewAggregate("user1", "org1").Aggregate
+ orgAgg := &org.NewAggregate("org1").Aggregate
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
@@ -1144,7 +1150,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(ctx,
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -1158,7 +1164,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewUserRemovedEvent(ctx,
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
nil,
true,
@@ -1184,7 +1190,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(ctx,
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -1199,17 +1205,18 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
),
expectFilter(
eventFromEventPusher(
- org.NewDomainPolicyAddedEvent(ctx,
- &user.NewAggregate("user1", "org1").Aggregate,
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ orgAgg,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewUserRemovedEvent(ctx,
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
nil,
true,
@@ -1234,7 +1241,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(ctx,
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"firstname",
"lastname",
@@ -1248,7 +1255,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewHumanInitializedCheckSucceededEvent(ctx,
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
),
),
),
@@ -1269,7 +1276,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(ctx,
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"name",
"description",
@@ -1279,7 +1286,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
),
eventFromEventPusher(
user.NewUserRemovedEvent(ctx,
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
nil,
true,
@@ -1304,7 +1311,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(ctx,
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
"name",
"description",
@@ -1315,17 +1322,18 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
),
expectFilter(
eventFromEventPusher(
- org.NewDomainPolicyAddedEvent(ctx,
- &user.NewAggregate("user1", "org1").Aggregate,
+ org.NewDomainPolicyAddedEvent(context.Background(),
+ orgAgg,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewUserRemovedEvent(ctx,
- &user.NewAggregate("user1", "org1").Aggregate,
+ userAgg,
"username",
nil,
true,
@@ -1366,13 +1374,14 @@ func TestCommandSide_RemoveUserV2(t *testing.T) {
expectFilter(
eventFromEventPusher(
org.NewDomainPolicyAddedEvent(ctx,
- &user.NewAggregate(ctxUserID, "org1").Aggregate,
+ orgAgg,
true,
true,
true,
),
),
),
+ expectFilterOrganizationSettings("org1", false, false),
expectPush(
user.NewUserRemovedEvent(ctx,
&user.NewAggregate(ctxUserID, "org1").Aggregate,
diff --git a/internal/command/user_v2_username.go b/internal/command/user_v2_username.go
index a9132aceb9..cfec4f3b0f 100644
--- a/internal/command/user_v2_username.go
+++ b/internal/command/user_v2_username.go
@@ -18,10 +18,21 @@ func (c *Commands) changeUsername(ctx context.Context, cmds []eventstore.Command
if err != nil {
return cmds, zerrors.ThrowPreconditionFailed(err, "COMMAND-79pv6e1q62", "Errors.Org.DomainPolicy.NotExisting")
}
+
+ organizationScopedUsername, err := c.checkOrganizationScopedUsernames(ctx, orgID)
+ if err != nil {
+ return cmds, err
+ }
+
if err = c.userValidateDomain(ctx, orgID, userName, domainPolicy.UserLoginMustBeDomain); err != nil {
return cmds, err
}
return append(cmds,
- user.NewUsernameChangedEvent(ctx, &wm.Aggregate().Aggregate, wm.UserName, userName, domainPolicy.UserLoginMustBeDomain),
+ user.NewUsernameChangedEvent(ctx, &wm.Aggregate().Aggregate,
+ wm.UserName,
+ userName,
+ domainPolicy.UserLoginMustBeDomain,
+ organizationScopedUsername,
+ ),
), nil
}
diff --git a/internal/domain/organization_settings.go b/internal/domain/organization_settings.go
new file mode 100644
index 0000000000..3d9ed819fe
--- /dev/null
+++ b/internal/domain/organization_settings.go
@@ -0,0 +1,19 @@
+package domain
+
+type OrganizationSettingsState int32
+
+const (
+ OrganizationSettingsStateUnspecified OrganizationSettingsState = iota
+ OrganizationSettingsStateActive
+ OrganizationSettingsStateRemoved
+
+ organizationSettingsStateCount
+)
+
+func (c OrganizationSettingsState) Valid() bool {
+ return c >= 0 && c < organizationSettingsStateCount
+}
+
+func (s OrganizationSettingsState) Exists() bool {
+ return s.Valid() && s != OrganizationSettingsStateUnspecified && s != OrganizationSettingsStateRemoved
+}
diff --git a/internal/domain/permission.go b/internal/domain/permission.go
index 1405991dae..39738b1e84 100644
--- a/internal/domain/permission.go
+++ b/internal/domain/permission.go
@@ -65,6 +65,9 @@ const (
PermissionUserGrantWrite = "user.grant.write"
PermissionUserGrantRead = "user.grant.read"
PermissionUserGrantDelete = "user.grant.delete"
+ PermissionIAMPolicyWrite = "iam.policy.write"
+ PermissionIAMPolicyDelete = "iam.policy.delete"
+ PermissionPolicyRead = "policy.read"
)
// ProjectPermissionCheck is used as a check for preconditions dependent on application, project, user resourceowner and usergrants.
diff --git a/internal/eventstore/handler/v2/handler.go b/internal/eventstore/handler/v2/handler.go
index 7a45a5f598..2f59aeed62 100644
--- a/internal/eventstore/handler/v2/handler.go
+++ b/internal/eventstore/handler/v2/handler.go
@@ -646,7 +646,7 @@ func (h *Handler) executeStatements(ctx context.Context, tx *sql.Tx, statements
for i, statement := range statements {
select {
case <-ctx.Done():
- return lastProcessedIndex, nil
+ return lastProcessedIndex, ctx.Err()
default:
err := h.executeStatement(ctx, tx, statement)
if err != nil {
diff --git a/internal/integration/client.go b/internal/integration/client.go
index a89e4fa621..d4e57d06d0 100644
--- a/internal/integration/client.go
+++ b/internal/integration/client.go
@@ -405,6 +405,27 @@ func (i *Instance) CreateOrganizationWithUserID(ctx context.Context, name, userI
return resp
}
+func (i *Instance) SetOrganizationSettings(ctx context.Context, t *testing.T, orgID string, organizationScopedUsernames bool) *settings_v2beta.SetOrganizationSettingsResponse {
+ resp, err := i.Client.SettingsV2beta.SetOrganizationSettings(ctx,
+ &settings_v2beta.SetOrganizationSettingsRequest{
+ OrganizationId: orgID,
+ OrganizationScopedUsernames: gu.Ptr(organizationScopedUsernames),
+ },
+ )
+ require.NoError(t, err)
+ return resp
+}
+
+func (i *Instance) DeleteOrganizationSettings(ctx context.Context, t *testing.T, orgID string) *settings_v2beta.DeleteOrganizationSettingsResponse {
+ resp, err := i.Client.SettingsV2beta.DeleteOrganizationSettings(ctx,
+ &settings_v2beta.DeleteOrganizationSettingsRequest{
+ OrganizationId: orgID,
+ },
+ )
+ require.NoError(t, err)
+ return resp
+}
+
func (i *Instance) CreateHumanUserVerified(ctx context.Context, org, email, phone string) *user_v2.AddHumanUserResponse {
resp, err := i.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
Organization: &object.Organization{
diff --git a/internal/query/administrators.go b/internal/query/administrators.go
index 9ab81993ef..0128c0f25e 100644
--- a/internal/query/administrators.go
+++ b/internal/query/administrators.go
@@ -165,12 +165,13 @@ func administratorProjectGrantCheckPermission(ctx context.Context, resourceOwner
}
func (q *Queries) SearchAdministrators(ctx context.Context, queries *MembershipSearchQuery, permissionCheck domain.PermissionCheck) (*Administrators, error) {
- permissionCheckV2 := PermissionV2(ctx, permissionCheck)
- admins, err := q.searchAdministrators(ctx, queries, permissionCheckV2)
+ // removed as permission v2 is not implemented yet for project grant level permissions
+ // permissionCheckV2 := PermissionV2(ctx, permissionCheck)
+ admins, err := q.searchAdministrators(ctx, queries, false)
if err != nil {
return nil, err
}
- if permissionCheck != nil && !authz.GetFeatures(ctx).PermissionCheckV2 {
+ if permissionCheck != nil { // && !authz.GetFeatures(ctx).PermissionCheckV2 {
administratorsCheckPermission(ctx, admins, permissionCheck)
}
return admins, nil
@@ -184,7 +185,7 @@ func (q *Queries) searchAdministrators(ctx context.Context, queries *MembershipS
eq := sq.Eq{membershipInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
if err != nil {
- return nil, zerrors.ThrowInvalidArgument(err, "QUERY-TODO", "Errors.Query.InvalidRequest")
+ return nil, zerrors.ThrowInvalidArgument(err, "QUERY-xhEnpLFNpJ", "Errors.Query.InvalidRequest")
}
latestState, err := q.latestState(ctx, orgMemberTable, instanceMemberTable, projectMemberTable, projectGrantMemberTable)
if err != nil {
@@ -334,7 +335,7 @@ func prepareAdministratorsQuery(ctx context.Context, queries *MembershipSearchQu
}
if err := rows.Close(); err != nil {
- return nil, zerrors.ThrowInternal(err, "QUERY-TODO", "Errors.Query.CloseRows")
+ return nil, zerrors.ThrowInternal(err, "QUERY-ajYcn0eK7f", "Errors.Query.CloseRows")
}
return &Administrators{
diff --git a/internal/query/organization_settings.go b/internal/query/organization_settings.go
new file mode 100644
index 0000000000..2670fb4f8b
--- /dev/null
+++ b/internal/query/organization_settings.go
@@ -0,0 +1,196 @@
+package query
+
+import (
+ "context"
+ "database/sql"
+ "slices"
+ "time"
+
+ sq "github.com/Masterminds/squirrel"
+
+ "github.com/zitadel/zitadel/internal/api/authz"
+ "github.com/zitadel/zitadel/internal/domain"
+ "github.com/zitadel/zitadel/internal/query/projection"
+ "github.com/zitadel/zitadel/internal/telemetry/tracing"
+ "github.com/zitadel/zitadel/internal/zerrors"
+)
+
+var (
+ organizationSettingsTable = table{
+ name: projection.OrganizationSettingsTable,
+ instanceIDCol: projection.OrganizationSettingsInstanceIDCol,
+ }
+ OrganizationSettingsColumnID = Column{
+ name: projection.OrganizationSettingsIDCol,
+ table: organizationSettingsTable,
+ }
+ OrganizationSettingsColumnCreationDate = Column{
+ name: projection.OrganizationSettingsCreationDateCol,
+ table: organizationSettingsTable,
+ }
+ OrganizationSettingsColumnChangeDate = Column{
+ name: projection.OrganizationSettingsChangeDateCol,
+ table: organizationSettingsTable,
+ }
+ OrganizationSettingsColumnResourceOwner = Column{
+ name: projection.OrganizationSettingsResourceOwnerCol,
+ table: organizationSettingsTable,
+ }
+ OrganizationSettingsColumnInstanceID = Column{
+ name: projection.OrganizationSettingsInstanceIDCol,
+ table: organizationSettingsTable,
+ }
+ OrganizationSettingsColumnSequence = Column{
+ name: projection.OrganizationSettingsSequenceCol,
+ table: organizationSettingsTable,
+ }
+ OrganizationSettingsColumnOrganizationScopedUsernames = Column{
+ name: projection.OrganizationSettingsOrganizationScopedUsernamesCol,
+ table: organizationSettingsTable,
+ }
+)
+
+type OrganizationSettingsList struct {
+ SearchResponse
+ OrganizationSettingsList []*OrganizationSettings
+}
+
+func organizationSettingsListCheckPermission(ctx context.Context, organizationSettingsList *OrganizationSettingsList, permissionCheck domain.PermissionCheck) {
+ organizationSettingsList.OrganizationSettingsList = slices.DeleteFunc(organizationSettingsList.OrganizationSettingsList,
+ func(organizationSettings *OrganizationSettings) bool {
+ return organizationSettingsCheckPermission(ctx, organizationSettings.ResourceOwner, organizationSettings.ID, permissionCheck) != nil
+ },
+ )
+}
+
+func organizationSettingsCheckPermission(ctx context.Context, resourceOwner string, id string, permissionCheck domain.PermissionCheck) error {
+ return permissionCheck(ctx, domain.PermissionPolicyRead, resourceOwner, id)
+}
+
+type OrganizationSettings struct {
+ ID string
+ CreationDate time.Time
+ ChangeDate time.Time
+ ResourceOwner string
+ Sequence uint64
+
+ OrganizationScopedUsernames bool
+}
+
+type OrganizationSettingsSearchQueries struct {
+ SearchRequest
+ Queries []SearchQuery
+}
+
+func (q *OrganizationSettingsSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
+ query = q.SearchRequest.toQuery(query)
+ for _, q := range q.Queries {
+ query = q.toQuery(query)
+ }
+ return query
+}
+
+func organizationSettingsPermissionCheckV2(ctx context.Context, query sq.SelectBuilder, enabled bool, queries *OrganizationSettingsSearchQueries) sq.SelectBuilder {
+ if !enabled {
+ return query
+ }
+ join, args := PermissionClause(
+ ctx,
+ OrganizationSettingsColumnID,
+ domain.PermissionPolicyRead,
+ SingleOrgPermissionOption(queries.Queries),
+ )
+ return query.JoinClause(join, args...)
+}
+
+func (q *Queries) SearchOrganizationSettings(ctx context.Context, queries *OrganizationSettingsSearchQueries, permissionCheck domain.PermissionCheck) (*OrganizationSettingsList, error) {
+ permissionCheckV2 := PermissionV2(ctx, permissionCheck)
+ settings, err := q.searchOrganizationSettings(ctx, queries, permissionCheckV2)
+ if err != nil {
+ return nil, err
+ }
+ if permissionCheck != nil && !authz.GetFeatures(ctx).PermissionCheckV2 {
+ organizationSettingsListCheckPermission(ctx, settings, permissionCheck)
+ }
+ return settings, nil
+}
+
+func (q *Queries) searchOrganizationSettings(ctx context.Context, queries *OrganizationSettingsSearchQueries, permissionCheckV2 bool) (settingsList *OrganizationSettingsList, err error) {
+ ctx, span := tracing.NewSpan(ctx)
+ defer func() { span.EndWithError(err) }()
+
+ query, scan := prepareOrganizationSettingsListQuery()
+ query = organizationSettingsPermissionCheckV2(ctx, query, permissionCheckV2, queries)
+ eq := sq.Eq{OrganizationSettingsColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
+ stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
+ if err != nil {
+ return nil, zerrors.ThrowInvalidArgument(err, "QUERY-qNPeOXlMwj", "Errors.Query.InvalidRequest")
+ }
+
+ err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
+ settingsList, err = scan(rows)
+ return err
+ }, stmt, args...)
+ if err != nil {
+ return nil, err
+ }
+ return settingsList, nil
+}
+
+func NewOrganizationSettingsOrganizationIDSearchQuery(ids []string) (SearchQuery, error) {
+ list := make([]interface{}, len(ids))
+ for i, value := range ids {
+ list[i] = value
+ }
+ return NewListQuery(OrganizationSettingsColumnID, list, ListIn)
+}
+
+func NewOrganizationSettingsOrganizationScopedUsernamesSearchQuery(organizationScopedUsernames bool) (SearchQuery, error) {
+ return NewBoolQuery(OrganizationSettingsColumnOrganizationScopedUsernames, organizationScopedUsernames)
+}
+
+func prepareOrganizationSettingsListQuery() (sq.SelectBuilder, func(*sql.Rows) (*OrganizationSettingsList, error)) {
+ return sq.Select(
+ OrganizationSettingsColumnID.identifier(),
+ OrganizationSettingsColumnCreationDate.identifier(),
+ OrganizationSettingsColumnChangeDate.identifier(),
+ OrganizationSettingsColumnResourceOwner.identifier(),
+ OrganizationSettingsColumnSequence.identifier(),
+ OrganizationSettingsColumnOrganizationScopedUsernames.identifier(),
+ countColumn.identifier(),
+ ).From(organizationSettingsTable.identifier()).
+ PlaceholderFormat(sq.Dollar),
+ func(rows *sql.Rows) (*OrganizationSettingsList, error) {
+ settingsList := make([]*OrganizationSettings, 0)
+ var (
+ count uint64
+ )
+ for rows.Next() {
+ settings := new(OrganizationSettings)
+ err := rows.Scan(
+ &settings.ID,
+ &settings.CreationDate,
+ &settings.ChangeDate,
+ &settings.ResourceOwner,
+ &settings.Sequence,
+ &settings.OrganizationScopedUsernames,
+ &count,
+ )
+ if err != nil {
+ return nil, err
+ }
+ settingsList = append(settingsList, settings)
+ }
+
+ if err := rows.Close(); err != nil {
+ return nil, zerrors.ThrowInternal(err, "QUERY-mmC1K0t5Fq", "Errors.Query.CloseRows")
+ }
+
+ return &OrganizationSettingsList{
+ OrganizationSettingsList: settingsList,
+ SearchResponse: SearchResponse{
+ Count: count,
+ },
+ }, nil
+ }
+}
diff --git a/internal/query/organization_settings_test.go b/internal/query/organization_settings_test.go
new file mode 100644
index 0000000000..7ee370c548
--- /dev/null
+++ b/internal/query/organization_settings_test.go
@@ -0,0 +1,180 @@
+package query
+
+import (
+ "database/sql"
+ "database/sql/driver"
+ "errors"
+ "fmt"
+ "regexp"
+ "testing"
+)
+
+var (
+ prepareOrganizationSettingsListStmt = `SELECT projections.organization_settings.id,` +
+ ` projections.organization_settings.creation_date,` +
+ ` projections.organization_settings.change_date,` +
+ ` projections.organization_settings.resource_owner,` +
+ ` projections.organization_settings.sequence,` +
+ ` projections.organization_settings.organization_scoped_usernames,` +
+ ` COUNT(*) OVER ()` +
+ ` FROM projections.organization_settings`
+ prepareOrganizationSettingsListCols = []string{
+ "id",
+ "creation_date",
+ "change_date",
+ "resource_owner",
+ "sequence",
+ "organization_scoped_usernames",
+ "count",
+ }
+)
+
+func Test_OrganizationSettingsListPrepares(t *testing.T) {
+ type want struct {
+ sqlExpectations sqlExpectation
+ err checkErr
+ }
+ tests := []struct {
+ name string
+ prepare interface{}
+ want want
+ object interface{}
+ }{
+ {
+ name: "prepareOrganizationSettingsListQuery no result",
+ prepare: prepareOrganizationSettingsListQuery,
+ want: want{
+ sqlExpectations: mockQueries(
+ regexp.QuoteMeta(prepareOrganizationSettingsListStmt),
+ nil,
+ nil,
+ ),
+ },
+ object: &OrganizationSettingsList{OrganizationSettingsList: []*OrganizationSettings{}},
+ },
+ {
+ name: "prepareOrganizationSettingsListQuery one result",
+ prepare: prepareOrganizationSettingsListQuery,
+ want: want{
+ sqlExpectations: mockQueries(
+ regexp.QuoteMeta(prepareOrganizationSettingsListStmt),
+ prepareOrganizationSettingsListCols,
+ [][]driver.Value{
+ {
+ "id",
+ testNow,
+ testNow,
+ "ro",
+ uint64(20211108),
+ true,
+ },
+ },
+ ),
+ },
+ object: &OrganizationSettingsList{
+ SearchResponse: SearchResponse{
+ Count: 1,
+ },
+ OrganizationSettingsList: []*OrganizationSettings{
+ {
+ ID: "id",
+ CreationDate: testNow,
+ ChangeDate: testNow,
+ ResourceOwner: "ro",
+ Sequence: 20211108,
+ OrganizationScopedUsernames: true,
+ },
+ },
+ },
+ },
+ {
+ name: "prepareOrganizationSettingsListQuery multiple result",
+ prepare: prepareOrganizationSettingsListQuery,
+ want: want{
+ sqlExpectations: mockQueries(
+ regexp.QuoteMeta(prepareOrganizationSettingsListStmt),
+ prepareOrganizationSettingsListCols,
+ [][]driver.Value{
+ {
+ "id-1",
+ testNow,
+ testNow,
+ "ro",
+ uint64(20211108),
+ true,
+ },
+ {
+ "id-2",
+ testNow,
+ testNow,
+ "ro",
+ uint64(20211108),
+ false,
+ },
+ {
+ "id-3",
+ testNow,
+ testNow,
+ "ro",
+ uint64(20211108),
+ true,
+ },
+ },
+ ),
+ },
+ object: &OrganizationSettingsList{
+ SearchResponse: SearchResponse{
+ Count: 3,
+ },
+ OrganizationSettingsList: []*OrganizationSettings{
+ {
+ ID: "id-1",
+ CreationDate: testNow,
+ ChangeDate: testNow,
+ ResourceOwner: "ro",
+ Sequence: 20211108,
+ OrganizationScopedUsernames: true,
+ },
+ {
+ ID: "id-2",
+ CreationDate: testNow,
+ ChangeDate: testNow,
+ ResourceOwner: "ro",
+ Sequence: 20211108,
+ OrganizationScopedUsernames: false,
+ },
+ {
+ ID: "id-3",
+ CreationDate: testNow,
+ ChangeDate: testNow,
+ ResourceOwner: "ro",
+ Sequence: 20211108,
+ OrganizationScopedUsernames: true,
+ },
+ },
+ },
+ },
+ {
+ name: "prepareOrganizationSettingsListQuery sql err",
+ prepare: prepareOrganizationSettingsListQuery,
+ want: want{
+ sqlExpectations: mockQueryErr(
+ regexp.QuoteMeta(prepareOrganizationSettingsListStmt),
+ sql.ErrConnDone,
+ ),
+ err: func(err error) (error, bool) {
+ if !errors.Is(err, sql.ErrConnDone) {
+ return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
+ }
+ return nil, true
+ },
+ },
+ object: (*OrganizationSettingsList)(nil),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err)
+ })
+ }
+}
diff --git a/internal/query/project.go b/internal/query/project.go
index 59e2dd95c0..23c70b9ab8 100644
--- a/internal/query/project.go
+++ b/internal/query/project.go
@@ -282,12 +282,13 @@ func projectPermissionCheckV2(ctx context.Context, query sq.SelectBuilder, enabl
}
func (q *Queries) SearchGrantedProjects(ctx context.Context, queries *ProjectAndGrantedProjectSearchQueries, permissionCheck domain.PermissionCheck) (*GrantedProjects, error) {
- permissionCheckV2 := PermissionV2(ctx, permissionCheck)
- projects, err := q.searchGrantedProjects(ctx, queries, permissionCheckV2)
+ // removed as permission v2 is not implemented yet for project grant level permissions
+ // permissionCheckV2 := PermissionV2(ctx, permissionCheck)
+ projects, err := q.searchGrantedProjects(ctx, queries, false)
if err != nil {
return nil, err
}
- if permissionCheck != nil && !authz.GetFeatures(ctx).PermissionCheckV2 {
+ if permissionCheck != nil { // && !authz.GetFeatures(ctx).PermissionCheckV2 {
grantedProjectsCheckPermission(ctx, projects, permissionCheck)
}
return projects, nil
diff --git a/internal/query/project_grant.go b/internal/query/project_grant.go
index 1931cad0f5..17e40e01eb 100644
--- a/internal/query/project_grant.go
+++ b/internal/query/project_grant.go
@@ -200,12 +200,13 @@ func (q *Queries) ProjectGrantByIDAndGrantedOrg(ctx context.Context, id, granted
}
func (q *Queries) SearchProjectGrants(ctx context.Context, queries *ProjectGrantSearchQueries, permissionCheck domain.PermissionCheck) (grants *ProjectGrants, err error) {
- permissionCheckV2 := PermissionV2(ctx, permissionCheck)
- projectsGrants, err := q.searchProjectGrants(ctx, queries, permissionCheckV2)
+ // removed as permission v2 is not implemented yet for project grant level permissions
+ // permissionCheckV2 := PermissionV2(ctx, permissionCheck)
+ projectsGrants, err := q.searchProjectGrants(ctx, queries, false)
if err != nil {
return nil, err
}
- if permissionCheck != nil && !authz.GetFeatures(ctx).PermissionCheckV2 {
+ if permissionCheck != nil { // && !authz.GetFeatures(ctx).PermissionCheckV2 {
projectGrantsCheckPermission(ctx, projectsGrants, permissionCheck)
}
return projectsGrants, nil
diff --git a/internal/query/projection/organization_settings.go b/internal/query/projection/organization_settings.go
new file mode 100644
index 0000000000..c97bb2691a
--- /dev/null
+++ b/internal/query/projection/organization_settings.go
@@ -0,0 +1,141 @@
+package projection
+
+import (
+ "context"
+
+ "github.com/zitadel/zitadel/internal/eventstore"
+ old_handler "github.com/zitadel/zitadel/internal/eventstore/handler"
+ "github.com/zitadel/zitadel/internal/eventstore/handler/v2"
+ "github.com/zitadel/zitadel/internal/repository/instance"
+ "github.com/zitadel/zitadel/internal/repository/org"
+ settings "github.com/zitadel/zitadel/internal/repository/organization_settings"
+)
+
+const (
+ OrganizationSettingsTable = "projections.organization_settings"
+ OrganizationSettingsIDCol = "id"
+ OrganizationSettingsCreationDateCol = "creation_date"
+ OrganizationSettingsChangeDateCol = "change_date"
+ OrganizationSettingsResourceOwnerCol = "resource_owner"
+ OrganizationSettingsInstanceIDCol = "instance_id"
+ OrganizationSettingsSequenceCol = "sequence"
+ OrganizationSettingsOrganizationScopedUsernamesCol = "organization_scoped_usernames"
+)
+
+type organizationSettingsProjection struct{}
+
+func newOrganizationSettingsProjection(ctx context.Context, config handler.Config) *handler.Handler {
+ return handler.NewHandler(ctx, &config, new(organizationSettingsProjection))
+}
+
+func (*organizationSettingsProjection) Name() string {
+ return OrganizationSettingsTable
+}
+
+func (*organizationSettingsProjection) Init() *old_handler.Check {
+ return handler.NewTableCheck(
+ handler.NewTable([]*handler.InitColumn{
+ handler.NewColumn(OrganizationSettingsIDCol, handler.ColumnTypeText),
+ handler.NewColumn(OrganizationSettingsCreationDateCol, handler.ColumnTypeTimestamp),
+ handler.NewColumn(OrganizationSettingsChangeDateCol, handler.ColumnTypeTimestamp),
+ handler.NewColumn(OrganizationSettingsResourceOwnerCol, handler.ColumnTypeText),
+ handler.NewColumn(OrganizationSettingsInstanceIDCol, handler.ColumnTypeText),
+ handler.NewColumn(OrganizationSettingsSequenceCol, handler.ColumnTypeInt64),
+ handler.NewColumn(OrganizationSettingsOrganizationScopedUsernamesCol, handler.ColumnTypeBool),
+ },
+ handler.NewPrimaryKey(OrganizationSettingsInstanceIDCol, OrganizationSettingsResourceOwnerCol, OrganizationSettingsIDCol),
+ handler.WithIndex(handler.NewIndex("resource_owner", []string{OrganizationSettingsResourceOwnerCol})),
+ ),
+ )
+}
+
+func (p *organizationSettingsProjection) Reducers() []handler.AggregateReducer {
+ return []handler.AggregateReducer{
+ {
+ Aggregate: settings.AggregateType,
+ EventReducers: []handler.EventReducer{
+ {
+ Event: settings.OrganizationSettingsSetEventType,
+ Reduce: p.reduceOrganizationSettingsSet,
+ },
+ {
+ Event: settings.OrganizationSettingsRemovedEventType,
+ Reduce: p.reduceOrganizationSettingsRemoved,
+ },
+ },
+ },
+ {
+ Aggregate: org.AggregateType,
+ EventReducers: []handler.EventReducer{
+ {
+ Event: org.OrgRemovedEventType,
+ Reduce: p.reduceOrgRemoved,
+ },
+ },
+ },
+ {
+ Aggregate: instance.AggregateType,
+ EventReducers: []handler.EventReducer{
+ {
+ Event: instance.InstanceRemovedEventType,
+ Reduce: reduceInstanceRemovedHelper(OrganizationSettingsInstanceIDCol),
+ },
+ },
+ },
+ }
+}
+
+func (p *organizationSettingsProjection) reduceOrganizationSettingsSet(event eventstore.Event) (*handler.Statement, error) {
+ e, err := assertEvent[*settings.OrganizationSettingsSetEvent](event)
+ if err != nil {
+ return nil, err
+ }
+
+ return handler.NewUpsertStatement(e,
+ []handler.Column{
+ handler.NewCol(OrganizationSettingsInstanceIDCol, e.Aggregate().InstanceID),
+ handler.NewCol(OrganizationSettingsResourceOwnerCol, e.Aggregate().ResourceOwner),
+ handler.NewCol(OrganizationSettingsIDCol, e.Aggregate().ID),
+ },
+ []handler.Column{
+ handler.NewCol(OrganizationSettingsInstanceIDCol, e.Aggregate().InstanceID),
+ handler.NewCol(OrganizationSettingsResourceOwnerCol, e.Aggregate().ResourceOwner),
+ handler.NewCol(OrganizationSettingsIDCol, e.Aggregate().ID),
+ handler.NewCol(OrganizationSettingsCreationDateCol, handler.OnlySetValueOnInsert(OrganizationSettingsTable, e.CreationDate())),
+ handler.NewCol(OrganizationSettingsChangeDateCol, e.CreationDate()),
+ handler.NewCol(OrganizationSettingsSequenceCol, e.Sequence()),
+ handler.NewCol(OrganizationSettingsOrganizationScopedUsernamesCol, e.OrganizationScopedUsernames),
+ },
+ ), nil
+}
+
+func (p *organizationSettingsProjection) reduceOrganizationSettingsRemoved(event eventstore.Event) (*handler.Statement, error) {
+ e, err := assertEvent[*settings.OrganizationSettingsRemovedEvent](event)
+ if err != nil {
+ return nil, err
+ }
+
+ return handler.NewDeleteStatement(e,
+ []handler.Condition{
+ handler.NewCond(OrganizationSettingsInstanceIDCol, e.Aggregate().InstanceID),
+ handler.NewCond(OrganizationSettingsResourceOwnerCol, e.Aggregate().ResourceOwner),
+ handler.NewCond(OrganizationSettingsIDCol, e.Aggregate().ID),
+ },
+ ), nil
+}
+
+func (p *organizationSettingsProjection) reduceOrgRemoved(event eventstore.Event) (*handler.Statement, error) {
+ e, err := assertEvent[*org.OrgRemovedEvent](event)
+ if err != nil {
+ return nil, err
+ }
+
+ return handler.NewDeleteStatement(
+ e,
+ []handler.Condition{
+ handler.NewCond(OrganizationSettingsInstanceIDCol, e.Aggregate().InstanceID),
+ handler.NewCond(OrganizationSettingsResourceOwnerCol, e.Aggregate().ResourceOwner),
+ handler.NewCond(OrganizationSettingsIDCol, e.Aggregate().ID),
+ },
+ ), nil
+}
diff --git a/internal/query/projection/organization_settings_test.go b/internal/query/projection/organization_settings_test.go
new file mode 100644
index 0000000000..e69e42c71e
--- /dev/null
+++ b/internal/query/projection/organization_settings_test.go
@@ -0,0 +1,154 @@
+package projection
+
+import (
+ "testing"
+
+ "github.com/zitadel/zitadel/internal/eventstore"
+ "github.com/zitadel/zitadel/internal/eventstore/handler/v2"
+ "github.com/zitadel/zitadel/internal/repository/instance"
+ "github.com/zitadel/zitadel/internal/repository/org"
+ settings "github.com/zitadel/zitadel/internal/repository/organization_settings"
+ "github.com/zitadel/zitadel/internal/zerrors"
+)
+
+func TestOrganizationSettingsProjection_reduces(t *testing.T) {
+ type args struct {
+ event func(t *testing.T) eventstore.Event
+ }
+ tests := []struct {
+ name string
+ args args
+ reduce func(event eventstore.Event) (*handler.Statement, error)
+ want wantReduce
+ }{
+ {
+ name: "reduce organization settings set",
+ args: args{
+ event: getEvent(
+ testEvent(
+ settings.OrganizationSettingsSetEventType,
+ settings.AggregateType,
+ []byte(`{"organizationScopedUsernames": true}`),
+ ), eventstore.GenericEventMapper[settings.OrganizationSettingsSetEvent],
+ ),
+ },
+ reduce: (&organizationSettingsProjection{}).reduceOrganizationSettingsSet,
+ want: wantReduce{
+ aggregateType: eventstore.AggregateType("organization_settings"),
+ sequence: 15,
+ executer: &testExecuter{
+ executions: []execution{
+ {
+ expectedStmt: "INSERT INTO projections.organization_settings (instance_id, resource_owner, id, creation_date, change_date, sequence, organization_scoped_usernames) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (instance_id, resource_owner, id) DO UPDATE SET (creation_date, change_date, sequence, organization_scoped_usernames) = (projections.organization_settings.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.organization_scoped_usernames)",
+ expectedArgs: []interface{}{
+ "instance-id",
+ "ro-id",
+ "agg-id",
+ anyArg{},
+ anyArg{},
+ uint64(15),
+ true,
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "reduce organization settings removed",
+ args: args{
+ event: getEvent(
+ testEvent(
+ settings.OrganizationSettingsRemovedEventType,
+ settings.AggregateType,
+ []byte(`{}`),
+ ), eventstore.GenericEventMapper[settings.OrganizationSettingsRemovedEvent],
+ ),
+ },
+ reduce: (&organizationSettingsProjection{}).reduceOrganizationSettingsRemoved,
+ want: wantReduce{
+ aggregateType: eventstore.AggregateType("organization_settings"),
+ sequence: 15,
+ executer: &testExecuter{
+ executions: []execution{
+ {
+ expectedStmt: "DELETE FROM projections.organization_settings WHERE (instance_id = $1) AND (resource_owner = $2) AND (id = $3)",
+ expectedArgs: []interface{}{
+ "instance-id",
+ "ro-id",
+ "agg-id",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "reduceOrgRemoved",
+ args: args{
+ event: getEvent(
+ testEvent(
+ org.OrgRemovedEventType,
+ org.AggregateType,
+ nil,
+ ), org.OrgRemovedEventMapper),
+ },
+ reduce: (&organizationSettingsProjection{}).reduceOrgRemoved,
+ want: wantReduce{
+ aggregateType: eventstore.AggregateType("org"),
+ sequence: 15,
+ executer: &testExecuter{
+ executions: []execution{
+ {
+ expectedStmt: "DELETE FROM projections.organization_settings WHERE (instance_id = $1) AND (resource_owner = $2) AND (id = $3)",
+ expectedArgs: []interface{}{
+ "instance-id",
+ "ro-id",
+ "agg-id",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "instance reduceInstanceRemoved",
+ args: args{
+ event: getEvent(
+ testEvent(
+ instance.InstanceRemovedEventType,
+ instance.AggregateType,
+ nil,
+ ), instance.InstanceRemovedEventMapper),
+ },
+ reduce: reduceInstanceRemovedHelper(OrganizationSettingsInstanceIDCol),
+ want: wantReduce{
+ aggregateType: eventstore.AggregateType("instance"),
+ sequence: 15,
+ executer: &testExecuter{
+ executions: []execution{
+ {
+ expectedStmt: "DELETE FROM projections.organization_settings WHERE (instance_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 := zerrors.IsErrorInvalidArgument(err); !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, OrganizationSettingsTable, tt.want)
+ })
+ }
+}
diff --git a/internal/query/projection/projection.go b/internal/query/projection/projection.go
index 2e7401332f..bc70269e15 100644
--- a/internal/query/projection/projection.go
+++ b/internal/query/projection/projection.go
@@ -87,6 +87,7 @@ var (
WebKeyProjection *handler.Handler
DebugEventsProjection *handler.Handler
HostedLoginTranslationProjection *handler.Handler
+ OrganizationSettingsProjection *handler.Handler
InstanceRelationalProjection *handler.Handler
OrganizationRelationalProjection *handler.Handler
@@ -186,6 +187,7 @@ func Create(ctx context.Context, sqlClient *database.DB, es handler.EventStore,
WebKeyProjection = newWebKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["web_keys"]))
DebugEventsProjection = newDebugEventsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["debug_events"]))
HostedLoginTranslationProjection = newHostedLoginTranslationProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["hosted_login_translation"]))
+ OrganizationSettingsProjection = newOrganizationSettingsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["organization_settings"]))
ProjectGrantFields = newFillProjectGrantFields(applyCustomConfig(projectionConfig, config.Customizations[fieldsProjectGrant]))
OrgDomainVerifiedFields = newFillOrgDomainVerifiedFields(applyCustomConfig(projectionConfig, config.Customizations[fieldsOrgDomainVerified]))
@@ -370,6 +372,7 @@ func newProjectionsList() {
WebKeyProjection,
DebugEventsProjection,
HostedLoginTranslationProjection,
+ OrganizationSettingsProjection,
InstanceRelationalProjection,
OrganizationRelationalProjection,
diff --git a/internal/query/user_grant.go b/internal/query/user_grant.go
index 212972ea8c..da38d48821 100644
--- a/internal/query/user_grant.go
+++ b/internal/query/user_grant.go
@@ -305,12 +305,13 @@ func (q *Queries) UserGrant(ctx context.Context, shouldTriggerBulk bool, queries
}
func (q *Queries) UserGrants(ctx context.Context, queries *UserGrantsQueries, shouldTriggerBulk bool, permissionCheck domain.PermissionCheck) (*UserGrants, error) {
- permissionCheckV2 := PermissionV2(ctx, permissionCheck)
- grants, err := q.userGrants(ctx, queries, shouldTriggerBulk, permissionCheckV2)
+ // removed as permission v2 is not implemented yet for project grant level permissions
+ // permissionCheckV2 := PermissionV2(ctx, permissionCheck)
+ grants, err := q.userGrants(ctx, queries, shouldTriggerBulk, false)
if err != nil {
return nil, err
}
- if permissionCheck != nil && !authz.GetFeatures(ctx).PermissionCheckV2 {
+ if permissionCheck != nil { // && !authz.GetFeatures(ctx).PermissionCheckV2 {
userGrantsCheckPermission(ctx, grants, permissionCheck)
}
return grants, nil
diff --git a/internal/queue/database.go b/internal/queue/database.go
index c5eb0b8ca3..93bf5abb9f 100644
--- a/internal/queue/database.go
+++ b/internal/queue/database.go
@@ -1,45 +1,6 @@
package queue
-import (
- "context"
- "sync"
-
- "github.com/jackc/pgx/v5"
-
- "github.com/zitadel/zitadel/internal/database/dialect"
-)
-
const (
schema = "queue"
applicationName = "zitadel_queue"
)
-
-var conns = &sync.Map{}
-
-type queueKey struct{}
-
-func WithQueue(parent context.Context) context.Context {
- return context.WithValue(parent, queueKey{}, struct{}{})
-}
-
-func init() {
- dialect.RegisterBeforeAcquire(func(ctx context.Context, c *pgx.Conn) error {
- if _, ok := ctx.Value(queueKey{}).(struct{}); !ok {
- return nil
- }
- _, err := c.Exec(ctx, "SET search_path TO "+schema+"; SET application_name TO "+applicationName)
- if err != nil {
- return err
- }
- conns.Store(c, struct{}{})
- return nil
- })
- dialect.RegisterAfterRelease(func(c *pgx.Conn) error {
- _, ok := conns.LoadAndDelete(c)
- if !ok {
- return nil
- }
- _, err := c.Exec(context.Background(), "SET search_path TO DEFAULT; SET application_name TO "+dialect.DefaultAppName)
- return err
- })
-}
diff --git a/internal/queue/migrate.go b/internal/queue/migrate.go
index e814da3bd3..9af294cbe1 100644
--- a/internal/queue/migrate.go
+++ b/internal/queue/migrate.go
@@ -27,11 +27,10 @@ func (m *Migrator) Execute(ctx context.Context) error {
return err
}
- migrator, err := rivermigrate.New(m.driver, nil)
+ migrator, err := rivermigrate.New(m.driver, &rivermigrate.Config{Schema: schema})
if err != nil {
return err
}
- ctx = WithQueue(ctx)
_, err = migrator.Migrate(ctx, rivermigrate.DirectionUp, nil)
return err
diff --git a/internal/queue/queue.go b/internal/queue/queue.go
index 44e291bf4d..6db5e0ec2a 100644
--- a/internal/queue/queue.go
+++ b/internal/queue/queue.go
@@ -41,6 +41,7 @@ func NewQueue(config *Config) (_ *Queue, err error) {
Queues: make(map[string]river.QueueConfig),
JobTimeout: -1,
Middleware: middleware,
+ Schema: schema,
},
}, nil
}
@@ -56,7 +57,6 @@ func (q *Queue) Start(ctx context.Context) (err error) {
if q == nil || !q.shouldStart {
return nil
}
- ctx = WithQueue(ctx)
q.client, err = river.NewClient(q.driver, q.config)
if err != nil {
@@ -112,7 +112,6 @@ func WithQueueName(name string) InsertOpt {
func (q *Queue) Insert(ctx context.Context, args river.JobArgs, opts ...InsertOpt) error {
options := new(river.InsertOpts)
- ctx = WithQueue(ctx)
for _, opt := range opts {
opt(options)
}
diff --git a/internal/repository/org/org.go b/internal/repository/org/org.go
index cf8a3ce114..562cbd85f8 100644
--- a/internal/repository/org/org.go
+++ b/internal/repository/org/org.go
@@ -275,13 +275,13 @@ func OrgReactivatedEventMapper(event eventstore.Event) (eventstore.Event, error)
}
type OrgRemovedEvent struct {
- eventstore.BaseEvent `json:"-"`
- name string
- usernames []string
- loginMustBeDomain bool
- domains []string
- externalIDPs []*domain.UserIDPLink
- samlEntityIDs []string
+ eventstore.BaseEvent `json:"-"`
+ name string
+ usernames []string
+ organizationScopedUsernames bool
+ domains []string
+ externalIDPs []*domain.UserIDPLink
+ samlEntityIDs []string
}
func (e *OrgRemovedEvent) Payload() interface{} {
@@ -293,7 +293,7 @@ func (e *OrgRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
NewRemoveOrgNameUniqueConstraint(e.name),
}
for _, name := range e.usernames {
- constraints = append(constraints, user.NewRemoveUsernameUniqueConstraint(name, e.Aggregate().ID, e.loginMustBeDomain))
+ constraints = append(constraints, user.NewRemoveUsernameUniqueConstraint(name, e.Aggregate().ID, e.organizationScopedUsernames))
}
for _, domain := range e.domains {
constraints = append(constraints, NewRemoveOrgDomainUniqueConstraint(domain))
@@ -314,19 +314,19 @@ func (e *OrgRemovedEvent) Fields() []*eventstore.FieldOperation {
}
}
-func NewOrgRemovedEvent(ctx context.Context, aggregate *eventstore.Aggregate, name string, usernames []string, loginMustBeDomain bool, domains []string, externalIDPs []*domain.UserIDPLink, samlEntityIDs []string) *OrgRemovedEvent {
+func NewOrgRemovedEvent(ctx context.Context, aggregate *eventstore.Aggregate, name string, usernames []string, organizationScopedUsernames bool, domains []string, externalIDPs []*domain.UserIDPLink, samlEntityIDs []string) *OrgRemovedEvent {
return &OrgRemovedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
OrgRemovedEventType,
),
- name: name,
- usernames: usernames,
- domains: domains,
- externalIDPs: externalIDPs,
- samlEntityIDs: samlEntityIDs,
- loginMustBeDomain: loginMustBeDomain,
+ name: name,
+ usernames: usernames,
+ domains: domains,
+ externalIDPs: externalIDPs,
+ samlEntityIDs: samlEntityIDs,
+ organizationScopedUsernames: organizationScopedUsernames,
}
}
diff --git a/internal/repository/organization_settings/aggregate.go b/internal/repository/organization_settings/aggregate.go
new file mode 100644
index 0000000000..11ea000785
--- /dev/null
+++ b/internal/repository/organization_settings/aggregate.go
@@ -0,0 +1,23 @@
+package organization_settings
+
+import "github.com/zitadel/zitadel/internal/eventstore"
+
+const (
+ AggregateType = "organization_settings"
+ AggregateVersion = "v1"
+)
+
+type Aggregate struct {
+ eventstore.Aggregate
+}
+
+func NewAggregate(id, resourceOwner string) *Aggregate {
+ return &Aggregate{
+ Aggregate: eventstore.Aggregate{
+ Type: AggregateType,
+ Version: AggregateVersion,
+ ID: id,
+ ResourceOwner: resourceOwner,
+ },
+ }
+}
diff --git a/internal/repository/organization_settings/eventstore.go b/internal/repository/organization_settings/eventstore.go
new file mode 100644
index 0000000000..e3cca585d6
--- /dev/null
+++ b/internal/repository/organization_settings/eventstore.go
@@ -0,0 +1,8 @@
+package organization_settings
+
+import "github.com/zitadel/zitadel/internal/eventstore"
+
+func init() {
+ eventstore.RegisterFilterEventMapper(AggregateType, OrganizationSettingsSetEventType, eventstore.GenericEventMapper[OrganizationSettingsSetEvent])
+ eventstore.RegisterFilterEventMapper(AggregateType, OrganizationSettingsRemovedEventType, eventstore.GenericEventMapper[OrganizationSettingsRemovedEvent])
+}
diff --git a/internal/repository/organization_settings/organization.go b/internal/repository/organization_settings/organization.go
new file mode 100644
index 0000000000..c157316ba9
--- /dev/null
+++ b/internal/repository/organization_settings/organization.go
@@ -0,0 +1,96 @@
+package organization_settings
+
+import (
+ "context"
+
+ "github.com/zitadel/zitadel/internal/eventstore"
+ "github.com/zitadel/zitadel/internal/repository/user"
+)
+
+const (
+ organizationSettingsPrefix = "settings.organization."
+ OrganizationSettingsSetEventType = organizationSettingsPrefix + "set"
+ OrganizationSettingsRemovedEventType = organizationSettingsPrefix + "removed"
+)
+
+type OrganizationSettingsSetEvent struct {
+ *eventstore.BaseEvent `json:"-"`
+
+ OrganizationScopedUsernames bool `json:"organizationScopedUsernames,omitempty"`
+ oldOrganizationScopedUsernames bool
+ usernameChanges []string
+}
+
+func (e *OrganizationSettingsSetEvent) SetBaseEvent(b *eventstore.BaseEvent) {
+ e.BaseEvent = b
+}
+
+func (e *OrganizationSettingsSetEvent) Payload() any {
+ return e
+}
+
+func (e *OrganizationSettingsSetEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
+ if len(e.usernameChanges) == 0 || e.oldOrganizationScopedUsernames == e.OrganizationScopedUsernames {
+ return []*eventstore.UniqueConstraint{}
+ }
+ changes := make([]*eventstore.UniqueConstraint, len(e.usernameChanges)*2)
+ for i, username := range e.usernameChanges {
+ changes[i*2] = user.NewRemoveUsernameUniqueConstraint(username, e.Aggregate().ResourceOwner, e.oldOrganizationScopedUsernames)
+ changes[i*2+1] = user.NewAddUsernameUniqueConstraint(username, e.Aggregate().ResourceOwner, e.OrganizationScopedUsernames)
+ }
+ return changes
+}
+
+func NewOrganizationSettingsAddedEvent(
+ ctx context.Context,
+ aggregate *eventstore.Aggregate,
+ usernameChanges []string,
+ organizationScopedUsernames bool,
+ oldOrganizationScopedUsernames bool,
+) *OrganizationSettingsSetEvent {
+ return &OrganizationSettingsSetEvent{
+ BaseEvent: eventstore.NewBaseEventForPush(
+ ctx, aggregate, OrganizationSettingsSetEventType,
+ ),
+ OrganizationScopedUsernames: organizationScopedUsernames,
+ oldOrganizationScopedUsernames: oldOrganizationScopedUsernames,
+ usernameChanges: usernameChanges,
+ }
+}
+
+type OrganizationSettingsRemovedEvent struct {
+ *eventstore.BaseEvent `json:"-"`
+
+ organizationScopedUsernames bool
+ oldOrganizationScopedUsernames bool
+ usernameChanges []string
+}
+
+func (e *OrganizationSettingsRemovedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
+ e.BaseEvent = b
+}
+
+func (e *OrganizationSettingsRemovedEvent) Payload() any {
+ return e
+}
+
+func (e *OrganizationSettingsRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
+ return user.NewUsernameUniqueConstraints(e.usernameChanges, e.Aggregate().ResourceOwner, e.organizationScopedUsernames, e.oldOrganizationScopedUsernames)
+}
+
+func NewOrganizationSettingsRemovedEvent(
+ ctx context.Context,
+ aggregate *eventstore.Aggregate,
+ usernameChanges []string,
+ organizationScopedUsernames bool,
+ oldOrganizationScopedUsernames bool,
+) *OrganizationSettingsRemovedEvent {
+ return &OrganizationSettingsRemovedEvent{
+ BaseEvent: eventstore.NewBaseEventForPush(
+ ctx, aggregate, OrganizationSettingsRemovedEventType,
+ ),
+ organizationScopedUsernames: organizationScopedUsernames,
+ oldOrganizationScopedUsernames: oldOrganizationScopedUsernames,
+ usernameChanges: usernameChanges,
+ }
+}
diff --git a/internal/repository/policy/policy_domain.go b/internal/repository/policy/policy_domain.go
index bd1d9c1b7e..ad459625b2 100644
--- a/internal/repository/policy/policy_domain.go
+++ b/internal/repository/policy/policy_domain.go
@@ -2,6 +2,7 @@ package policy
import (
"github.com/zitadel/zitadel/internal/eventstore"
+ "github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/zerrors"
)
@@ -122,6 +123,10 @@ func DomainPolicyChangedEventMapper(event eventstore.Event) (eventstore.Event, e
type DomainPolicyRemovedEvent struct {
eventstore.BaseEvent `json:"-"`
+
+ usernameChanges []string
+ userLoginMustBeDomain bool
+ oldUserLoginMustBeDomain bool
}
func (e *DomainPolicyRemovedEvent) Payload() interface{} {
@@ -129,7 +134,7 @@ func (e *DomainPolicyRemovedEvent) Payload() interface{} {
}
func (e *DomainPolicyRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
- return nil
+ return user.NewUsernameUniqueConstraints(e.usernameChanges, e.Aggregate().ResourceOwner, e.userLoginMustBeDomain, e.oldUserLoginMustBeDomain)
}
func NewDomainPolicyRemovedEvent(base *eventstore.BaseEvent) *DomainPolicyRemovedEvent {
@@ -143,3 +148,9 @@ func DomainPolicyRemovedEventMapper(event eventstore.Event) (eventstore.Event, e
BaseEvent: *eventstore.BaseEventFromRepo(event),
}, nil
}
+
+func (e *DomainPolicyRemovedEvent) AddUniqueConstraintChanges(usernameChanges []string, userLoginMustBeDomain, oldUserLoginMustBeDomain bool) {
+ e.usernameChanges = usernameChanges
+ e.userLoginMustBeDomain = userLoginMustBeDomain
+ e.oldUserLoginMustBeDomain = oldUserLoginMustBeDomain
+}
diff --git a/internal/repository/user/human.go b/internal/repository/user/human.go
index d503ecc899..823a86aaed 100644
--- a/internal/repository/user/human.go
+++ b/internal/repository/user/human.go
@@ -31,8 +31,8 @@ const (
type HumanAddedEvent struct {
eventstore.BaseEvent `json:"-"`
- UserName string `json:"userName"`
- userLoginMustBeDomain bool
+ UserName string `json:"userName"`
+ orgScopedUsername bool
FirstName string `json:"firstName,omitempty"`
LastName string `json:"lastName,omitempty"`
@@ -63,7 +63,7 @@ func (e *HumanAddedEvent) Payload() interface{} {
}
func (e *HumanAddedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
- return []*eventstore.UniqueConstraint{NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, e.userLoginMustBeDomain)}
+ return []*eventstore.UniqueConstraint{NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, e.orgScopedUsername)}
}
func (e *HumanAddedEvent) AddAddressData(
@@ -106,7 +106,7 @@ func NewHumanAddedEvent(
preferredLanguage language.Tag,
gender domain.Gender,
emailAddress domain.EmailAddress,
- userLoginMustBeDomain bool,
+ orgScopedUsername bool,
) *HumanAddedEvent {
return &HumanAddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@@ -114,15 +114,15 @@ func NewHumanAddedEvent(
aggregate,
HumanAddedType,
),
- UserName: userName,
- FirstName: firstName,
- LastName: lastName,
- NickName: nickName,
- DisplayName: displayName,
- PreferredLanguage: preferredLanguage,
- Gender: gender,
- EmailAddress: emailAddress,
- userLoginMustBeDomain: userLoginMustBeDomain,
+ UserName: userName,
+ FirstName: firstName,
+ LastName: lastName,
+ NickName: nickName,
+ DisplayName: displayName,
+ PreferredLanguage: preferredLanguage,
+ Gender: gender,
+ EmailAddress: emailAddress,
+ orgScopedUsername: orgScopedUsername,
}
}
@@ -139,22 +139,24 @@ func HumanAddedEventMapper(event eventstore.Event) (eventstore.Event, error) {
}
type HumanRegisteredEvent struct {
- eventstore.BaseEvent `json:"-"`
- UserName string `json:"userName"`
- userLoginMustBeDomain bool
- FirstName string `json:"firstName,omitempty"`
- LastName string `json:"lastName,omitempty"`
- NickName string `json:"nickName,omitempty"`
- DisplayName string `json:"displayName,omitempty"`
- PreferredLanguage language.Tag `json:"preferredLanguage,omitempty"`
- Gender domain.Gender `json:"gender,omitempty"`
- EmailAddress domain.EmailAddress `json:"email,omitempty"`
- PhoneNumber domain.PhoneNumber `json:"phone,omitempty"`
- Country string `json:"country,omitempty"`
- Locality string `json:"locality,omitempty"`
- PostalCode string `json:"postalCode,omitempty"`
- Region string `json:"region,omitempty"`
- StreetAddress string `json:"streetAddress,omitempty"`
+ eventstore.BaseEvent `json:"-"`
+
+ UserName string `json:"userName"`
+ orgScopedUsername bool
+
+ FirstName string `json:"firstName,omitempty"`
+ LastName string `json:"lastName,omitempty"`
+ NickName string `json:"nickName,omitempty"`
+ DisplayName string `json:"displayName,omitempty"`
+ PreferredLanguage language.Tag `json:"preferredLanguage,omitempty"`
+ Gender domain.Gender `json:"gender,omitempty"`
+ EmailAddress domain.EmailAddress `json:"email,omitempty"`
+ PhoneNumber domain.PhoneNumber `json:"phone,omitempty"`
+ Country string `json:"country,omitempty"`
+ Locality string `json:"locality,omitempty"`
+ PostalCode string `json:"postalCode,omitempty"`
+ Region string `json:"region,omitempty"`
+ StreetAddress string `json:"streetAddress,omitempty"`
// New events only use EncodedHash. However, the secret field
// is preserved to handle events older than the switch to Passwap.
@@ -170,7 +172,7 @@ func (e *HumanRegisteredEvent) Payload() interface{} {
}
func (e *HumanRegisteredEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
- return []*eventstore.UniqueConstraint{NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, e.userLoginMustBeDomain)}
+ return []*eventstore.UniqueConstraint{NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, e.orgScopedUsername)}
}
func (e *HumanRegisteredEvent) AddAddressData(
@@ -213,7 +215,7 @@ func NewHumanRegisteredEvent(
preferredLanguage language.Tag,
gender domain.Gender,
emailAddress domain.EmailAddress,
- userLoginMustBeDomain bool,
+ orgScopedUsername bool,
userAgentID string,
) *HumanRegisteredEvent {
return &HumanRegisteredEvent{
@@ -222,16 +224,16 @@ func NewHumanRegisteredEvent(
aggregate,
HumanRegisteredType,
),
- UserName: userName,
- FirstName: firstName,
- LastName: lastName,
- NickName: nickName,
- DisplayName: displayName,
- PreferredLanguage: preferredLanguage,
- Gender: gender,
- EmailAddress: emailAddress,
- userLoginMustBeDomain: userLoginMustBeDomain,
- UserAgentID: userAgentID,
+ UserName: userName,
+ FirstName: firstName,
+ LastName: lastName,
+ NickName: nickName,
+ DisplayName: displayName,
+ PreferredLanguage: preferredLanguage,
+ Gender: gender,
+ EmailAddress: emailAddress,
+ orgScopedUsername: orgScopedUsername,
+ UserAgentID: userAgentID,
}
}
diff --git a/internal/repository/user/machine.go b/internal/repository/user/machine.go
index a466f92fe3..b54149639a 100644
--- a/internal/repository/user/machine.go
+++ b/internal/repository/user/machine.go
@@ -17,8 +17,8 @@ const (
type MachineAddedEvent struct {
eventstore.BaseEvent `json:"-"`
- UserName string `json:"userName"`
- userLoginMustBeDomain bool
+ UserName string `json:"userName"`
+ orgScopedUsername bool
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
@@ -30,7 +30,7 @@ func (e *MachineAddedEvent) Payload() interface{} {
}
func (e *MachineAddedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
- return []*eventstore.UniqueConstraint{NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, e.userLoginMustBeDomain)}
+ return []*eventstore.UniqueConstraint{NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, e.orgScopedUsername)}
}
func NewMachineAddedEvent(
@@ -39,7 +39,7 @@ func NewMachineAddedEvent(
userName,
name,
description string,
- userLoginMustBeDomain bool,
+ orgScopedUsername bool,
accessTokenType domain.OIDCTokenType,
) *MachineAddedEvent {
return &MachineAddedEvent{
@@ -48,11 +48,11 @@ func NewMachineAddedEvent(
aggregate,
MachineAddedEventType,
),
- UserName: userName,
- Name: name,
- Description: description,
- userLoginMustBeDomain: userLoginMustBeDomain,
- AccessTokenType: accessTokenType,
+ UserName: userName,
+ Name: name,
+ Description: description,
+ orgScopedUsername: orgScopedUsername,
+ AccessTokenType: accessTokenType,
}
}
diff --git a/internal/repository/user/user.go b/internal/repository/user/user.go
index e8faddb645..470edc6f16 100644
--- a/internal/repository/user/user.go
+++ b/internal/repository/user/user.go
@@ -27,9 +27,9 @@ const (
UserUserNameChangedType = userEventTypePrefix + "username.changed"
)
-func NewAddUsernameUniqueConstraint(userName, resourceOwner string, userLoginMustBeDomain bool) *eventstore.UniqueConstraint {
+func NewAddUsernameUniqueConstraint(userName, resourceOwner string, orgScopedUsername bool) *eventstore.UniqueConstraint {
uniqueUserName := userName
- if userLoginMustBeDomain {
+ if orgScopedUsername {
uniqueUserName = userName + resourceOwner
}
return eventstore.NewAddEventUniqueConstraint(
@@ -38,9 +38,9 @@ func NewAddUsernameUniqueConstraint(userName, resourceOwner string, userLoginMus
"Errors.User.AlreadyExists")
}
-func NewRemoveUsernameUniqueConstraint(userName, resourceOwner string, userLoginMustBeDomain bool) *eventstore.UniqueConstraint {
+func NewRemoveUsernameUniqueConstraint(userName, resourceOwner string, orgScopedUsername bool) *eventstore.UniqueConstraint {
uniqueUserName := userName
- if userLoginMustBeDomain {
+ if orgScopedUsername {
uniqueUserName = userName + resourceOwner
}
return eventstore.NewRemoveUniqueConstraint(
@@ -48,6 +48,18 @@ func NewRemoveUsernameUniqueConstraint(userName, resourceOwner string, userLogin
uniqueUserName)
}
+func NewUsernameUniqueConstraints(usernameChanges []string, resourceOwner string, orgScopedUsername, oldOrgScopedUsername bool) []*eventstore.UniqueConstraint {
+ if len(usernameChanges) == 0 || oldOrgScopedUsername == orgScopedUsername {
+ return []*eventstore.UniqueConstraint{}
+ }
+ changes := make([]*eventstore.UniqueConstraint, len(usernameChanges)*2)
+ for i, username := range usernameChanges {
+ changes[i*2] = NewRemoveUsernameUniqueConstraint(username, resourceOwner, oldOrgScopedUsername)
+ changes[i*2+1] = NewAddUsernameUniqueConstraint(username, resourceOwner, orgScopedUsername)
+ }
+ return changes
+}
+
type UserLockedEvent struct {
eventstore.BaseEvent `json:"-"`
}
@@ -165,7 +177,7 @@ type UserRemovedEvent struct {
userName string
externalIDPs []*domain.UserIDPLink
- loginMustBeDomain bool
+ orgScopedUsername bool
}
func (e *UserRemovedEvent) Payload() interface{} {
@@ -175,7 +187,7 @@ func (e *UserRemovedEvent) Payload() interface{} {
func (e *UserRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
events := make([]*eventstore.UniqueConstraint, 0)
if e.userName != "" {
- events = append(events, NewRemoveUsernameUniqueConstraint(e.userName, e.Aggregate().ResourceOwner, e.loginMustBeDomain))
+ events = append(events, NewRemoveUsernameUniqueConstraint(e.userName, e.Aggregate().ResourceOwner, e.orgScopedUsername))
}
for _, idp := range e.externalIDPs {
events = append(events, NewRemoveUserIDPLinkUniqueConstraint(idp.IDPConfigID, idp.ExternalUserID))
@@ -188,7 +200,7 @@ func NewUserRemovedEvent(
aggregate *eventstore.Aggregate,
userName string,
externalIDPs []*domain.UserIDPLink,
- userLoginMustBeDomain bool,
+ orgScopedUsername bool,
) *UserRemovedEvent {
return &UserRemovedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@@ -198,7 +210,7 @@ func NewUserRemovedEvent(
),
userName: userName,
externalIDPs: externalIDPs,
- loginMustBeDomain: userLoginMustBeDomain,
+ orgScopedUsername: orgScopedUsername,
}
}
@@ -393,10 +405,10 @@ func UserTokenRemovedEventMapper(event eventstore.Event) (eventstore.Event, erro
type DomainClaimedEvent struct {
eventstore.BaseEvent `json:"-"`
- UserName string `json:"userName"`
- TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
- oldUserName string
- userLoginMustBeDomain bool
+ UserName string `json:"userName"`
+ TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
+ oldUserName string
+ orgScopedUsername bool
}
func (e *DomainClaimedEvent) Payload() interface{} {
@@ -405,8 +417,8 @@ func (e *DomainClaimedEvent) Payload() interface{} {
func (e *DomainClaimedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return []*eventstore.UniqueConstraint{
- NewRemoveUsernameUniqueConstraint(e.oldUserName, e.Aggregate().ResourceOwner, e.userLoginMustBeDomain),
- NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, e.userLoginMustBeDomain),
+ NewRemoveUsernameUniqueConstraint(e.oldUserName, e.Aggregate().ResourceOwner, e.orgScopedUsername),
+ NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, e.orgScopedUsername),
}
}
@@ -419,7 +431,7 @@ func NewDomainClaimedEvent(
aggregate *eventstore.Aggregate,
userName,
oldUserName string,
- userLoginMustBeDomain bool,
+ orgScopedUsername bool,
) *DomainClaimedEvent {
return &DomainClaimedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@@ -427,10 +439,10 @@ func NewDomainClaimedEvent(
aggregate,
UserDomainClaimedType,
),
- UserName: userName,
- oldUserName: oldUserName,
- userLoginMustBeDomain: userLoginMustBeDomain,
- TriggeredAtOrigin: http.DomainContext(ctx).Origin(),
+ UserName: userName,
+ oldUserName: oldUserName,
+ orgScopedUsername: orgScopedUsername,
+ TriggeredAtOrigin: http.DomainContext(ctx).Origin(),
}
}
@@ -480,10 +492,11 @@ func DomainClaimedSentEventMapper(event eventstore.Event) (eventstore.Event, err
type UsernameChangedEvent struct {
eventstore.BaseEvent `json:"-"`
- UserName string `json:"userName"`
- oldUserName string
- userLoginMustBeDomain bool
- oldUserLoginMustBeDomain bool
+ UserName string `json:"userName"`
+ oldUserName string
+ userLoginMustBeDomain bool
+ oldUserLoginMustBeDomain bool
+ organizationScopedUsernames bool
}
func (e *UsernameChangedEvent) Payload() interface{} {
@@ -491,9 +504,20 @@ func (e *UsernameChangedEvent) Payload() interface{} {
}
func (e *UsernameChangedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
+ newSetting := e.userLoginMustBeDomain || e.organizationScopedUsernames
+ oldSetting := e.oldUserLoginMustBeDomain || e.organizationScopedUsernames
+
+ // changes only necessary if username changed or setting for usernames changed
+ // if user login must be domain is set, there is a possibility that the username changes
+ // organization scoped usernames are included here so that the unique constraint only gets changed if necessary
+ if e.oldUserName == e.UserName &&
+ newSetting == oldSetting {
+ return []*eventstore.UniqueConstraint{}
+ }
+
return []*eventstore.UniqueConstraint{
- NewRemoveUsernameUniqueConstraint(e.oldUserName, e.Aggregate().ResourceOwner, e.oldUserLoginMustBeDomain),
- NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, e.userLoginMustBeDomain),
+ NewRemoveUsernameUniqueConstraint(e.oldUserName, e.Aggregate().ResourceOwner, oldSetting),
+ NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, newSetting),
}
}
@@ -503,6 +527,7 @@ func NewUsernameChangedEvent(
oldUserName,
newUserName string,
userLoginMustBeDomain bool,
+ organizationScopedUsernames bool,
opts ...UsernameChangedEventOption,
) *UsernameChangedEvent {
event := &UsernameChangedEvent{
@@ -511,10 +536,11 @@ func NewUsernameChangedEvent(
aggregate,
UserUserNameChangedType,
),
- UserName: newUserName,
- oldUserName: oldUserName,
- userLoginMustBeDomain: userLoginMustBeDomain,
- oldUserLoginMustBeDomain: userLoginMustBeDomain,
+ UserName: newUserName,
+ oldUserName: oldUserName,
+ userLoginMustBeDomain: userLoginMustBeDomain,
+ oldUserLoginMustBeDomain: userLoginMustBeDomain,
+ organizationScopedUsernames: organizationScopedUsernames,
}
for _, opt := range opts {
opt(event)
@@ -526,9 +552,9 @@ type UsernameChangedEventOption func(*UsernameChangedEvent)
// UsernameChangedEventWithPolicyChange signals that the change occurs because of / during a domain policy change
// (will ensure the unique constraint change is handled correctly)
-func UsernameChangedEventWithPolicyChange() UsernameChangedEventOption {
+func UsernameChangedEventWithPolicyChange(oldUserLoginMustBeDomain bool) UsernameChangedEventOption {
return func(e *UsernameChangedEvent) {
- e.oldUserLoginMustBeDomain = !e.userLoginMustBeDomain
+ e.oldUserLoginMustBeDomain = oldUserLoginMustBeDomain
}
}
diff --git a/load-test/package.json b/load-test/package.json
index 73bf8fd449..fd47427f60 100644
--- a/load-test/package.json
+++ b/load-test/package.json
@@ -28,6 +28,7 @@
"scripts": {
"bundle": "webpack",
"lint": "prettier --check src/**",
- "lint:fix": "prettier --write src"
+ "lint:fix": "prettier --write src",
+ "clean": "rm -rf dist .turbo node_modules"
}
}
diff --git a/package.json b/package.json
index 744ef66c04..eb0c881b11 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,8 @@
"devcontainer": "devcontainer",
"devcontainer:lint-unit": "pnpm devcontainer up --config .devcontainer/turbo-lint-unit/devcontainer.json --workspace-folder . --remove-existing-container",
"devcontainer:integration:login": "pnpm devcontainer up --config .devcontainer/login-integration/devcontainer.json --workspace-folder . --remove-existing-container",
+ "clean": "turbo run clean",
+ "clean:all": "pnpm run clean && rm -rf .turbo node_modules",
"generate": "turbo run generate"
},
"pnpm": {
diff --git a/packages/zitadel-client/package.json b/packages/zitadel-client/package.json
index 9dcdcc324e..cef3a02021 100644
--- a/packages/zitadel-client/package.json
+++ b/packages/zitadel-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@zitadel/client",
- "version": "1.2.0",
+ "version": "1.3.1",
"license": "MIT",
"publishConfig": {
"access": "public"
@@ -77,7 +77,7 @@
"@connectrpc/connect": "^2.0.0",
"@connectrpc/connect-node": "^2.0.0",
"@connectrpc/connect-web": "^2.0.0",
- "@zitadel/proto": "latest",
+ "@zitadel/proto": "workspace:*",
"jose": "^5.3.0"
},
"devDependencies": {
diff --git a/packages/zitadel-proto/.npmignore b/packages/zitadel-proto/.npmignore
new file mode 100644
index 0000000000..f422a6b429
--- /dev/null
+++ b/packages/zitadel-proto/.npmignore
@@ -0,0 +1,6 @@
+node_modules
+.turbo
+*.log
+.DS_Store
+buf.gen.yaml
+turbo.json
diff --git a/packages/zitadel-proto/package.json b/packages/zitadel-proto/package.json
index 780ae13ce3..7119edb8de 100644
--- a/packages/zitadel-proto/package.json
+++ b/packages/zitadel-proto/package.json
@@ -1,6 +1,6 @@
{
"name": "@zitadel/proto",
- "version": "1.2.0",
+ "version": "1.3.1",
"license": "MIT",
"publishConfig": {
"access": "public"
@@ -8,6 +8,15 @@
"type": "module",
"main": "./cjs/index.js",
"types": "./types/index.d.ts",
+ "files": [
+ "cjs/**",
+ "es/**",
+ "types/**",
+ "zitadel/**",
+ "google/**",
+ "validate/**",
+ "protoc-gen-openapiv2/**"
+ ],
"exports": {
"./zitadel/*": {
"types": "./types/zitadel/*.d.ts",
diff --git a/proto/zitadel/action/v2beta/action_service.proto b/proto/zitadel/action/v2beta/action_service.proto
index f225905225..c040eb9a1d 100644
--- a/proto/zitadel/action/v2beta/action_service.proto
+++ b/proto/zitadel/action/v2beta/action_service.proto
@@ -290,7 +290,7 @@ service ActionService {
// - `actions`
rpc ListTargets (ListTargetsRequest) returns (ListTargetsResponse) {
option (google.api.http) = {
- post: "/v2beta/actions/targets/_search",
+ post: "/v2beta/actions/targets/search",
body: "*"
};
@@ -372,7 +372,8 @@ service ActionService {
// - `actions`
rpc ListExecutions (ListExecutionsRequest) returns (ListExecutionsResponse) {
option (google.api.http) = {
- post: "/v2beta/actions/executions/_search"
+ post: "/v2beta/actions/executions/search"
+ body: "*"
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
@@ -663,8 +664,9 @@ message ListTargetsRequest {
}
message ListTargetsResponse {
+ reserved 'result';
zitadel.filter.v2beta.PaginationResponse pagination = 1;
- repeated Target result = 2;
+ repeated Target targets = 2;
}
message SetExecutionRequest {
@@ -703,8 +705,9 @@ message ListExecutionsRequest {
}
message ListExecutionsResponse {
+ reserved 'result';
zitadel.filter.v2beta.PaginationResponse pagination = 1;
- repeated Execution result = 2;
+ repeated Execution executions = 2;
}
message ListExecutionFunctionsRequest{}
diff --git a/proto/zitadel/action/v2beta/execution.proto b/proto/zitadel/action/v2beta/execution.proto
index e93470e5dc..61f535abcc 100644
--- a/proto/zitadel/action/v2beta/execution.proto
+++ b/proto/zitadel/action/v2beta/execution.proto
@@ -11,7 +11,6 @@ import "validate/validate.proto";
import "zitadel/protoc_gen_zitadel/v2/options.proto";
import "google/protobuf/timestamp.proto";
-import "zitadel/object/v3alpha/object.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/action/v2beta;action";
diff --git a/proto/zitadel/filter/v2/filter.proto b/proto/zitadel/filter/v2/filter.proto
index 3817324d31..ec85519b44 100644
--- a/proto/zitadel/filter/v2/filter.proto
+++ b/proto/zitadel/filter/v2/filter.proto
@@ -94,3 +94,23 @@ message TimestampFilter {
(validate.rules).enum.defined_only = true
];
}
+
+message InIDsFilter {
+ // Defines the ids to query for.
+ repeated string ids = 1 [
+ (validate.rules).repeated = {
+ unique: true
+ items: {
+ string: {
+ min_len: 1
+ max_len: 200
+ }
+ }
+ },
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ min_length: 1;
+ max_length: 200;
+ example: "[\"69629023906488334\",\"69622366012355662\"]";
+ }
+ ];
+}
diff --git a/proto/zitadel/project/v2beta/project_service.proto b/proto/zitadel/project/v2beta/project_service.proto
index 66f221b911..3fd1513d2d 100644
--- a/proto/zitadel/project/v2beta/project_service.proto
+++ b/proto/zitadel/project/v2beta/project_service.proto
@@ -638,7 +638,7 @@ service ProjectService {
// Returns a list of project grants. A project grant is when the organization grants its project to another organization.
//
// Required permission:
- // - `project.grant.write`
+ // - `project.grant.read`
rpc ListProjectGrants(ListProjectGrantsRequest) returns (ListProjectGrantsResponse) {
option (google.api.http) = {
post: "/v2beta/projects/grants/search"
diff --git a/proto/zitadel/settings/v2/settings_service.proto b/proto/zitadel/settings/v2/settings_service.proto
index 0a1f13e7e7..ea3e2c3653 100644
--- a/proto/zitadel/settings/v2/settings_service.proto
+++ b/proto/zitadel/settings/v2/settings_service.proto
@@ -17,6 +17,8 @@ import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
import "google/protobuf/struct.proto";
import "zitadel/settings/v2/settings.proto";
+import "google/protobuf/timestamp.proto";
+import "zitadel/filter/v2/filter.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2;settings";
@@ -415,7 +417,7 @@ service SettingsService {
}
};
};
-
+
option (google.api.http) = {
put: "/v2/settings/hosted_login_translation";
body: "*"
@@ -579,8 +581,8 @@ message GetHostedLoginTranslationResponse {
google.protobuf.Struct translations = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "{\"common\":{\"back\":\"Indietro\"},\"accounts\":{\"title\":\"Account\",\"description\":\"Seleziona l'account che desideri utilizzare.\",\"addAnother\":\"Aggiungi un altro account\",\"noResults\":\"Nessun account trovato\"}}";
- description: "Translations contains the translations in the request language.";
+ example: "{\"common\":{\"back\":\"Indietro\"},\"accounts\":{\"title\":\"Account\",\"description\":\"Seleziona l'account che desideri utilizzare.\",\"addAnother\":\"Aggiungi un altro account\",\"noResults\":\"Nessun account trovato\"}}";
+ description: "Translations contains the translations in the request language.";
}
];
}
@@ -590,7 +592,7 @@ message SetHostedLoginTranslationRequest {
bool instance = 1 [(validate.rules).bool = {const: true}];
string organization_id = 2;
}
-
+
string locale = 3 [
(validate.rules).string = {min_len: 2},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
@@ -601,8 +603,8 @@ message SetHostedLoginTranslationRequest {
google.protobuf.Struct translations = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "{\"common\":{\"back\":\"Indietro\"},\"accounts\":{\"title\":\"Account\",\"description\":\"Seleziona l'account che desideri utilizzare.\",\"addAnother\":\"Aggiungi un altro account\",\"noResults\":\"Nessun account trovato\"}}";
- description: "Translations should contain the translations in the specified locale.";
+ example: "{\"common\":{\"back\":\"Indietro\"},\"accounts\":{\"title\":\"Account\",\"description\":\"Seleziona l'account che desideri utilizzare.\",\"addAnother\":\"Aggiungi un altro account\",\"noResults\":\"Nessun account trovato\"}}";
+ description: "Translations should contain the translations in the specified locale.";
}
];
}
@@ -617,4 +619,4 @@ message SetHostedLoginTranslationResponse {
example: "\"42a1ba123e6ea6f0c93e286ed97c7018\"";
}
];
-}
\ No newline at end of file
+}
diff --git a/proto/zitadel/settings/v2beta/organization_settings.proto b/proto/zitadel/settings/v2beta/organization_settings.proto
new file mode 100644
index 0000000000..743dbf9881
--- /dev/null
+++ b/proto/zitadel/settings/v2beta/organization_settings.proto
@@ -0,0 +1,55 @@
+syntax = "proto3";
+
+package zitadel.settings.v2beta;
+
+option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta;settings";
+
+import "google/api/field_behavior.proto";
+import "protoc-gen-openapiv2/options/annotations.proto";
+import "validate/validate.proto";
+import "google/protobuf/timestamp.proto";
+import "zitadel/filter/v2beta/filter.proto";
+
+message OrganizationSettings {
+ // The unique identifier of the organization the settings belong to.
+ string organization_id = 1 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"69629012906488334\"";
+ }
+ ];
+ // The timestamp of the organization settings creation.
+ google.protobuf.Timestamp creation_date = 2[
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"2024-12-18T07:50:47.492Z\"";
+ }
+ ];
+ // The timestamp of the last change to the organization settings.
+ google.protobuf.Timestamp change_date = 3 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"2025-01-23T10:34:18.051Z\"";
+ }
+ ];
+ // Defines if the usernames have to be unique in the organization context.
+ bool organization_scoped_usernames = 4;
+}
+
+enum OrganizationSettingsFieldName {
+ ORGANIZATION_SETTINGS_FIELD_NAME_UNSPECIFIED = 0;
+ ORGANIZATION_SETTINGS_FIELD_NAME_ORGANIZATION_ID = 1;
+ ORGANIZATION_SETTINGS_FIELD_NAME_CREATION_DATE = 2;
+ ORGANIZATION_SETTINGS_FIELD_NAME_CHANGE_DATE = 3;
+}
+
+message OrganizationSettingsSearchFilter {
+ oneof filter {
+ option (validate.required) = true;
+
+ zitadel.filter.v2beta.InIDsFilter in_organization_ids_filter = 1;
+ OrganizationScopedUsernamesFilter organization_scoped_usernames_filter = 2;
+ }
+}
+
+// Query for organization settings with specific scopes usernames.
+message OrganizationScopedUsernamesFilter {
+ bool organization_scoped_usernames = 1;
+}
\ No newline at end of file
diff --git a/proto/zitadel/settings/v2beta/settings_service.proto b/proto/zitadel/settings/v2beta/settings_service.proto
index 9404e002a7..a1679b7cb7 100644
--- a/proto/zitadel/settings/v2beta/settings_service.proto
+++ b/proto/zitadel/settings/v2beta/settings_service.proto
@@ -15,6 +15,9 @@ import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
+import "google/protobuf/timestamp.proto";
+import "zitadel/filter/v2beta/filter.proto";
+import "zitadel/settings/v2beta/organization_settings.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta;settings";
@@ -360,6 +363,98 @@ service SettingsService {
description: "Set the security settings of the ZITADEL instance."
};
}
+
+ // Set Organization Settings
+ //
+ // Sets the settings specific to an organization.
+ // Organization scopes usernames defines that the usernames have to be unique in the organization scope, can only be changed if the usernames of the users are unique in the scope.
+ //
+ // Required permissions:
+ // - `iam.policy.write`
+ rpc SetOrganizationSettings(SetOrganizationSettingsRequest) returns (SetOrganizationSettingsResponse) {
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ responses: {
+ key: "200";
+ value: {
+ description: "The translations was successfully set.";
+ }
+ };
+ };
+
+ option (google.api.http) = {
+ post: "/v2/settings/organization";
+ body: "*"
+ };
+
+ option (zitadel.protoc_gen_zitadel.v2.options) = {
+ auth_option: {
+ permission: "authenticated"
+ }
+ };
+ }
+
+ // Delete Organization Settings
+ //
+ // Delete the settings specific to an organization.
+ //
+ // Required permissions:
+ // - `iam.policy.delete`
+ rpc DeleteOrganizationSettings(DeleteOrganizationSettingsRequest) returns (DeleteOrganizationSettingsResponse) {
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ responses: {
+ key: "200";
+ value: {
+ description: "The translations was successfully set.";
+ }
+ };
+ };
+
+ option (google.api.http) = {
+ delete: "/v2/settings/organization";
+ body: "*"
+ };
+
+ option (zitadel.protoc_gen_zitadel.v2.options) = {
+ auth_option: {
+ permission: "authenticated"
+ }
+ };
+ }
+
+ // List Organization Settings
+ //
+ // Returns a list of organization settings.
+ //
+ // Required permission:
+ // - `iam.policy.read`
+ // - `org.policy.read`
+ rpc ListOrganizationSettings(ListOrganizationSettingsRequest) returns (ListOrganizationSettingsResponse) {
+ option (google.api.http) = {
+ post: "/v2/settings/organization/search"
+ body: "*"
+ };
+
+ option (zitadel.protoc_gen_zitadel.v2.options) = {
+ auth_option: {
+ permission: "authenticated"
+ }
+ };
+
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ responses: {
+ key: "200";
+ value: {
+ description: "A list of all project grants matching the query";
+ };
+ };
+ responses: {
+ key: "400";
+ value: {
+ description: "invalid list query";
+ };
+ };
+ };
+ }
}
message GetLoginSettingsRequest {
@@ -474,4 +569,55 @@ message SetSecuritySettingsRequest{
message SetSecuritySettingsResponse{
zitadel.object.v2beta.Details details = 1;
-}
\ No newline at end of file
+}
+
+
+message SetOrganizationSettingsRequest {
+ // Organization ID in which this settings are set.
+ string organization_id = 1;
+ // Force the usernames in the organization to be unique, only possible to set if the existing users already have unique usernames in the organization context.
+ optional bool organization_scoped_usernames = 2;
+}
+
+message SetOrganizationSettingsResponse {
+ // The timestamp of the set of the organization settings.
+ google.protobuf.Timestamp set_date = 1 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"2025-01-23T10:34:18.051Z\"";
+ }
+ ];
+}
+
+message DeleteOrganizationSettingsRequest {
+ // Organization ID in which this settings are set.
+ string organization_id = 1;
+}
+
+message DeleteOrganizationSettingsResponse {
+ // The timestamp of the deletion of the organization settings.
+ google.protobuf.Timestamp deletion_date = 1 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"2025-01-23T10:34:18.051Z\"";
+ }
+ ];
+}
+
+message ListOrganizationSettingsRequest{
+ // List limitations and ordering.
+ optional zitadel.filter.v2beta.PaginationRequest pagination = 1;
+ // The field the result is sorted by. The default is the creation date. Beware that if you change this, your result pagination might be inconsistent.
+ optional OrganizationSettingsFieldName sorting_column = 2 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ default: "\"ORGANIZATION_SETTINGS_FIELD_NAME_CREATION_DATE\""
+ }
+ ];
+ repeated OrganizationSettingsSearchFilter filters = 4;
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
+ example: "{\"pagination\":{\"offset\":0,\"limit\":0,\"asc\":true},\"sortingColumn\":\"ORGANIZATION_SETTINGS_FIELD_NAME_CREATION_DATE\",\"filters\":[{\"inOrganizationIdsFilter\":{\"ids\":[\"28746028909593987\"]}}]}";
+ };
+}
+
+message ListOrganizationSettingsResponse {
+ zitadel.filter.v2beta.PaginationResponse pagination = 1;
+ repeated OrganizationSettings organization_settings = 2;
+}