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 e501eb169b..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
@@ -81,7 +79,7 @@ jobs:
with:
node_version: "18"
buf_version: "latest"
- go_lint_version: "v1.64.8"
+ go_lint_version: "latest"
core_cache_key: ${{ needs.core.outputs.cache_key }}
core_cache_path: ${{ needs.core.outputs.cache_path }}
@@ -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 b8c7486f1f..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:
@@ -77,7 +79,7 @@ jobs:
path: ${{ inputs.core_cache_path }}
key: ${{ inputs.core_cache_key }}
fail-on-cache-miss: true
- - uses: golangci/golangci-lint-action@v6
+ - uses: golangci/golangci-lint-action@v8
with:
version: ${{ inputs.go_lint_version }}
github-token: ${{ github.token }}
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/.golangci.yaml b/.golangci.yaml
index a4d5fd95d4..712df7d33d 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -1,309 +1,148 @@
-issues:
- new-from-rev: main
- # Set to 0 to disable.
- max-issues-per-linter: 0
- # Set to 0 to disable.
- max-same-issues: 0
- exclude-dirs:
- - .artifacts
- - .backups
- - .codecov
- - .github
- - .keys
- - .vscode
- - build
- - console
- - deploy
- - docs
- - guides
- - internal/api/ui/login/static
- - openapi
- - proto
- - tools
- - login
-
+version: "2"
run:
concurrency: 4
- timeout: 10m
- go: '1.22'
+ go: "1.24"
linters:
enable:
- # Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false]
- asciicheck
- # checks whether HTTP response body is closed successfully [fast: false, auto-fix: false]
- bodyclose
- # check the function whether use a non-inherited context [fast: false, auto-fix: false]
- contextcheck
- # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
- - gocognit
- # Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
- - unused
- # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false]
- - errcheck
- # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. [fast: false, auto-fix: false]
- errname
- # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. [fast: false, auto-fix: false]
- errorlint
- # check exhaustiveness of enum switch statements [fast: false, auto-fix: false]
- exhaustive
- # Gci controls golang package import order and makes it always deterministic. [fast: true, auto-fix: false]
- - gci
- # Provides diagnostics that check for bugs, performance and style issues. [fast: false, auto-fix: false]
+ - gocognit
- gocritic
- # Linter for Go source code that specializes in simplifying a code [fast: false, auto-fix: false]
- - gosimple
- # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: false, auto-fix: false]
- - govet
- # Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
- - ineffassign
- # Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
- misspell
- # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
- nakedret
- # Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: false, auto-fix: false]
- - staticcheck
- # Like the front-end of a Go compiler, parses and type-checks Go code [fast: false, auto-fix: false]
- - typecheck
- # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: false]
- nolintlint
- # Checks for misuse of Sprintf to construct a host with port in a URL.
- nosprintfhostport
- # checks whether Err of rows is checked successfully in `sql.Rows` [fast: false, auto-fix: false]
- rowserrcheck
- # Checks that sql.Rows and sql.Stmt are closed. [fast: false, auto-fix: false]
- sqlclosecheck
- # Remove unnecessary type conversions [fast: false, auto-fix: false]
- unconvert
- disable:
- # Checks for dangerous unicode character sequences [fast: true, auto-fix: false]
- # not needed because github does that out of the box
- - bidichk
- # containedctx is a linter that detects struct contained context.Context field [fast: true, auto-fix: false]
- # using contextcheck which looks more active
- - containedctx
- # checks function and package cyclomatic complexity [fast: false, auto-fix: false]
- # not use because gocognit is used
- - cyclop
- # The owner seems to have abandoned the linter. Replaced by unused.
- # deprecated, replaced by unused
- - deadcode
- # check declaration order and count of types, constants, variables and functions [fast: true, auto-fix: false]
- # FUTURE: IMO it sometimes makes sense to declare consts or types after a func
- - decorder
- # Go linter that checks if package imports are in a list of acceptable packages [fast: false, auto-fix: false]
- # not required because of dependabot
- - depguard
- # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
- # FUTURE: old code is not compatible
- - dogsled
- # Tool for code clone detection [fast: true, auto-fix: false]
- # FUTURE: old code is not compatible
- - dupl
- # checks for duplicate words in the source code
- # not sure if it makes sense
- - dupword
- # check for two durations multiplied together [fast: false, auto-fix: false]
- # FUTURE: checks for accident `1 * time.Second * time.Second`
- - durationcheck
- # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted. [fast: false, auto-fix: false]
- # FUTURE: use asap, because we use json alot. nice feature is possiblity to check if err check is required
- - errchkjson
- # execinquery is a linter about query string checker in Query function which reads your Go src files and warning it finds
- # FUTURE: might find some errors in sql queries
- - execinquery
- # Checks if all struct's fields are initialized [fast: false, auto-fix: false]
- # deprecated
- - exhaustivestruct
- # Checks if all structure fields are initialized
- # Not all fields have to be initialized
- - exhaustruct
- # checks for pointers to enclosing loop variables [fast: false, auto-fix: false]
- # FUTURE: finds bugs hard to find, could occur much later
- - exportloopref
- # Forbids identifiers [fast: true, auto-fix: false]
- # see no reason. allows to define regexp which are not allowed to use
- - forbidigo
- # finds forced type assertions [fast: true, auto-fix: false]
- # not used because we mostly use `_, _ = a.(int)`
- - forcetypeassert
- # Tool for detection of long functions [fast: true, auto-fix: false]
- # not used because it ignores complexity
- - funlen
- # check that no global variables exist [fast: true, auto-fix: false]
- # We use some global variables which is ok IMO
- - gochecknoglobals
- # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
- # we use inits for the database abstraction
- - gochecknoinits
- # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
- # FUTURE: might be cool to check
- - goconst
- # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
- # not used because cyclop also checks complexity of package
- - gocyclo
- # Check if comments end in a period [fast: true, auto-fix: true]
- # FUTURE: checks if comments are written as specified
- - godot
- # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false]
- # FUTURE: maybe makes sense later. IMO some view todos are ok for later tasks.
- - godox
- # Golang linter to check the errors handling expressions [fast: false, auto-fix: false]
- # Not used in favore of errorlint
- - goerr113
- # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true]
- # ignored in favor of goimports
- - gofmt
- # Gofumpt checks whether code was gofumpt-ed. [fast: true, auto-fix: true]
- # ignored in favor of goimports
- - gofumpt
- # Checks is file header matches to pattern [fast: true, auto-fix: false]
- # ignored because we don't write licenses as headers
- - goheader
- # In addition to fixing imports, goimports also formats your code in the same style as gofmt. [fast: true, auto-fix: true]
- # ignored in favor of gci
- - goimports
- #deprecated]: Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: false, auto-fix: false]
- # ignored in favor of goimports
- - golint
- # An analyzer to detect magic numbers. [fast: true, auto-fix: false]
- # FUTURE: not that critical at the moment
- - gomnd
- # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. [fast: true, auto-fix: false]
- # FUTURE: not a problem at the moment
- - gomoddirectives
- # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. [fast: true, auto-fix: false]
- # FUTURE: maybe interesting because of licenses
- - gomodguard
- # Checks that printf-like functions are named with `f` at the end [fast: true, auto-fix: false]
- # FUTURE: not a problem at the moment
- - goprintffuncname
- # Inspects source code for security problems [fast: false, auto-fix: false]
- # TODO: I think it would be more interesting to integrate into gh code scanning: https://github.com/securego/gosec#integrating-with-code-scanning
- - gosec
- # An analyzer to analyze expression groups. [fast: true, auto-fix: false]
- # I think the groups (vars, consts, imports, ...) we have atm are ok
- - grouper
- # Checks that your code uses short syntax for if-statements whenever possible [fast: true, auto-fix: false]
- # Dont't use its deprecated
- - ifshort
- # Enforces consistent import aliases [fast: false, auto-fix: false]
- # FUTURE: aliasing of imports is more or less consistent
- - importas
- # A linter that checks the number of methods inside an interface.
- # No need at the moment, repository abstraction was removed
- - interfacebloat
- # A linter that suggests interface types
- # Don't use it's archived
- - interfacer
- # Accept Interfaces, Return Concrete Types [fast: false, auto-fix: false]
- # FUTURE: check if no interface is returned
- - ireturn
- # Reports long lines [fast: true, auto-fix: false]
- # FUTURE: would make code more readable
- - lll
- # Checks key valur pairs for common logger libraries (kitlog,klog,logr,zap).
- # FUTURE: useable as soon as we switch logger library
- - loggercheck
- # maintidx measures the maintainability index of each function. [fast: true, auto-fix: false]
- # not used because volume of halstead complexity feels strange as measurement https://en.wikipedia.org/wiki/Halstead_complexity_measures
- - maintidx
- # Finds slice declarations with non-zero initial length [fast: false, auto-fix: false]
- # I would prefer to use https://github.com/alexkohler/prealloc
- - makezero
- # Reports deeply nested if statements [fast: true, auto-fix: false]
- # focus only on if's
- - nestif
- # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false]
- # FUTURE: check if it is allowed to return nil partially in error catch
- - nilerr
- # Checks that there is no simultaneous return of `nil` error and an invalid value. [fast: false, auto-fix: false]
- # FUTURE: would reduce checks and panics
+ - errcheck
+ - govet
+ - ineffassign
+ - staticcheck
+ - unused
+ - nilnil
+ disable:
+ - bidichk
+ - containedctx
+ - cyclop
+ - decorder
+ - depguard
+ - dogsled
+ - dupl
+ - dupword
+ - durationcheck
+ - err113
+ - errchkjson
+ - exhaustruct
+ - forbidigo
+ - forcetypeassert
+ - funlen
+ - gochecknoglobals
+ - gochecknoinits
+ - goconst
+ - gocyclo
+ - godot
+ - godox
+ - goheader
+ - gomoddirectives
+ - gomodguard
+ - goprintffuncname
+ - gosec
+ - grouper
+ - importas
+ - interfacebloat
+ - ireturn
+ - lll
+ - loggercheck
+ - maintidx
+ - makezero
+ - mnd
+ - nestif
+ - nilerr
- nilnil
- # nlreturn checks for a new line before return and branch statements to increase code clarity [fast: true, auto-fix: false]
- # DISCUSS: IMO the readability of does not always increase using more empty lines
- nlreturn
- # noctx finds sending http request without context.Context [fast: false, auto-fix: false]
- # only interesting if using http
- noctx
- # Reports all names returns
- # Named returns are not allowed which IMO reduces readability of code
- nonamedreturns
- # detects snake case of variable naming and function name.
- # has not been a problem in our code and deprecated
- - nosnakecase
- # paralleltest detects missing usage of t.Parallel() method in your Go test [fast: true, auto-fix: false]
- # FUTURE: will break all of our tests
- paralleltest
- # Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false]
- # FUTURE: would improve performance
- prealloc
- # find code that shadows one of Go's predeclared identifiers [fast: true, auto-fix: false]
- # FUTURE: checks for overwrites
- predeclared
- # Check Prometheus metrics naming via promlint [fast: true, auto-fix: false]
- # Not interesting at the moment
- promlinter
- # Checks that package variables are not reassigned
- # FUTURE: checks if vars like Err's are reassigned which might break code
- reassign
- # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false]
- # Linter aggregator, would allow to use less other linters
- revive
- # checks for unpinned variables in go programs
- # deprecated
- - scopelint
- # Finds unused struct fields [fast: false, auto-fix: false]
- # deprecated, replaced by unused
- - structcheck
- # Stylecheck is a replacement for golint [fast: false, auto-fix: false]
- # we use goimports
- - stylecheck
- # Checks the struct tags. [fast: true, auto-fix: false]
- # FUTURE: would help for new structs
- tagliatelle
- # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 [fast: false, auto-fix: false]
- # FUTURE: currently are no env vars set
- - tenv
- # linter checks if examples are testable (have an expected output)
- # FUTURE: as soon as examples are added
- testableexamples
- # linter that makes you use a separate _test package [fast: true, auto-fix: false]
- # don't use because we test some unexported functions
- testpackage
- # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers [fast: false, auto-fix: false]
- # FUTURE: nice to improve test quality
- thelper
- # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes [fast: false, auto-fix: false]
- # FUTURE: nice to improve test quality
- tparallel
- # Reports unused function parameters [fast: false, auto-fix: false]
- # DISCUSS: nice idea and would improve code quality, but how to handle false positives?
- unparam
- # A linter that detect the possibility to use variables/constants from the Go standard library.
- # FUTURE: improves code quality
- usestdlibvars
- # Finds unused global variables and constants [fast: false, auto-fix: false]
- # deprecated, replaced by unused
- - varcheck
- # checks that the length of a variable's name matches its scope [fast: false, auto-fix: false]
- # I would not use it because it more or less checks if var lenght matches
- varnamelen
- # wastedassign finds wasted assignment statements. [fast: false, auto-fix: false]
- # FUTURE: would improve code quality (maybe already checked by vet?)
- wastedassign
- # Tool for detection of leading and trailing whitespace [fast: true, auto-fix: true]
- # Not sure if it improves code readability
- whitespace
- # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false]
- # FUTURE: improves UX because all the errors will be ZITADEL errors
- wrapcheck
- # Whitespace Linter - Forces you to use empty lines! [fast: true, auto-fix: false]
- # FUTURE: improves code quality by allowing and blocking line breaks
- wsl
-linters-settings:
- gci:
- sections:
- - standard # Standard section: captures all standard packages.
- - default # Default section: contains all imports that could not be matched to another section type.
- - prefix(github.com/zitadel/zitadel) # Custom section: groups all imports with the specified Prefix.
- custom-order: true
+ exclusions:
+ generated: lax
+ presets:
+ - comments
+ - common-false-positives
+ - legacy
+ - std-error-handling
+ paths:
+ - .artifacts
+ - .backups
+ - .codecov
+ - .github
+ - .keys
+ - .vscode
+ - build
+ - console
+ - deploy
+ - docs
+ - guides
+ - internal/api/ui/login/static
+ - openapi
+ - proto
+ - tools
+ - third_party$
+ - builtin$
+ - examples$
+issues:
+ max-issues-per-linter: 0
+ max-same-issues: 0
+ new-from-rev: main
+formatters:
+ enable:
+ - gci
+ settings:
+ gci:
+ sections:
+ - standard
+ - default
+ - prefix(github.com/zitadel/zitadel)
+ custom-order: true
+ exclusions:
+ generated: lax
+ paths:
+ - .artifacts
+ - .backups
+ - .codecov
+ - .github
+ - .keys
+ - .vscode
+ - build
+ - console
+ - deploy
+ - docs
+ - guides
+ - internal/api/ui/login/static
+ - openapi
+ - proto
+ - tools
+ - third_party$
+ - builtin$
+ - examples$
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..a95295f495 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,10 @@ 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.41.1-0.20250718152526-16ebef905b40 h1:MmUhfhwIcPStWqsTW+Pw+kYa5SNY7TxwzktUDohwO78=
+github.com/zitadel/oidc/v3 v3.41.1-0.20250718152526-16ebef905b40/go.mod h1:Y/rY7mHTzMGrZgf7REgQZFWxySlaSVqqFdBmNZq+9wA=
+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 +967,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 +1011,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/admin/repository/eventsourcing/handler/styling.go b/internal/admin/repository/eventsourcing/handler/styling.go
index 2f8964b519..a9dfba0108 100644
--- a/internal/admin/repository/eventsourcing/handler/styling.go
+++ b/internal/admin/repository/eventsourcing/handler/styling.go
@@ -185,7 +185,7 @@ func (s *Styling) Reducers() []handler.AggregateReducer {
}
func (m *Styling) processLabelPolicy(event eventstore.Event) (_ *handler.Statement, err error) {
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
policy := new(iam_model.LabelPolicyView)
switch event.Type() {
case instance.LabelPolicyAddedEventType,
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/init.go b/internal/eventstore/handler/init.go
index 0ae6d2261b..6b1f108a66 100644
--- a/internal/eventstore/handler/init.go
+++ b/internal/eventstore/handler/init.go
@@ -6,7 +6,7 @@ import "context"
type Init func(context.Context, *Check) error
type Check struct {
- Executes []func(ex Executer, projectionName string) (bool, error)
+ Executes []func(ctx context.Context, executer Executer, projectionName string) (bool, error)
}
func (c *Check) IsNoop() bool {
diff --git a/internal/eventstore/handler/v2/handler.go b/internal/eventstore/handler/v2/handler.go
index fd8b206b38..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():
- break
+ return lastProcessedIndex, ctx.Err()
default:
err := h.executeStatement(ctx, tx, statement)
if err != nil {
@@ -669,7 +669,7 @@ func (h *Handler) executeStatement(ctx context.Context, tx *sql.Tx, statement *S
return err
}
- if err = statement.Execute(tx, h.projection.Name()); err != nil {
+ if err = statement.Execute(ctx, tx, h.projection.Name()); err != nil {
h.log().WithError(err).Error("statement execution failed")
_, rollbackErr := tx.ExecContext(ctx, "ROLLBACK TO SAVEPOINT exec_stmt")
diff --git a/internal/eventstore/handler/v2/init.go b/internal/eventstore/handler/v2/init.go
index ead1c806d0..f797734644 100644
--- a/internal/eventstore/handler/v2/init.go
+++ b/internal/eventstore/handler/v2/init.go
@@ -200,7 +200,7 @@ func (h *Handler) Init(ctx context.Context) error {
}
for i, execute := range check.Init().Executes {
logging.WithFields("projection", h.projection.Name(), "execute", i).Debug("executing check")
- next, err := execute(tx, h.projection.Name())
+ next, err := execute(ctx, tx, h.projection.Name())
if err != nil {
logging.OnError(tx.Rollback()).Debug("unable to rollback")
return err
@@ -218,7 +218,7 @@ func NewTableCheck(table *Table, opts ...execOption) *handler.Check {
create := func(config execConfig) string {
return createTableStatement(table, config.tableName, "")
}
- executes := make([]func(handler.Executer, string) (bool, error), len(table.indices)+1)
+ executes := make([]func(context.Context, handler.Executer, string) (bool, error), len(table.indices)+1)
executes[0] = execNextIfExists(config, create, opts, true)
for i, index := range table.indices {
executes[i+1] = execNextIfExists(config, createIndexCheck(index), opts, true)
@@ -239,7 +239,7 @@ func NewMultiTableCheck(primaryTable *Table, secondaryTables ...*SuffixedTable)
}
return &handler.Check{
- Executes: []func(handler.Executer, string) (bool, error){
+ Executes: []func(context.Context, handler.Executer, string) (bool, error){
execNextIfExists(config, create, nil, true),
},
}
@@ -257,14 +257,14 @@ func NewViewCheck(selectStmt string, secondaryTables ...*SuffixedTable) *handler
}
return &handler.Check{
- Executes: []func(handler.Executer, string) (bool, error){
+ Executes: []func(context.Context, handler.Executer, string) (bool, error){
execNextIfExists(config, create, nil, false),
},
}
}
-func execNextIfExists(config execConfig, q query, opts []execOption, executeNext bool) func(handler.Executer, string) (bool, error) {
- return func(handler handler.Executer, name string) (shouldExecuteNext bool, err error) {
+func execNextIfExists(config execConfig, q query, opts []execOption, executeNext bool) func(ctx context.Context, handler handler.Executer, name string) (bool, error) {
+ return func(ctx context.Context, handler handler.Executer, name string) (shouldExecuteNext bool, err error) {
_, err = handler.Exec("SAVEPOINT exec_stmt")
if err != nil {
return false, zerrors.ThrowInternal(err, "V2-U1wlz", "create savepoint failed")
@@ -280,7 +280,7 @@ func execNextIfExists(config execConfig, q query, opts []execOption, executeNext
return
}
}()
- err = exec(config, q, opts)(handler, name)
+ err = exec(config, q, opts)(ctx, handler, name)
return false, err
}
}
diff --git a/internal/eventstore/handler/v2/statement.go b/internal/eventstore/handler/v2/statement.go
index 5024c8c945..e584160287 100644
--- a/internal/eventstore/handler/v2/statement.go
+++ b/internal/eventstore/handler/v2/statement.go
@@ -1,6 +1,7 @@
package handler
import (
+ "context"
"database/sql"
"encoding/json"
"errors"
@@ -91,7 +92,7 @@ type Statement struct {
Execute Exec
}
-type Exec func(ex Executer, projectionName string) error
+type Exec func(ctx context.Context, ex Executer, projectionName string) error
func WithTableSuffix(name string) func(*execConfig) {
return func(o *execConfig) {
@@ -670,7 +671,7 @@ type execConfig struct {
type query func(config execConfig) string
func exec(config execConfig, q query, opts []execOption) Exec {
- return func(ex Executer, projectionName string) (err error) {
+ return func(ctx context.Context, ex Executer, projectionName string) (err error) {
if projectionName == "" {
return ErrNoProjection
}
@@ -694,12 +695,12 @@ func exec(config execConfig, q query, opts []execOption) Exec {
}
func multiExec(execList []Exec) Exec {
- return func(ex Executer, projectionName string) error {
+ return func(ctx context.Context, ex Executer, projectionName string) error {
for _, exec := range execList {
if exec == nil {
continue
}
- if err := exec(ex, projectionName); err != nil {
+ if err := exec(ctx, ex, projectionName); err != nil {
return err
}
}
diff --git a/internal/eventstore/handler/v2/statement_test.go b/internal/eventstore/handler/v2/statement_test.go
index 787ec105e1..8384029ec8 100644
--- a/internal/eventstore/handler/v2/statement_test.go
+++ b/internal/eventstore/handler/v2/statement_test.go
@@ -1,6 +1,7 @@
package handler
import (
+ "context"
"database/sql"
"errors"
"reflect"
@@ -197,7 +198,7 @@ func TestNewCreateStatement(t *testing.T) {
tt.want.executer.t = t
stmt := NewCreateStatement(tt.args.event, tt.args.values)
- err := stmt.Execute(tt.want.executer, tt.args.table)
+ err := stmt.Execute(t.Context(), tt.want.executer, tt.args.table)
if !tt.want.isErr(err) {
t.Errorf("unexpected error: %v", err)
}
@@ -506,7 +507,7 @@ func TestNewUpsertStatement(t *testing.T) {
tt.want.executer.t = t
stmt := NewUpsertStatement(tt.args.event, tt.args.conflictCols, tt.args.values)
- err := stmt.Execute(tt.want.executer, tt.args.table)
+ err := stmt.Execute(t.Context(), tt.want.executer, tt.args.table)
if !tt.want.isErr(err) {
t.Errorf("unexpected error: %v", err)
}
@@ -710,7 +711,7 @@ func TestNewUpdateStatement(t *testing.T) {
tt.want.executer.t = t
stmt := NewUpdateStatement(tt.args.event, tt.args.values, tt.args.conditions)
- err := stmt.Execute(tt.want.executer, tt.args.table)
+ err := stmt.Execute(t.Context(), tt.want.executer, tt.args.table)
if !tt.want.isErr(err) {
t.Errorf("unexpected error: %v", err)
}
@@ -827,7 +828,7 @@ func TestNewDeleteStatement(t *testing.T) {
tt.want.executer.t = t
stmt := NewDeleteStatement(tt.args.event, tt.args.conditions)
- err := stmt.Execute(tt.want.executer, tt.args.table)
+ err := stmt.Execute(t.Context(), tt.want.executer, tt.args.table)
if !tt.want.isErr(err) {
t.Errorf("unexpected error: %v", err)
}
@@ -878,7 +879,7 @@ func TestNewNoOpStatement(t *testing.T) {
return
}
tt.want.executer.t = t
- err := stmt.Execute(tt.want.executer, tt.args.table)
+ err := stmt.Execute(t.Context(), tt.want.executer, tt.args.table)
if !tt.want.isErr(err) {
t.Errorf("unexpected error: %v", err)
}
@@ -1054,7 +1055,7 @@ func TestNewMultiStatement(t *testing.T) {
return
}
tt.want.executer.t = t
- err := stmt.Execute(tt.want.executer, tt.args.table)
+ err := stmt.Execute(t.Context(), tt.want.executer, tt.args.table)
if !tt.want.isErr(err) {
t.Errorf("unexpected error: %v", err)
}
@@ -1338,7 +1339,7 @@ func TestNewCopyStatement(t *testing.T) {
tt.want.executer.t = t
stmt := NewCopyStatement(tt.args.event, tt.args.conflictingCols, tt.args.from, tt.args.to, tt.args.conds)
- err := stmt.Execute(tt.want.executer, tt.args.table)
+ err := stmt.Execute(t.Context(), tt.want.executer, tt.args.table)
if !tt.want.isErr(err) {
t.Errorf("unexpected error: %v", err)
}
@@ -1349,7 +1350,7 @@ func TestNewCopyStatement(t *testing.T) {
func TestStatement_Execute(t *testing.T) {
type fields struct {
- execute func(ex Executer, projectionName string) error
+ execute func(ctx context.Context, ex Executer, projectionName string) error
}
type want struct {
isErr func(error) bool
@@ -1366,7 +1367,7 @@ func TestStatement_Execute(t *testing.T) {
{
name: "execute returns no error",
fields: fields{
- execute: func(ex Executer, projectionName string) error { return nil },
+ execute: func(ctx context.Context, ex Executer, projectionName string) error { return nil },
},
args: args{
projectionName: "my_projection",
@@ -1383,7 +1384,7 @@ func TestStatement_Execute(t *testing.T) {
projectionName: "my_projection",
},
fields: fields{
- execute: func(ex Executer, projectionName string) error { return errTest },
+ execute: func(ctx context.Context, ex Executer, projectionName string) error { return errTest },
},
want: want{
isErr: func(err error) bool {
@@ -1397,7 +1398,7 @@ func TestStatement_Execute(t *testing.T) {
stmt := &Statement{
Execute: tt.fields.execute,
}
- if err := stmt.Execute(nil, tt.args.projectionName); !tt.want.isErr(err) {
+ if err := stmt.Execute(t.Context(), nil, tt.args.projectionName); !tt.want.isErr(err) {
t.Errorf("unexpected error: %v", err)
}
})
diff --git a/internal/execution/ctx.go b/internal/execution/ctx.go
index 9e6bac3e30..d63fa13e5d 100644
--- a/internal/execution/ctx.go
+++ b/internal/execution/ctx.go
@@ -9,8 +9,8 @@ import (
const ExecutionUserID = "EXECUTION"
-func HandlerContext(event *eventstore.Aggregate) context.Context {
- ctx := authz.WithInstanceID(context.Background(), event.InstanceID)
+func HandlerContext(parent context.Context, event *eventstore.Aggregate) context.Context {
+ ctx := authz.WithInstanceID(parent, event.InstanceID)
return authz.SetCtxData(ctx, authz.CtxData{UserID: ExecutionUserID, OrgID: event.ResourceOwner})
}
diff --git a/internal/execution/handlers.go b/internal/execution/handlers.go
index 030e6d5186..1c27cb5920 100644
--- a/internal/execution/handlers.go
+++ b/internal/execution/handlers.go
@@ -113,7 +113,7 @@ func idsForEventType(eventType string) []string {
}
func (u *eventHandler) reduce(e eventstore.Event) (*handler.Statement, error) {
- ctx := HandlerContext(e.Aggregate())
+ ctx := HandlerContext(context.Background(), e.Aggregate())
targets, err := u.query.TargetsByExecutionID(ctx, idsForEventType(string(e.Type())))
if err != nil {
@@ -125,8 +125,8 @@ func (u *eventHandler) reduce(e eventstore.Event) (*handler.Statement, error) {
return handler.NewNoOpStatement(e), nil
}
- return handler.NewStatement(e, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(e.Aggregate())
+ return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, e.Aggregate())
req, err := NewRequest(e, targets)
if err != nil {
return err
diff --git a/internal/execution/handlers_test.go b/internal/execution/handlers_test.go
index de220abcc0..a123947160 100644
--- a/internal/execution/handlers_test.go
+++ b/internal/execution/handlers_test.go
@@ -440,7 +440,7 @@ func TestActionProjection_reduces(t *testing.T) {
assert.Nil(t, stmt.Execute)
return
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.stmtErr != nil {
w.stmtErr(t, err)
return
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/notification/handlers/back_channel_logout.go b/internal/notification/handlers/back_channel_logout.go
index 983915ac28..12c2c708e9 100644
--- a/internal/notification/handlers/back_channel_logout.go
+++ b/internal/notification/handlers/back_channel_logout.go
@@ -95,8 +95,8 @@ func (u *backChannelLogoutNotifier) reduceUserSignedOut(event eventstore.Event)
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Gr63h", "reduce.wrong.event.type %s", user.HumanSignedOutType)
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx, err := u.queries.HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx, err := u.queries.HandlerContext(ctx, event.Aggregate())
if err != nil {
return err
}
@@ -116,8 +116,8 @@ func (u *backChannelLogoutNotifier) reduceSessionTerminated(event eventstore.Eve
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-D6H2h", "reduce.wrong.event.type %s", session.TerminateType)
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx, err := u.queries.HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx, err := u.queries.HandlerContext(ctx, event.Aggregate())
if err != nil {
return err
}
diff --git a/internal/notification/handlers/ctx.go b/internal/notification/handlers/ctx.go
index b091a61cdd..2c2599b060 100644
--- a/internal/notification/handlers/ctx.go
+++ b/internal/notification/handlers/ctx.go
@@ -9,8 +9,8 @@ import (
const NotifyUserID = "NOTIFICATION" //TODO: system?
-func HandlerContext(event *eventstore.Aggregate) context.Context {
- ctx := authz.WithInstanceID(context.Background(), event.InstanceID)
+func HandlerContext(parent context.Context, event *eventstore.Aggregate) context.Context {
+ ctx := authz.WithInstanceID(parent, event.InstanceID)
return authz.SetCtxData(ctx, authz.CtxData{UserID: NotifyUserID, OrgID: event.ResourceOwner})
}
@@ -18,12 +18,11 @@ func ContextWithNotifier(ctx context.Context, aggregate *eventstore.Aggregate) c
return authz.WithInstanceID(authz.SetCtxData(ctx, authz.CtxData{UserID: NotifyUserID, OrgID: aggregate.ResourceOwner}), aggregate.InstanceID)
}
-func (n *NotificationQueries) HandlerContext(event *eventstore.Aggregate) (context.Context, error) {
- ctx := context.Background()
- instance, err := n.InstanceByID(ctx, event.InstanceID)
+func (n *NotificationQueries) HandlerContext(parent context.Context, event *eventstore.Aggregate) (context.Context, error) {
+ instance, err := n.InstanceByID(parent, event.InstanceID)
if err != nil {
return nil, err
}
- ctx = authz.WithInstance(ctx, instance)
+ ctx := authz.WithInstance(parent, instance)
return authz.SetCtxData(ctx, authz.CtxData{UserID: NotifyUserID, OrgID: event.ResourceOwner}), nil
}
diff --git a/internal/notification/handlers/quota_notifier.go b/internal/notification/handlers/quota_notifier.go
index f308291243..365888959f 100644
--- a/internal/notification/handlers/quota_notifier.go
+++ b/internal/notification/handlers/quota_notifier.go
@@ -62,8 +62,8 @@ func (u *quotaNotifier) reduceNotificationDue(event eventstore.Event) (*handler.
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-DLxdE", "reduce.wrong.event.type %s", quota.NotificationDueEventType)
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.queries.IsAlreadyHandled(ctx, event, map[string]interface{}{"dueEventID": e.ID}, quota.NotifiedEventType)
if err != nil {
return err
diff --git a/internal/notification/handlers/telemetry_pusher.go b/internal/notification/handlers/telemetry_pusher.go
index 7e510a2b4c..2c32db61c1 100644
--- a/internal/notification/handlers/telemetry_pusher.go
+++ b/internal/notification/handlers/telemetry_pusher.go
@@ -5,7 +5,6 @@ import (
"net/http"
"time"
- "github.com/zitadel/zitadel/internal/api/call"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
@@ -69,12 +68,11 @@ func (t *telemetryPusher) Reducers() []handler.AggregateReducer {
}
func (t *telemetryPusher) pushMilestones(event eventstore.Event) (*handler.Statement, error) {
- ctx := call.WithTimestamp(context.Background())
e, ok := event.(*milestone.ReachedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-lDTs5", "reduce.wrong.event.type %s", event.Type())
}
- return handler.NewStatement(event, func(handler.Executer, string) error {
+ return handler.NewStatement(event, func(ctx context.Context, _ handler.Executer, _ string) error {
// Do not push the milestone again if this was a migration event.
if e.ReachedDate != nil {
return nil
diff --git a/internal/notification/handlers/user_notifier.go b/internal/notification/handlers/user_notifier.go
index f36f5d828c..6ca753caa9 100644
--- a/internal/notification/handlers/user_notifier.go
+++ b/internal/notification/handlers/user_notifier.go
@@ -203,8 +203,8 @@ func (u *userNotifier) reduceInitCodeAdded(event eventstore.Event) (*handler.Sta
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-EFe2f", "reduce.wrong.event.type %s", user.HumanInitialCodeAddedType)
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.UserV1InitialCodeAddedType, user.UserV1InitialCodeSentType,
user.HumanInitialCodeAddedType, user.HumanInitialCodeSentType)
@@ -253,8 +253,8 @@ func (u *userNotifier) reduceEmailCodeAdded(event eventstore.Event) (*handler.St
return handler.NewNoOpStatement(e), nil
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.UserV1EmailCodeAddedType, user.UserV1EmailCodeSentType,
user.HumanEmailCodeAddedType, user.HumanEmailCodeSentType)
@@ -309,8 +309,8 @@ func (u *userNotifier) reducePasswordCodeAdded(event eventstore.Event) (*handler
return handler.NewNoOpStatement(e), nil
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.UserV1PasswordCodeAddedType, user.UserV1PasswordCodeSentType,
user.HumanPasswordCodeAddedType, user.HumanPasswordCodeSentType)
@@ -362,8 +362,8 @@ func (u *userNotifier) reduceOTPSMSCodeAdded(event eventstore.Event) (*handler.S
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-ASF3g", "reduce.wrong.event.type %s", user.HumanOTPSMSCodeAddedType)
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.HumanOTPSMSCodeAddedType,
user.HumanOTPSMSCodeSentType)
@@ -406,8 +406,8 @@ func (u *userNotifier) reduceSessionOTPSMSChallenged(event eventstore.Event) (*h
return handler.NewNoOpStatement(e), nil
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
session.OTPSMSChallengedType,
session.OTPSMSSentType)
@@ -455,8 +455,8 @@ func (u *userNotifier) reduceOTPEmailCodeAdded(event eventstore.Event) (*handler
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-JL3hw", "reduce.wrong.event.type %s", user.HumanOTPEmailCodeAddedType)
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.HumanOTPEmailCodeAddedType,
user.HumanOTPEmailCodeSentType)
@@ -507,8 +507,8 @@ func (u *userNotifier) reduceSessionOTPEmailChallenged(event eventstore.Event) (
if e.ReturnCode {
return handler.NewNoOpStatement(e), nil
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
session.OTPEmailChallengedType,
session.OTPEmailSentType)
@@ -573,8 +573,8 @@ func (u *userNotifier) reduceDomainClaimed(event eventstore.Event) (*handler.Sta
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Drh5w", "reduce.wrong.event.type %s", user.UserDomainClaimedType)
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.queries.IsAlreadyHandled(ctx, event, nil,
user.UserDomainClaimedType, user.UserDomainClaimedSentType)
if err != nil {
@@ -619,8 +619,8 @@ func (u *userNotifier) reducePasswordlessCodeRequested(event eventstore.Event) (
return handler.NewNoOpStatement(e), nil
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, map[string]interface{}{"id": e.ID}, user.HumanPasswordlessInitCodeSentType)
if err != nil {
return err
@@ -668,8 +668,8 @@ func (u *userNotifier) reducePasswordChanged(event eventstore.Event) (*handler.S
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Yko2z8", "reduce.wrong.event.type %s", user.HumanPasswordChangedType)
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.queries.IsAlreadyHandled(ctx, event, nil, user.HumanPasswordChangeSentType)
if err != nil {
return err
@@ -720,8 +720,8 @@ func (u *userNotifier) reducePhoneCodeAdded(event eventstore.Event) (*handler.St
return handler.NewNoOpStatement(e), nil
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.UserV1PhoneCodeAddedType, user.UserV1PhoneCodeSentType,
user.HumanPhoneCodeAddedType, user.HumanPhoneCodeSentType)
@@ -768,8 +768,8 @@ func (u *userNotifier) reduceInviteCodeAdded(event eventstore.Event) (*handler.S
return handler.NewNoOpStatement(e), nil
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.HumanInviteCodeAddedType, user.HumanInviteCodeSentType)
if err != nil {
diff --git a/internal/notification/handlers/user_notifier_legacy.go b/internal/notification/handlers/user_notifier_legacy.go
index 1921510bf3..146c60e10b 100644
--- a/internal/notification/handlers/user_notifier_legacy.go
+++ b/internal/notification/handlers/user_notifier_legacy.go
@@ -133,8 +133,8 @@ func (u *userNotifierLegacy) reduceInitCodeAdded(event eventstore.Event) (*handl
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-EFe2f", "reduce.wrong.event.type %s", user.HumanInitialCodeAddedType)
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.UserV1InitialCodeAddedType, user.UserV1InitialCodeSentType,
user.HumanInitialCodeAddedType, user.HumanInitialCodeSentType)
@@ -194,8 +194,8 @@ func (u *userNotifierLegacy) reduceEmailCodeAdded(event eventstore.Event) (*hand
return handler.NewNoOpStatement(e), nil
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.UserV1EmailCodeAddedType, user.UserV1EmailCodeSentType,
user.HumanEmailCodeAddedType, user.HumanEmailCodeSentType)
@@ -254,8 +254,8 @@ func (u *userNotifierLegacy) reducePasswordCodeAdded(event eventstore.Event) (*h
return handler.NewNoOpStatement(e), nil
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.UserV1PasswordCodeAddedType, user.UserV1PasswordCodeSentType,
user.HumanPasswordCodeAddedType, user.HumanPasswordCodeSentType)
@@ -337,7 +337,7 @@ func (u *userNotifierLegacy) reduceSessionOTPSMSChallenged(event eventstore.Even
if e.CodeReturned {
return handler.NewNoOpStatement(e), nil
}
- ctx := HandlerContext(event.Aggregate())
+ ctx := HandlerContext(context.Background(), event.Aggregate())
s, err := u.queries.SessionByID(ctx, true, e.Aggregate().ID, "", nil)
if err != nil {
return nil, err
@@ -363,7 +363,7 @@ func (u *userNotifierLegacy) reduceOTPSMS(
sentCommand func(ctx context.Context, userID, resourceOwner string, generatorInfo *senders.CodeGeneratorInfo) (err error),
eventTypes ...eventstore.EventType,
) (*handler.Statement, error) {
- ctx := HandlerContext(event.Aggregate())
+ ctx := HandlerContext(context.Background(), event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, expiry, nil, eventTypes...)
if err != nil {
return nil, err
@@ -445,7 +445,7 @@ func (u *userNotifierLegacy) reduceSessionOTPEmailChallenged(event eventstore.Ev
if e.ReturnCode {
return handler.NewNoOpStatement(e), nil
}
- ctx := HandlerContext(event.Aggregate())
+ ctx := HandlerContext(context.Background(), event.Aggregate())
s, err := u.queries.SessionByID(ctx, true, e.Aggregate().ID, "", nil)
if err != nil {
return nil, err
@@ -484,7 +484,7 @@ func (u *userNotifierLegacy) reduceOTPEmail(
sentCommand func(ctx context.Context, userID string, resourceOwner string) (err error),
eventTypes ...eventstore.EventType,
) (*handler.Statement, error) {
- ctx := HandlerContext(event.Aggregate())
+ ctx := HandlerContext(context.Background(), event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, expiry, nil, eventTypes...)
if err != nil {
return nil, err
@@ -543,8 +543,8 @@ func (u *userNotifierLegacy) reduceDomainClaimed(event eventstore.Event) (*handl
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Drh5w", "reduce.wrong.event.type %s", user.UserDomainClaimedType)
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.queries.IsAlreadyHandled(ctx, event, nil,
user.UserDomainClaimedType, user.UserDomainClaimedSentType)
if err != nil {
@@ -598,8 +598,8 @@ func (u *userNotifierLegacy) reducePasswordlessCodeRequested(event eventstore.Ev
return handler.NewNoOpStatement(e), nil
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, map[string]interface{}{"id": e.ID}, user.HumanPasswordlessInitCodeSentType)
if err != nil {
return err
@@ -653,8 +653,8 @@ func (u *userNotifierLegacy) reducePasswordChanged(event eventstore.Event) (*han
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-Yko2z8", "reduce.wrong.event.type %s", user.HumanPasswordChangedType)
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.queries.IsAlreadyHandled(ctx, event, nil, user.HumanPasswordChangeSentType)
if err != nil {
return err
@@ -719,8 +719,8 @@ func (u *userNotifierLegacy) reducePhoneCodeAdded(event eventstore.Event) (*hand
return handler.NewNoOpStatement(e), nil
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.UserV1PhoneCodeAddedType, user.UserV1PhoneCodeSentType,
user.HumanPhoneCodeAddedType, user.HumanPhoneCodeSentType)
@@ -777,8 +777,8 @@ func (u *userNotifierLegacy) reduceInviteCodeAdded(event eventstore.Event) (*han
return handler.NewNoOpStatement(e), nil
}
- return handler.NewStatement(event, func(ex handler.Executer, projectionName string) error {
- ctx := HandlerContext(event.Aggregate())
+ return handler.NewStatement(event, func(ctx context.Context, ex handler.Executer, projectionName string) error {
+ ctx = HandlerContext(ctx, event.Aggregate())
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
user.HumanInviteCodeAddedType, user.HumanInviteCodeSentType)
if err != nil {
diff --git a/internal/notification/handlers/user_notifier_legacy_test.go b/internal/notification/handlers/user_notifier_legacy_test.go
index a4c24fd196..08461ee706 100644
--- a/internal/notification/handlers/user_notifier_legacy_test.go
+++ b/internal/notification/handlers/user_notifier_legacy_test.go
@@ -283,7 +283,7 @@ func Test_userNotifierLegacy_reduceInitCodeAdded(t *testing.T) {
} else {
assert.NoError(t, err)
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
@@ -596,7 +596,7 @@ func Test_userNotifierLegacy_reduceEmailCodeAdded(t *testing.T) {
} else {
assert.NoError(t, err)
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
@@ -949,7 +949,7 @@ func Test_userNotifierLegacy_reducePasswordCodeAdded(t *testing.T) {
} else {
assert.NoError(t, err)
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
@@ -1080,7 +1080,7 @@ func Test_userNotifierLegacy_reduceDomainClaimed(t *testing.T) {
} else {
assert.NoError(t, err)
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
@@ -1355,7 +1355,7 @@ func Test_userNotifierLegacy_reducePasswordlessCodeRequested(t *testing.T) {
} else {
assert.NoError(t, err)
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
@@ -1495,7 +1495,7 @@ func Test_userNotifierLegacy_reducePasswordChanged(t *testing.T) {
} else {
assert.NoError(t, err)
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
diff --git a/internal/notification/handlers/user_notifier_test.go b/internal/notification/handlers/user_notifier_test.go
index f7090f0146..874fbdf9af 100644
--- a/internal/notification/handlers/user_notifier_test.go
+++ b/internal/notification/handlers/user_notifier_test.go
@@ -188,7 +188,7 @@ func Test_userNotifier_reduceInitCodeAdded(t *testing.T) {
} else {
assert.NoError(t, err)
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
@@ -366,7 +366,7 @@ func Test_userNotifier_reduceEmailCodeAdded(t *testing.T) {
assert.Nil(t, stmt.Execute)
return
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
@@ -601,7 +601,7 @@ func Test_userNotifier_reducePasswordCodeAdded(t *testing.T) {
assert.Nil(t, stmt.Execute)
return
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
@@ -731,7 +731,7 @@ func Test_userNotifier_reduceDomainClaimed(t *testing.T) {
} else {
assert.NoError(t, err)
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
@@ -906,7 +906,7 @@ func Test_userNotifier_reducePasswordlessCodeRequested(t *testing.T) {
assert.Nil(t, stmt.Execute)
return
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
@@ -1066,7 +1066,7 @@ func Test_userNotifier_reducePasswordChanged(t *testing.T) {
} else {
assert.NoError(t, err)
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
@@ -1329,7 +1329,7 @@ func Test_userNotifier_reduceOTPEmailChallenged(t *testing.T) {
assert.Nil(t, stmt.Execute)
return
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
@@ -1590,7 +1590,7 @@ func Test_userNotifier_reduceOTPSMSChallenged(t *testing.T) {
assert.Nil(t, stmt.Execute)
return
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
@@ -1886,7 +1886,7 @@ func Test_userNotifier_reduceInviteCodeAdded(t *testing.T) {
assert.Nil(t, stmt.Execute)
return
}
- err = stmt.Execute(nil, "")
+ err = stmt.Execute(t.Context(), nil, "")
if w.err != nil {
w.err(t, err)
} else {
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/event_test.go b/internal/query/projection/event_test.go
index 073f34c688..317efe817e 100644
--- a/internal/query/projection/event_test.go
+++ b/internal/query/projection/event_test.go
@@ -99,7 +99,7 @@ func assertReduce(t *testing.T, stmt *handler.Statement, err error, projection s
want.executer.Validate(t)
return
}
- err = stmt.Execute(want.executer, projection)
+ err = stmt.Execute(t.Context(), want.executer, projection)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
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 4b9062a897..709a4851f8 100644
--- a/internal/query/projection/projection.go
+++ b/internal/query/projection/projection.go
@@ -89,6 +89,7 @@ var (
WebKeyProjection *handler.Handler
DebugEventsProjection *handler.Handler
HostedLoginTranslationProjection *handler.Handler
+ OrganizationSettingsProjection *handler.Handler
ProjectGrantFields *handler.FieldHandler
OrgDomainVerifiedFields *handler.FieldHandler
@@ -185,6 +186,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]))
@@ -366,5 +368,6 @@ func newProjectionsList() {
WebKeyProjection,
DebugEventsProjection,
HostedLoginTranslationProjection,
+ OrganizationSettingsProjection,
}
}
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;
+}