Compare commits
	
		
			63 Commits
		
	
	
		
			version/20
			...
			version/20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a15a040362 | |||
| fcd6dc1d60 | |||
| acc3b59869 | |||
| d9d5ac10e6 | |||
| 750669dcab | |||
| 88a3eed67e | |||
| 6c214fffc4 | |||
| 70100fc105 | |||
| 3c1163fabd | |||
| 539e8242ff | |||
| 2648333590 | |||
| fe828ef993 | |||
| 29a6530742 | |||
| a6b9274c4f | |||
| a2a67161ac | |||
| 2e8263a99b | |||
| 6b9afed21f | |||
| 1eb1f4e0b8 | |||
| 7c3d60ec3a | |||
| a494c6b6e8 | |||
| 6604d3577f | |||
| f8bfa7e16a | |||
| ea6cf6eabf | |||
| 769ce3ce7b | |||
| 3891fb3fa8 | |||
| 41eb965350 | |||
| 8d95612287 | |||
| 82b5274b15 | |||
| af56ce3d78 | |||
| f5c6e7aeb0 | |||
| 3809400e93 | |||
| 1def9865cf | |||
| 3716298639 | |||
| c16317d7cf | |||
| bbb8fa8269 | |||
| e4c251a178 | |||
| 0fefd5f522 | |||
| 88057db0b0 | |||
| 91cb6c9beb | |||
| 8e72fcab59 | |||
| 261879022d | |||
| 2a47ff2977 | |||
| c3a81a1cce | |||
| 220d739fef | |||
| 4a57c6f230 | |||
| 4a93b97bec | |||
| ac2bbd7e2f | |||
| ad9f500ad1 | |||
| 15d7175750 | |||
| 41d372a340 | |||
| 83b84e8d26 | |||
| f22daca091 | |||
| ae4d5a30f2 | |||
| 9708481005 | |||
| 1c32c9e06d | |||
| 7a3d92ffdb | |||
| a72b36d94d | |||
| 6b25f6f592 | |||
| 7d91842e8a | |||
| 2b4b1d2f76 | |||
| 2ce5c74f33 | |||
| 168fabfc70 | |||
| eb53c28352 | 
| @ -1,5 +1,5 @@ | |||||||
| [bumpversion] | [bumpversion] | ||||||
| current_version = 2023.10.1 | current_version = 2023.10.5 | ||||||
| tag = True | tag = True | ||||||
| commit = True | commit = True | ||||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+) | parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+) | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								.github/actions/setup/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								.github/actions/setup/action.yml
									
									
									
									
										vendored
									
									
								
							| @ -2,36 +2,39 @@ name: "Setup authentik testing environment" | |||||||
| description: "Setup authentik testing environment" | description: "Setup authentik testing environment" | ||||||
|  |  | ||||||
| inputs: | inputs: | ||||||
|   postgresql_tag: |   postgresql_version: | ||||||
|     description: "Optional postgresql image tag" |     description: "Optional postgresql image tag" | ||||||
|     default: "12" |     default: "12" | ||||||
|  |  | ||||||
| runs: | runs: | ||||||
|   using: "composite" |   using: "composite" | ||||||
|   steps: |   steps: | ||||||
|     - name: Install poetry |     - name: Install poetry & deps | ||||||
|       shell: bash |       shell: bash | ||||||
|       run: | |       run: | | ||||||
|         pipx install poetry || true |         pipx install poetry || true | ||||||
|         sudo apt update |         sudo apt-get update | ||||||
|         sudo apt install -y libpq-dev openssl libxmlsec1-dev pkg-config gettext |         sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext | ||||||
|     - name: Setup python and restore poetry |     - name: Setup python and restore poetry | ||||||
|       uses: actions/setup-python@v3 |       uses: actions/setup-python@v4 | ||||||
|       with: |       with: | ||||||
|         python-version: "3.11" |         python-version-file: 'pyproject.toml' | ||||||
|         cache: "poetry" |         cache: "poetry" | ||||||
|     - name: Setup node |     - name: Setup node | ||||||
|       uses: actions/setup-node@v3 |       uses: actions/setup-node@v3 | ||||||
|       with: |       with: | ||||||
|         node-version: "20" |         node-version-file: web/package.json | ||||||
|         cache: "npm" |         cache: "npm" | ||||||
|         cache-dependency-path: web/package-lock.json |         cache-dependency-path: web/package-lock.json | ||||||
|  |     - name: Setup go | ||||||
|  |       uses: actions/setup-go@v4 | ||||||
|  |       with: | ||||||
|  |         go-version-file: "go.mod" | ||||||
|     - name: Setup dependencies |     - name: Setup dependencies | ||||||
|       shell: bash |       shell: bash | ||||||
|       run: | |       run: | | ||||||
|         export PSQL_TAG=${{ inputs.postgresql_tag }} |         export PSQL_TAG=${{ inputs.postgresql_version }} | ||||||
|         docker-compose -f .github/actions/setup/docker-compose.yml up -d |         docker-compose -f .github/actions/setup/docker-compose.yml up -d | ||||||
|         poetry env use python3.11 |  | ||||||
|         poetry install |         poetry install | ||||||
|         cd web && npm ci |         cd web && npm ci | ||||||
|     - name: Generate config |     - name: Generate config | ||||||
|  | |||||||
							
								
								
									
										30
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							| @ -11,6 +11,7 @@ on: | |||||||
|   pull_request: |   pull_request: | ||||||
|     branches: |     branches: | ||||||
|       - main |       - main | ||||||
|  |       - version-* | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   POSTGRES_DB: authentik |   POSTGRES_DB: authentik | ||||||
| @ -47,25 +48,38 @@ jobs: | |||||||
|       - name: run migrations |       - name: run migrations | ||||||
|         run: poetry run python -m lifecycle.migrate |         run: poetry run python -m lifecycle.migrate | ||||||
|   test-migrations-from-stable: |   test-migrations-from-stable: | ||||||
|  |     name: test-migrations-from-stable - PostgreSQL ${{ matrix.psql }} | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     continue-on-error: true |     strategy: | ||||||
|  |       fail-fast: false | ||||||
|  |       matrix: | ||||||
|  |         psql: | ||||||
|  |           - 12-alpine | ||||||
|  |           - 15-alpine | ||||||
|  |           - 16-alpine | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|       - name: Setup authentik env |       - name: Setup authentik env | ||||||
|         uses: ./.github/actions/setup |         uses: ./.github/actions/setup | ||||||
|  |         with: | ||||||
|  |           postgresql_version: ${{ matrix.psql }} | ||||||
|       - name: checkout stable |       - name: checkout stable | ||||||
|         run: | |         run: | | ||||||
|  |           # Delete all poetry envs | ||||||
|  |           rm -rf /home/runner/.cache/pypoetry | ||||||
|           # Copy current, latest config to local |           # Copy current, latest config to local | ||||||
|           cp authentik/lib/default.yml local.env.yml |           cp authentik/lib/default.yml local.env.yml | ||||||
|           cp -R .github .. |           cp -R .github .. | ||||||
|           cp -R scripts .. |           cp -R scripts .. | ||||||
|           git checkout $(git describe --tags $(git rev-list --tags --max-count=1)) |           git checkout version/$(python -c "from authentik import __version__; print(__version__)") | ||||||
|           rm -rf .github/ scripts/ |           rm -rf .github/ scripts/ | ||||||
|           mv ../.github ../scripts . |           mv ../.github ../scripts . | ||||||
|       - name: Setup authentik env (ensure stable deps are installed) |       - name: Setup authentik env (ensure stable deps are installed) | ||||||
|         uses: ./.github/actions/setup |         uses: ./.github/actions/setup | ||||||
|  |         with: | ||||||
|  |           postgresql_version: ${{ matrix.psql }} | ||||||
|       - name: run migrations to stable |       - name: run migrations to stable | ||||||
|         run: poetry run python -m lifecycle.migrate |         run: poetry run python -m lifecycle.migrate | ||||||
|       - name: checkout current code |       - name: checkout current code | ||||||
| @ -75,9 +89,13 @@ jobs: | |||||||
|           git reset --hard HEAD |           git reset --hard HEAD | ||||||
|           git clean -d -fx . |           git clean -d -fx . | ||||||
|           git checkout $GITHUB_SHA |           git checkout $GITHUB_SHA | ||||||
|  |           # Delete previous poetry env | ||||||
|  |           rm -rf $(poetry env info --path) | ||||||
|           poetry install |           poetry install | ||||||
|       - name: Setup authentik env (ensure latest deps are installed) |       - name: Setup authentik env (ensure latest deps are installed) | ||||||
|         uses: ./.github/actions/setup |         uses: ./.github/actions/setup | ||||||
|  |         with: | ||||||
|  |           postgresql_version: ${{ matrix.psql }} | ||||||
|       - name: migrate to latest |       - name: migrate to latest | ||||||
|         run: poetry run python -m lifecycle.migrate |         run: poetry run python -m lifecycle.migrate | ||||||
|   test-unittest: |   test-unittest: | ||||||
| @ -96,7 +114,7 @@ jobs: | |||||||
|       - name: Setup authentik env |       - name: Setup authentik env | ||||||
|         uses: ./.github/actions/setup |         uses: ./.github/actions/setup | ||||||
|         with: |         with: | ||||||
|           postgresql_tag: ${{ matrix.psql }} |           postgresql_version: ${{ matrix.psql }} | ||||||
|       - name: run unittest |       - name: run unittest | ||||||
|         run: | |         run: | | ||||||
|           poetry run make test |           poetry run make test | ||||||
| @ -185,6 +203,9 @@ jobs: | |||||||
|   build: |   build: | ||||||
|     needs: ci-core-mark |     needs: ci-core-mark | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |     permissions: | ||||||
|  |       # Needed to upload contianer images to ghcr.io | ||||||
|  |       packages: write | ||||||
|     timeout-minutes: 120 |     timeout-minutes: 120 | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
| @ -235,6 +256,9 @@ jobs: | |||||||
|   build-arm64: |   build-arm64: | ||||||
|     needs: ci-core-mark |     needs: ci-core-mark | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |     permissions: | ||||||
|  |       # Needed to upload contianer images to ghcr.io | ||||||
|  |       packages: write | ||||||
|     timeout-minutes: 120 |     timeout-minutes: 120 | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								.github/workflows/ci-outpost.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/ci-outpost.yml
									
									
									
									
										vendored
									
									
								
							| @ -9,6 +9,7 @@ on: | |||||||
|   pull_request: |   pull_request: | ||||||
|     branches: |     branches: | ||||||
|       - main |       - main | ||||||
|  |       - version-* | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   lint-golint: |   lint-golint: | ||||||
| @ -65,6 +66,9 @@ jobs: | |||||||
|           - ldap |           - ldap | ||||||
|           - radius |           - radius | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |     permissions: | ||||||
|  |       # Needed to upload contianer images to ghcr.io | ||||||
|  |       packages: write | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|         with: |         with: | ||||||
| @ -126,7 +130,7 @@ jobs: | |||||||
|           go-version-file: "go.mod" |           go-version-file: "go.mod" | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: "20" |           node-version-file: web/package.json | ||||||
|           cache: "npm" |           cache: "npm" | ||||||
|           cache-dependency-path: web/package-lock.json |           cache-dependency-path: web/package-lock.json | ||||||
|       - name: Generate API |       - name: Generate API | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								.github/workflows/ci-web.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/ci-web.yml
									
									
									
									
										vendored
									
									
								
							| @ -9,6 +9,7 @@ on: | |||||||
|   pull_request: |   pull_request: | ||||||
|     branches: |     branches: | ||||||
|       - main |       - main | ||||||
|  |       - version-* | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   lint-eslint: |   lint-eslint: | ||||||
| @ -23,7 +24,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: "20" |           node-version-file: ${{ matrix.project }}/package.json | ||||||
|           cache: "npm" |           cache: "npm" | ||||||
|           cache-dependency-path: ${{ matrix.project }}/package-lock.json |           cache-dependency-path: ${{ matrix.project }}/package-lock.json | ||||||
|       - working-directory: ${{ matrix.project }}/ |       - working-directory: ${{ matrix.project }}/ | ||||||
| @ -39,7 +40,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: "20" |           node-version-file: web/package.json | ||||||
|           cache: "npm" |           cache: "npm" | ||||||
|           cache-dependency-path: web/package-lock.json |           cache-dependency-path: web/package-lock.json | ||||||
|       - working-directory: web/ |       - working-directory: web/ | ||||||
| @ -61,7 +62,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: "20" |           node-version-file: ${{ matrix.project }}/package.json | ||||||
|           cache: "npm" |           cache: "npm" | ||||||
|           cache-dependency-path: ${{ matrix.project }}/package-lock.json |           cache-dependency-path: ${{ matrix.project }}/package-lock.json | ||||||
|       - working-directory: ${{ matrix.project }}/ |       - working-directory: ${{ matrix.project }}/ | ||||||
| @ -77,7 +78,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: "20" |           node-version-file: web/package.json | ||||||
|           cache: "npm" |           cache: "npm" | ||||||
|           cache-dependency-path: web/package-lock.json |           cache-dependency-path: web/package-lock.json | ||||||
|       - working-directory: web/ |       - working-directory: web/ | ||||||
| @ -109,7 +110,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: "20" |           node-version-file: web/package.json | ||||||
|           cache: "npm" |           cache: "npm" | ||||||
|           cache-dependency-path: web/package-lock.json |           cache-dependency-path: web/package-lock.json | ||||||
|       - working-directory: web/ |       - working-directory: web/ | ||||||
|  | |||||||
							
								
								
									
										7
									
								
								.github/workflows/ci-website.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/ci-website.yml
									
									
									
									
										vendored
									
									
								
							| @ -9,6 +9,7 @@ on: | |||||||
|   pull_request: |   pull_request: | ||||||
|     branches: |     branches: | ||||||
|       - main |       - main | ||||||
|  |       - version-* | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   lint-prettier: |   lint-prettier: | ||||||
| @ -17,7 +18,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: "20" |           node-version-file: website/package.json | ||||||
|           cache: "npm" |           cache: "npm" | ||||||
|           cache-dependency-path: website/package-lock.json |           cache-dependency-path: website/package-lock.json | ||||||
|       - working-directory: website/ |       - working-directory: website/ | ||||||
| @ -31,7 +32,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: "20" |           node-version-file: website/package.json | ||||||
|           cache: "npm" |           cache: "npm" | ||||||
|           cache-dependency-path: website/package-lock.json |           cache-dependency-path: website/package-lock.json | ||||||
|       - working-directory: website/ |       - working-directory: website/ | ||||||
| @ -52,7 +53,7 @@ jobs: | |||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: "20" |           node-version-file: website/package.json | ||||||
|           cache: "npm" |           cache: "npm" | ||||||
|           cache-dependency-path: website/package-lock.json |           cache-dependency-path: website/package-lock.json | ||||||
|       - working-directory: website/ |       - working-directory: website/ | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								.github/workflows/release-next-branch.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/release-next-branch.yml
									
									
									
									
										vendored
									
									
								
							| @ -6,6 +6,7 @@ on: | |||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
|  |  | ||||||
| permissions: | permissions: | ||||||
|  |   # Needed to be able to push to the next branch | ||||||
|   contents: write |   contents: write | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							| @ -7,6 +7,9 @@ on: | |||||||
| jobs: | jobs: | ||||||
|   build-server: |   build-server: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |     permissions: | ||||||
|  |       # Needed to upload contianer images to ghcr.io | ||||||
|  |       packages: write | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Set up QEMU |       - name: Set up QEMU | ||||||
| @ -52,6 +55,9 @@ jobs: | |||||||
|             VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }} |             VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }} | ||||||
|   build-outpost: |   build-outpost: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |     permissions: | ||||||
|  |       # Needed to upload contianer images to ghcr.io | ||||||
|  |       packages: write | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
| @ -106,6 +112,9 @@ jobs: | |||||||
|   build-outpost-binary: |   build-outpost-binary: | ||||||
|     timeout-minutes: 120 |     timeout-minutes: 120 | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |     permissions: | ||||||
|  |       # Needed to upload binaries to the release | ||||||
|  |       contents: write | ||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
| @ -122,7 +131,7 @@ jobs: | |||||||
|           go-version-file: "go.mod" |           go-version-file: "go.mod" | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: "20" |           node-version-file: web/package.json | ||||||
|           cache: "npm" |           cache: "npm" | ||||||
|           cache-dependency-path: web/package-lock.json |           cache-dependency-path: web/package-lock.json | ||||||
|       - name: Build web |       - name: Build web | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								.github/workflows/repo-stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/repo-stale.yml
									
									
									
									
										vendored
									
									
								
							| @ -6,8 +6,8 @@ on: | |||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
|  |  | ||||||
| permissions: | permissions: | ||||||
|  |   # Needed to update issues and PRs | ||||||
|   issues: write |   issues: write | ||||||
|   pull-requests: write |  | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   stale: |   stale: | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								.github/workflows/web-api-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/web-api-publish.yml
									
									
									
									
										vendored
									
									
								
							| @ -19,7 +19,7 @@ jobs: | |||||||
|           token: ${{ steps.generate_token.outputs.token }} |           token: ${{ steps.generate_token.outputs.token }} | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: "20" |           node-version-file: web/package.json | ||||||
|           registry-url: "https://registry.npmjs.org" |           registry-url: "https://registry.npmjs.org" | ||||||
|       - name: Generate API Client |       - name: Generate API Client | ||||||
|         run: make gen-client-ts |         run: make gen-client-ts | ||||||
|  | |||||||
							
								
								
									
										27
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								Dockerfile
									
									
									
									
									
								
							| @ -1,3 +1,5 @@ | |||||||
|  | # syntax=docker/dockerfile:1 | ||||||
|  |  | ||||||
| # Stage 1: Build website | # Stage 1: Build website | ||||||
| FROM --platform=${BUILDPLATFORM} docker.io/node:21 as website-builder | FROM --platform=${BUILDPLATFORM} docker.io/node:21 as website-builder | ||||||
|  |  | ||||||
| @ -7,7 +9,7 @@ WORKDIR /work/website | |||||||
|  |  | ||||||
| RUN --mount=type=bind,target=/work/website/package.json,src=./website/package.json \ | RUN --mount=type=bind,target=/work/website/package.json,src=./website/package.json \ | ||||||
|     --mount=type=bind,target=/work/website/package-lock.json,src=./website/package-lock.json \ |     --mount=type=bind,target=/work/website/package-lock.json,src=./website/package-lock.json \ | ||||||
|     --mount=type=cache,target=/root/.npm \ |     --mount=type=cache,id=npm-website,sharing=shared,target=/root/.npm \ | ||||||
|     npm ci --include=dev |     npm ci --include=dev | ||||||
|  |  | ||||||
| COPY ./website /work/website/ | COPY ./website /work/website/ | ||||||
| @ -25,7 +27,7 @@ WORKDIR /work/web | |||||||
|  |  | ||||||
| RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \ | RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \ | ||||||
|     --mount=type=bind,target=/work/web/package-lock.json,src=./web/package-lock.json \ |     --mount=type=bind,target=/work/web/package-lock.json,src=./web/package-lock.json \ | ||||||
|     --mount=type=cache,target=/root/.npm \ |     --mount=type=cache,id=npm-web,sharing=shared,target=/root/.npm \ | ||||||
|     npm ci --include=dev |     npm ci --include=dev | ||||||
|  |  | ||||||
| COPY ./web /work/web/ | COPY ./web /work/web/ | ||||||
| @ -35,7 +37,14 @@ COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api | |||||||
| RUN npm run build | RUN npm run build | ||||||
|  |  | ||||||
| # Stage 3: Build go proxy | # Stage 3: Build go proxy | ||||||
| FROM docker.io/golang:1.21.3-bookworm AS go-builder | FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.4-bookworm AS go-builder | ||||||
|  |  | ||||||
|  | ARG TARGETOS | ||||||
|  | ARG TARGETARCH | ||||||
|  | ARG TARGETVARIANT | ||||||
|  |  | ||||||
|  | ARG GOOS=$TARGETOS | ||||||
|  | ARG GOARCH=$TARGETARCH | ||||||
|  |  | ||||||
| WORKDIR /go/src/goauthentik.io | WORKDIR /go/src/goauthentik.io | ||||||
|  |  | ||||||
| @ -55,12 +64,12 @@ COPY ./go.sum /go/src/goauthentik.io/go.sum | |||||||
|  |  | ||||||
| ENV CGO_ENABLED=0 | ENV CGO_ENABLED=0 | ||||||
|  |  | ||||||
| RUN --mount=type=cache,target=/go/pkg/mod \ | RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \ | ||||||
|     --mount=type=cache,target=/root/.cache/go-build \ |     --mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \ | ||||||
|     go build -o /go/authentik ./cmd/server |     GOARM="${TARGETVARIANT#v}" go build -o /go/authentik ./cmd/server | ||||||
|  |  | ||||||
| # Stage 4: MaxMind GeoIP | # Stage 4: MaxMind GeoIP | ||||||
| FROM ghcr.io/maxmind/geoipupdate:v6.0 as geoip | FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v6.0 as geoip | ||||||
|  |  | ||||||
| ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City" | ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City" | ||||||
| ENV GEOIPUPDATE_VERBOSE="true" | ENV GEOIPUPDATE_VERBOSE="true" | ||||||
| @ -82,7 +91,9 @@ ENV VENV_PATH="/ak-root/venv" \ | |||||||
|     POETRY_VIRTUALENVS_CREATE=false \ |     POETRY_VIRTUALENVS_CREATE=false \ | ||||||
|     PATH="/ak-root/venv/bin:$PATH" |     PATH="/ak-root/venv/bin:$PATH" | ||||||
|  |  | ||||||
| RUN --mount=type=cache,target=/var/cache/apt \ | RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache | ||||||
|  |  | ||||||
|  | RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \ | ||||||
|     apt-get update && \ |     apt-get update && \ | ||||||
|     # Required for installing pip packages |     # Required for installing pip packages | ||||||
|     apt-get install -y --no-install-recommends build-essential pkg-config libxmlsec1-dev zlib1g-dev libpq-dev |     apt-get install -y --no-install-recommends build-essential pkg-config libxmlsec1-dev zlib1g-dev libpq-dev | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| from os import environ | from os import environ | ||||||
| from typing import Optional | from typing import Optional | ||||||
|  |  | ||||||
| __version__ = "2023.10.1" | __version__ = "2023.10.5" | ||||||
| ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -21,7 +21,9 @@ _other_urls = [] | |||||||
| for _authentik_app in get_apps(): | for _authentik_app in get_apps(): | ||||||
|     try: |     try: | ||||||
|         api_urls = import_module(f"{_authentik_app.name}.urls") |         api_urls = import_module(f"{_authentik_app.name}.urls") | ||||||
|     except (ModuleNotFoundError, ImportError) as exc: |     except ModuleNotFoundError: | ||||||
|  |         continue | ||||||
|  |     except ImportError as exc: | ||||||
|         LOGGER.warning("Could not import app's URLs", app_name=_authentik_app.name, exc=exc) |         LOGGER.warning("Could not import app's URLs", app_name=_authentik_app.name, exc=exc) | ||||||
|         continue |         continue | ||||||
|     if not hasattr(api_urls, "api_urlpatterns"): |     if not hasattr(api_urls, "api_urlpatterns"): | ||||||
|  | |||||||
| @ -584,12 +584,17 @@ class EntryInvalidError(SentryIgnoredException): | |||||||
|     entry_model: Optional[str] |     entry_model: Optional[str] | ||||||
|     entry_id: Optional[str] |     entry_id: Optional[str] | ||||||
|     validation_error: Optional[ValidationError] |     validation_error: Optional[ValidationError] | ||||||
|  |     serializer: Optional[Serializer] = None | ||||||
|  |  | ||||||
|     def __init__(self, *args: object, validation_error: Optional[ValidationError] = None) -> None: |     def __init__( | ||||||
|  |         self, *args: object, validation_error: Optional[ValidationError] = None, **kwargs | ||||||
|  |     ) -> None: | ||||||
|         super().__init__(*args) |         super().__init__(*args) | ||||||
|         self.entry_model = None |         self.entry_model = None | ||||||
|         self.entry_id = None |         self.entry_id = None | ||||||
|         self.validation_error = validation_error |         self.validation_error = validation_error | ||||||
|  |         for key, value in kwargs.items(): | ||||||
|  |             setattr(self, key, value) | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def from_entry( |     def from_entry( | ||||||
|  | |||||||
| @ -255,7 +255,10 @@ class Importer: | |||||||
|         try: |         try: | ||||||
|             full_data = self.__update_pks_for_attrs(entry.get_attrs(self._import)) |             full_data = self.__update_pks_for_attrs(entry.get_attrs(self._import)) | ||||||
|         except ValueError as exc: |         except ValueError as exc: | ||||||
|             raise EntryInvalidError.from_entry(exc, entry) from exc |             raise EntryInvalidError.from_entry( | ||||||
|  |                 exc, | ||||||
|  |                 entry, | ||||||
|  |             ) from exc | ||||||
|         always_merger.merge(full_data, updated_identifiers) |         always_merger.merge(full_data, updated_identifiers) | ||||||
|         serializer_kwargs["data"] = full_data |         serializer_kwargs["data"] = full_data | ||||||
|  |  | ||||||
| @ -272,6 +275,7 @@ class Importer: | |||||||
|                 f"Serializer errors {serializer.errors}", |                 f"Serializer errors {serializer.errors}", | ||||||
|                 validation_error=exc, |                 validation_error=exc, | ||||||
|                 entry=entry, |                 entry=entry, | ||||||
|  |                 serializer=serializer, | ||||||
|             ) from exc |             ) from exc | ||||||
|         return serializer |         return serializer | ||||||
|  |  | ||||||
| @ -300,12 +304,14 @@ class Importer: | |||||||
|                 ) |                 ) | ||||||
|                 return False |                 return False | ||||||
|             # Validate each single entry |             # Validate each single entry | ||||||
|  |             serializer = None | ||||||
|             try: |             try: | ||||||
|                 serializer = self._validate_single(entry) |                 serializer = self._validate_single(entry) | ||||||
|             except EntryInvalidError as exc: |             except EntryInvalidError as exc: | ||||||
|                 # For deleting objects we don't need the serializer to be valid |                 # For deleting objects we don't need the serializer to be valid | ||||||
|                 if entry.get_state(self._import) == BlueprintEntryDesiredState.ABSENT: |                 if entry.get_state(self._import) == BlueprintEntryDesiredState.ABSENT: | ||||||
|                     continue |                     serializer = exc.serializer | ||||||
|  |                 else: | ||||||
|                     self.logger.warning(f"entry invalid: {exc}", entry=entry, error=exc) |                     self.logger.warning(f"entry invalid: {exc}", entry=entry, error=exc) | ||||||
|                     if raise_errors: |                     if raise_errors: | ||||||
|                         raise exc |                         raise exc | ||||||
|  | |||||||
| @ -75,14 +75,14 @@ class BlueprintEventHandler(FileSystemEventHandler): | |||||||
|             return |             return | ||||||
|         if event.is_directory: |         if event.is_directory: | ||||||
|             return |             return | ||||||
|         if isinstance(event, FileCreatedEvent): |  | ||||||
|             LOGGER.debug("new blueprint file created, starting discovery") |  | ||||||
|             blueprints_discovery.delay() |  | ||||||
|         if isinstance(event, FileModifiedEvent): |  | ||||||
|             path = Path(event.src_path) |  | ||||||
|         root = Path(CONFIG.get("blueprints_dir")).absolute() |         root = Path(CONFIG.get("blueprints_dir")).absolute() | ||||||
|  |         path = Path(event.src_path).absolute() | ||||||
|         rel_path = str(path.relative_to(root)) |         rel_path = str(path.relative_to(root)) | ||||||
|             for instance in BlueprintInstance.objects.filter(path=rel_path): |         if isinstance(event, FileCreatedEvent): | ||||||
|  |             LOGGER.debug("new blueprint file created, starting discovery", path=rel_path) | ||||||
|  |             blueprints_discovery.delay(rel_path) | ||||||
|  |         if isinstance(event, FileModifiedEvent): | ||||||
|  |             for instance in BlueprintInstance.objects.filter(path=rel_path, enabled=True): | ||||||
|                 LOGGER.debug("modified blueprint file, starting apply", instance=instance) |                 LOGGER.debug("modified blueprint file, starting apply", instance=instance) | ||||||
|                 apply_blueprint.delay(instance.pk.hex) |                 apply_blueprint.delay(instance.pk.hex) | ||||||
|  |  | ||||||
| @ -98,39 +98,32 @@ def blueprints_find_dict(): | |||||||
|     return blueprints |     return blueprints | ||||||
|  |  | ||||||
|  |  | ||||||
| def blueprints_find(): | def blueprints_find() -> list[BlueprintFile]: | ||||||
|     """Find blueprints and return valid ones""" |     """Find blueprints and return valid ones""" | ||||||
|     blueprints = [] |     blueprints = [] | ||||||
|     root = Path(CONFIG.get("blueprints_dir")) |     root = Path(CONFIG.get("blueprints_dir")) | ||||||
|     for path in root.rglob("**/*.yaml"): |     for path in root.rglob("**/*.yaml"): | ||||||
|  |         rel_path = path.relative_to(root) | ||||||
|         # Check if any part in the path starts with a dot and assume a hidden file |         # Check if any part in the path starts with a dot and assume a hidden file | ||||||
|         if any(part for part in path.parts if part.startswith(".")): |         if any(part for part in path.parts if part.startswith(".")): | ||||||
|             continue |             continue | ||||||
|         LOGGER.debug("found blueprint", path=str(path)) |  | ||||||
|         with open(path, "r", encoding="utf-8") as blueprint_file: |         with open(path, "r", encoding="utf-8") as blueprint_file: | ||||||
|             try: |             try: | ||||||
|                 raw_blueprint = load(blueprint_file.read(), BlueprintLoader) |                 raw_blueprint = load(blueprint_file.read(), BlueprintLoader) | ||||||
|             except YAMLError as exc: |             except YAMLError as exc: | ||||||
|                 raw_blueprint = None |                 raw_blueprint = None | ||||||
|                 LOGGER.warning("failed to parse blueprint", exc=exc, path=str(path)) |                 LOGGER.warning("failed to parse blueprint", exc=exc, path=str(rel_path)) | ||||||
|             if not raw_blueprint: |             if not raw_blueprint: | ||||||
|                 continue |                 continue | ||||||
|             metadata = raw_blueprint.get("metadata", None) |             metadata = raw_blueprint.get("metadata", None) | ||||||
|             version = raw_blueprint.get("version", 1) |             version = raw_blueprint.get("version", 1) | ||||||
|             if version != 1: |             if version != 1: | ||||||
|                 LOGGER.warning("invalid blueprint version", version=version, path=str(path)) |                 LOGGER.warning("invalid blueprint version", version=version, path=str(rel_path)) | ||||||
|                 continue |                 continue | ||||||
|         file_hash = sha512(path.read_bytes()).hexdigest() |         file_hash = sha512(path.read_bytes()).hexdigest() | ||||||
|         blueprint = BlueprintFile( |         blueprint = BlueprintFile(str(rel_path), version, file_hash, int(path.stat().st_mtime)) | ||||||
|             str(path.relative_to(root)), version, file_hash, int(path.stat().st_mtime) |  | ||||||
|         ) |  | ||||||
|         blueprint.meta = from_dict(BlueprintMetadata, metadata) if metadata else None |         blueprint.meta = from_dict(BlueprintMetadata, metadata) if metadata else None | ||||||
|         blueprints.append(blueprint) |         blueprints.append(blueprint) | ||||||
|         LOGGER.debug( |  | ||||||
|             "parsed & loaded blueprint", |  | ||||||
|             hash=file_hash, |  | ||||||
|             path=str(path), |  | ||||||
|         ) |  | ||||||
|     return blueprints |     return blueprints | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -138,10 +131,12 @@ def blueprints_find(): | |||||||
|     throws=(DatabaseError, ProgrammingError, InternalError), base=MonitoredTask, bind=True |     throws=(DatabaseError, ProgrammingError, InternalError), base=MonitoredTask, bind=True | ||||||
| ) | ) | ||||||
| @prefill_task | @prefill_task | ||||||
| def blueprints_discovery(self: MonitoredTask): | def blueprints_discovery(self: MonitoredTask, path: Optional[str] = None): | ||||||
|     """Find blueprints and check if they need to be created in the database""" |     """Find blueprints and check if they need to be created in the database""" | ||||||
|     count = 0 |     count = 0 | ||||||
|     for blueprint in blueprints_find(): |     for blueprint in blueprints_find(): | ||||||
|  |         if path and blueprint.path != path: | ||||||
|  |             continue | ||||||
|         check_blueprint_v1_file(blueprint) |         check_blueprint_v1_file(blueprint) | ||||||
|         count += 1 |         count += 1 | ||||||
|     self.set_status( |     self.set_status( | ||||||
| @ -171,7 +166,11 @@ def check_blueprint_v1_file(blueprint: BlueprintFile): | |||||||
|             metadata={}, |             metadata={}, | ||||||
|         ) |         ) | ||||||
|         instance.save() |         instance.save() | ||||||
|  |         LOGGER.info( | ||||||
|  |             "Creating new blueprint instance from file", instance=instance, path=instance.path | ||||||
|  |         ) | ||||||
|     if instance.last_applied_hash != blueprint.hash: |     if instance.last_applied_hash != blueprint.hash: | ||||||
|  |         LOGGER.info("Applying blueprint due to changed file", instance=instance, path=instance.path) | ||||||
|         apply_blueprint.delay(str(instance.pk)) |         apply_blueprint.delay(str(instance.pk)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -38,7 +38,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer): | |||||||
|  |  | ||||||
|     managed = ReadOnlyField() |     managed = ReadOnlyField() | ||||||
|     component = SerializerMethodField() |     component = SerializerMethodField() | ||||||
|     icon = ReadOnlyField(source="get_icon") |     icon = ReadOnlyField(source="icon_url") | ||||||
|  |  | ||||||
|     def get_component(self, obj: Source) -> str: |     def get_component(self, obj: Source) -> str: | ||||||
|         """Get object component so that we know how to edit the object""" |         """Get object component so that we know how to edit the object""" | ||||||
|  | |||||||
| @ -171,6 +171,11 @@ class UserSerializer(ModelSerializer): | |||||||
|             raise ValidationError("Setting a user to internal service account is not allowed.") |             raise ValidationError("Setting a user to internal service account is not allowed.") | ||||||
|         return user_type |         return user_type | ||||||
|  |  | ||||||
|  |     def validate(self, attrs: dict) -> dict: | ||||||
|  |         if self.instance and self.instance.type == UserTypes.INTERNAL_SERVICE_ACCOUNT: | ||||||
|  |             raise ValidationError("Can't modify internal service account users") | ||||||
|  |         return super().validate(attrs) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = User |         model = User | ||||||
|         fields = [ |         fields = [ | ||||||
|  | |||||||
| @ -17,9 +17,15 @@ class Command(BaseCommand): | |||||||
|     """Run worker""" |     """Run worker""" | ||||||
|  |  | ||||||
|     def add_arguments(self, parser): |     def add_arguments(self, parser): | ||||||
|         parser.add_argument("-b", "--beat", action="store_true") |         parser.add_argument( | ||||||
|  |             "-b", | ||||||
|  |             "--beat", | ||||||
|  |             action="store_false", | ||||||
|  |             help="When set, this worker will _not_ run Beat (scheduled) tasks", | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def handle(self, **options): |     def handle(self, **options): | ||||||
|  |         LOGGER.debug("Celery options", **options) | ||||||
|         close_old_connections() |         close_old_connections() | ||||||
|         if CONFIG.get_bool("remote_debug"): |         if CONFIG.get_bool("remote_debug"): | ||||||
|             import debugpy |             import debugpy | ||||||
|  | |||||||
| @ -13,7 +13,6 @@ | |||||||
|         {% block head_before %} |         {% block head_before %} | ||||||
|         {% endblock %} |         {% endblock %} | ||||||
|         <link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}"> |         <link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}"> | ||||||
|         <link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)"> |  | ||||||
|         <link rel="stylesheet" type="text/css" href="{% static 'dist/custom.css' %}" data-inject> |         <link rel="stylesheet" type="text/css" href="{% static 'dist/custom.css' %}" data-inject> | ||||||
|         <script src="{% static 'dist/poly.js' %}?version={{ version }}" type="module"></script> |         <script src="{% static 'dist/poly.js' %}?version={{ version }}" type="module"></script> | ||||||
|         <script src="{% static 'dist/standalone/loading/index.js' %}?version={{ version }}" type="module"></script> |         <script src="{% static 'dist/standalone/loading/index.js' %}?version={{ version }}" type="module"></script> | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ | |||||||
| {% block head_before %} | {% block head_before %} | ||||||
| <link rel="prefetch" href="/static/dist/assets/images/flow_background.jpg" /> | <link rel="prefetch" href="/static/dist/assets/images/flow_background.jpg" /> | ||||||
| <link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}"> | <link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}"> | ||||||
|  | <link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)"> | ||||||
| {% include "base/header_js.html" %} | {% include "base/header_js.html" %} | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,13 +1,10 @@ | |||||||
| """authentik crypto app config""" | """authentik crypto app config""" | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| from typing import TYPE_CHECKING, Optional | from typing import Optional | ||||||
|  |  | ||||||
| from authentik.blueprints.apps import ManagedAppConfig | from authentik.blueprints.apps import ManagedAppConfig | ||||||
| from authentik.lib.generators import generate_id | from authentik.lib.generators import generate_id | ||||||
|  |  | ||||||
| if TYPE_CHECKING: |  | ||||||
|     from authentik.crypto.models import CertificateKeyPair |  | ||||||
|  |  | ||||||
| MANAGED_KEY = "goauthentik.io/crypto/jwt-managed" | MANAGED_KEY = "goauthentik.io/crypto/jwt-managed" | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -23,33 +20,37 @@ class AuthentikCryptoConfig(ManagedAppConfig): | |||||||
|         """Load crypto tasks""" |         """Load crypto tasks""" | ||||||
|         self.import_module("authentik.crypto.tasks") |         self.import_module("authentik.crypto.tasks") | ||||||
|  |  | ||||||
|     def _create_update_cert(self, cert: Optional["CertificateKeyPair"] = None): |     def _create_update_cert(self): | ||||||
|         from authentik.crypto.builder import CertificateBuilder |         from authentik.crypto.builder import CertificateBuilder | ||||||
|         from authentik.crypto.models import CertificateKeyPair |         from authentik.crypto.models import CertificateKeyPair | ||||||
|  |  | ||||||
|         builder = CertificateBuilder("authentik Internal JWT Certificate") |         common_name = "authentik Internal JWT Certificate" | ||||||
|  |         builder = CertificateBuilder(common_name) | ||||||
|         builder.build( |         builder.build( | ||||||
|             subject_alt_names=["goauthentik.io"], |             subject_alt_names=["goauthentik.io"], | ||||||
|             validity_days=360, |             validity_days=360, | ||||||
|         ) |         ) | ||||||
|         if not cert: |         CertificateKeyPair.objects.update_or_create( | ||||||
|             cert = CertificateKeyPair() |             managed=MANAGED_KEY, | ||||||
|         builder.cert = cert |             defaults={ | ||||||
|         builder.cert.managed = MANAGED_KEY |                 "name": common_name, | ||||||
|         builder.save() |                 "certificate_data": builder.certificate, | ||||||
|  |                 "key_data": builder.private_key, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def reconcile_managed_jwt_cert(self): |     def reconcile_managed_jwt_cert(self): | ||||||
|         """Ensure managed JWT certificate""" |         """Ensure managed JWT certificate""" | ||||||
|         from authentik.crypto.models import CertificateKeyPair |         from authentik.crypto.models import CertificateKeyPair | ||||||
|  |  | ||||||
|         certs = CertificateKeyPair.objects.filter(managed=MANAGED_KEY) |         cert: Optional[CertificateKeyPair] = CertificateKeyPair.objects.filter( | ||||||
|         if not certs.exists(): |             managed=MANAGED_KEY | ||||||
|             self._create_update_cert() |         ).first() | ||||||
|             return |  | ||||||
|         cert: CertificateKeyPair = certs.first() |  | ||||||
|         now = datetime.now() |         now = datetime.now() | ||||||
|         if now < cert.certificate.not_valid_before or now > cert.certificate.not_valid_after: |         if not cert or ( | ||||||
|             self._create_update_cert(cert) |             now < cert.certificate.not_valid_before or now > cert.certificate.not_valid_after | ||||||
|  |         ): | ||||||
|  |             self._create_update_cert() | ||||||
|  |  | ||||||
|     def reconcile_self_signed(self): |     def reconcile_self_signed(self): | ||||||
|         """Create self-signed keypair""" |         """Create self-signed keypair""" | ||||||
| @ -61,4 +62,10 @@ class AuthentikCryptoConfig(ManagedAppConfig): | |||||||
|             return |             return | ||||||
|         builder = CertificateBuilder(name) |         builder = CertificateBuilder(name) | ||||||
|         builder.build(subject_alt_names=[f"{generate_id()}.self-signed.goauthentik.io"]) |         builder.build(subject_alt_names=[f"{generate_id()}.self-signed.goauthentik.io"]) | ||||||
|         builder.save() |         CertificateKeyPair.objects.get_or_create( | ||||||
|  |             name=name, | ||||||
|  |             defaults={ | ||||||
|  |                 "certificate_data": builder.certificate, | ||||||
|  |                 "key_data": builder.private_key, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  | |||||||
| @ -27,6 +27,7 @@ from authentik.lib.sentry import before_send | |||||||
| from authentik.lib.utils.errors import exception_to_string | from authentik.lib.utils.errors import exception_to_string | ||||||
| from authentik.outposts.models import OutpostServiceConnection | from authentik.outposts.models import OutpostServiceConnection | ||||||
| from authentik.policies.models import Policy, PolicyBindingModel | from authentik.policies.models import Policy, PolicyBindingModel | ||||||
|  | from authentik.policies.reputation.models import Reputation | ||||||
| from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken | from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken | ||||||
| from authentik.providers.scim.models import SCIMGroup, SCIMUser | from authentik.providers.scim.models import SCIMGroup, SCIMUser | ||||||
| from authentik.stages.authenticator_static.models import StaticToken | from authentik.stages.authenticator_static.models import StaticToken | ||||||
| @ -52,11 +53,13 @@ IGNORED_MODELS = ( | |||||||
|     RefreshToken, |     RefreshToken, | ||||||
|     SCIMUser, |     SCIMUser, | ||||||
|     SCIMGroup, |     SCIMGroup, | ||||||
|  |     Reputation, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def should_log_model(model: Model) -> bool: | def should_log_model(model: Model) -> bool: | ||||||
|     """Return true if operation on `model` should be logged""" |     """Return true if operation on `model` should be logged""" | ||||||
|  |     # Check for silk by string so this comparison doesn't fail when silk isn't installed | ||||||
|     if model.__module__.startswith("silk"): |     if model.__module__.startswith("silk"): | ||||||
|         return False |         return False | ||||||
|     return model.__class__ not in IGNORED_MODELS |     return model.__class__ not in IGNORED_MODELS | ||||||
| @ -93,21 +96,30 @@ class AuditMiddleware: | |||||||
|     of models""" |     of models""" | ||||||
|  |  | ||||||
|     get_response: Callable[[HttpRequest], HttpResponse] |     get_response: Callable[[HttpRequest], HttpResponse] | ||||||
|  |     anonymous_user: User = None | ||||||
|  |  | ||||||
|     def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]): |     def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]): | ||||||
|         self.get_response = get_response |         self.get_response = get_response | ||||||
|  |  | ||||||
|  |     def _ensure_fallback_user(self): | ||||||
|  |         """Defer fetching anonymous user until we have to""" | ||||||
|  |         if self.anonymous_user: | ||||||
|  |             return | ||||||
|  |         from guardian.shortcuts import get_anonymous_user | ||||||
|  |  | ||||||
|  |         self.anonymous_user = get_anonymous_user() | ||||||
|  |  | ||||||
|     def connect(self, request: HttpRequest): |     def connect(self, request: HttpRequest): | ||||||
|         """Connect signal for automatic logging""" |         """Connect signal for automatic logging""" | ||||||
|         if not hasattr(request, "user"): |         self._ensure_fallback_user() | ||||||
|             return |         user = getattr(request, "user", self.anonymous_user) | ||||||
|         if not getattr(request.user, "is_authenticated", False): |         if not user.is_authenticated: | ||||||
|             return |             user = self.anonymous_user | ||||||
|         if not hasattr(request, "request_id"): |         if not hasattr(request, "request_id"): | ||||||
|             return |             return | ||||||
|         post_save_handler = partial(self.post_save_handler, user=request.user, request=request) |         post_save_handler = partial(self.post_save_handler, user=user, request=request) | ||||||
|         pre_delete_handler = partial(self.pre_delete_handler, user=request.user, request=request) |         pre_delete_handler = partial(self.pre_delete_handler, user=user, request=request) | ||||||
|         m2m_changed_handler = partial(self.m2m_changed_handler, user=request.user, request=request) |         m2m_changed_handler = partial(self.m2m_changed_handler, user=user, request=request) | ||||||
|         post_save.connect( |         post_save.connect( | ||||||
|             post_save_handler, |             post_save_handler, | ||||||
|             dispatch_uid=request.request_id, |             dispatch_uid=request.request_id, | ||||||
|  | |||||||
| @ -217,6 +217,7 @@ class Event(SerializerModel, ExpiringModel): | |||||||
|                 "path": request.path, |                 "path": request.path, | ||||||
|                 "method": request.method, |                 "method": request.method, | ||||||
|                 "args": cleanse_dict(QueryDict(request.META.get("QUERY_STRING", ""))), |                 "args": cleanse_dict(QueryDict(request.META.get("QUERY_STRING", ""))), | ||||||
|  |                 "user_agent": request.META.get("HTTP_USER_AGENT", ""), | ||||||
|             } |             } | ||||||
|             # Special case for events created during flow execution |             # Special case for events created during flow execution | ||||||
|             # since they keep the http query within a wrapped query |             # since they keep the http query within a wrapped query | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ from authentik.events.tasks import event_notification_handler, gdpr_cleanup | |||||||
| from authentik.flows.models import Stage | from authentik.flows.models import Stage | ||||||
| from authentik.flows.planner import PLAN_CONTEXT_SOURCE, FlowPlan | from authentik.flows.planner import PLAN_CONTEXT_SOURCE, FlowPlan | ||||||
| from authentik.flows.views.executor import SESSION_KEY_PLAN | from authentik.flows.views.executor import SESSION_KEY_PLAN | ||||||
|  | from authentik.lib.config import CONFIG | ||||||
| from authentik.stages.invitation.models import Invitation | from authentik.stages.invitation.models import Invitation | ||||||
| from authentik.stages.invitation.signals import invitation_used | from authentik.stages.invitation.signals import invitation_used | ||||||
| from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS | from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS | ||||||
| @ -92,4 +93,5 @@ def event_post_save_notification(sender, instance: Event, **_): | |||||||
| @receiver(pre_delete, sender=User) | @receiver(pre_delete, sender=User) | ||||||
| def event_user_pre_delete_cleanup(sender, instance: User, **_): | def event_user_pre_delete_cleanup(sender, instance: User, **_): | ||||||
|     """If gdpr_compliance is enabled, remove all the user's events""" |     """If gdpr_compliance is enabled, remove all the user's events""" | ||||||
|  |     if CONFIG.get_bool("gdpr_compliance", True): | ||||||
|         gdpr_cleanup.delay(instance.pk) |         gdpr_cleanup.delay(instance.pk) | ||||||
|  | |||||||
| @ -53,7 +53,15 @@ class TestEvents(TestCase): | |||||||
|         """Test plain from_http""" |         """Test plain from_http""" | ||||||
|         event = Event.new("unittest").from_http(self.factory.get("/")) |         event = Event.new("unittest").from_http(self.factory.get("/")) | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|             event.context, {"http_request": {"args": {}, "method": "GET", "path": "/"}} |             event.context, | ||||||
|  |             { | ||||||
|  |                 "http_request": { | ||||||
|  |                     "args": {}, | ||||||
|  |                     "method": "GET", | ||||||
|  |                     "path": "/", | ||||||
|  |                     "user_agent": "", | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def test_from_http_clean_querystring(self): |     def test_from_http_clean_querystring(self): | ||||||
| @ -67,6 +75,7 @@ class TestEvents(TestCase): | |||||||
|                     "args": {"token": SafeExceptionReporterFilter.cleansed_substitute}, |                     "args": {"token": SafeExceptionReporterFilter.cleansed_substitute}, | ||||||
|                     "method": "GET", |                     "method": "GET", | ||||||
|                     "path": "/", |                     "path": "/", | ||||||
|  |                     "user_agent": "", | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
| @ -83,6 +92,7 @@ class TestEvents(TestCase): | |||||||
|                     "args": {"token": SafeExceptionReporterFilter.cleansed_substitute}, |                     "args": {"token": SafeExceptionReporterFilter.cleansed_substitute}, | ||||||
|                     "method": "GET", |                     "method": "GET", | ||||||
|                     "path": "/", |                     "path": "/", | ||||||
|  |                     "user_agent": "", | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
|  | |||||||
| @ -5,12 +5,13 @@ from dataclasses import asdict, is_dataclass | |||||||
| from datetime import date, datetime, time, timedelta | from datetime import date, datetime, time, timedelta | ||||||
| from enum import Enum | from enum import Enum | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from types import GeneratorType | from types import GeneratorType, NoneType | ||||||
| from typing import Any, Optional | from typing import Any, Optional | ||||||
| from uuid import UUID | from uuid import UUID | ||||||
|  |  | ||||||
| from django.contrib.auth.models import AnonymousUser | from django.contrib.auth.models import AnonymousUser | ||||||
| from django.core.handlers.wsgi import WSGIRequest | from django.core.handlers.wsgi import WSGIRequest | ||||||
|  | from django.core.serializers.json import DjangoJSONEncoder | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.db.models.base import Model | from django.db.models.base import Model | ||||||
| from django.http.request import HttpRequest | from django.http.request import HttpRequest | ||||||
| @ -153,7 +154,20 @@ def sanitize_item(value: Any) -> Any: | |||||||
|         return value.isoformat() |         return value.isoformat() | ||||||
|     if isinstance(value, timedelta): |     if isinstance(value, timedelta): | ||||||
|         return str(value.total_seconds()) |         return str(value.total_seconds()) | ||||||
|  |     if callable(value): | ||||||
|  |         return { | ||||||
|  |             "type": "callable", | ||||||
|  |             "name": value.__name__, | ||||||
|  |             "module": value.__module__, | ||||||
|  |         } | ||||||
|  |     # List taken from the stdlib's JSON encoder (_make_iterencode, encoder.py:415) | ||||||
|  |     if isinstance(value, (bool, int, float, NoneType, list, tuple, dict)): | ||||||
|         return value |         return value | ||||||
|  |     try: | ||||||
|  |         return DjangoJSONEncoder().default(value) | ||||||
|  |     except TypeError: | ||||||
|  |         return str(value) | ||||||
|  |     return str(value) | ||||||
|  |  | ||||||
|  |  | ||||||
| def sanitize_dict(source: dict[Any, Any]) -> dict[Any, Any]: | def sanitize_dict(source: dict[Any, Any]) -> dict[Any, Any]: | ||||||
|  | |||||||
							
								
								
									
										34
									
								
								authentik/flows/migrations/0027_auto_20231028_1424.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								authentik/flows/migrations/0027_auto_20231028_1424.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | |||||||
|  | # Generated by Django 4.2.6 on 2023-10-28 14:24 | ||||||
|  |  | ||||||
|  | from django.apps.registry import Apps | ||||||
|  | from django.db import migrations | ||||||
|  | from django.db.backends.base.schema import BaseDatabaseSchemaEditor | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def set_oobe_flow_authentication(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): | ||||||
|  |     from guardian.shortcuts import get_anonymous_user | ||||||
|  |  | ||||||
|  |     Flow = apps.get_model("authentik_flows", "Flow") | ||||||
|  |     User = apps.get_model("authentik_core", "User") | ||||||
|  |  | ||||||
|  |     db_alias = schema_editor.connection.alias | ||||||
|  |  | ||||||
|  |     users = User.objects.using(db_alias).exclude(username="akadmin") | ||||||
|  |     try: | ||||||
|  |         users = users.exclude(pk=get_anonymous_user().pk) | ||||||
|  |     # pylint: disable=broad-except | ||||||
|  |     except Exception:  # nosec | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     if users.exists(): | ||||||
|  |         Flow.objects.filter(slug="initial-setup").update(authentication="require_superuser") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |     dependencies = [ | ||||||
|  |         ("authentik_flows", "0026_alter_flow_options"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.RunPython(set_oobe_flow_authentication), | ||||||
|  |     ] | ||||||
| @ -167,7 +167,11 @@ class ChallengeStageView(StageView): | |||||||
|                 stage_type=self.__class__.__name__, method="get_challenge" |                 stage_type=self.__class__.__name__, method="get_challenge" | ||||||
|             ).time(), |             ).time(), | ||||||
|         ): |         ): | ||||||
|  |             try: | ||||||
|                 challenge = self.get_challenge(*args, **kwargs) |                 challenge = self.get_challenge(*args, **kwargs) | ||||||
|  |             except StageInvalidException as exc: | ||||||
|  |                 self.logger.debug("Got StageInvalidException", exc=exc) | ||||||
|  |                 return self.executor.stage_invalid() | ||||||
|         with Hub.current.start_span( |         with Hub.current.start_span( | ||||||
|             op="authentik.flow.stage._get_challenge", |             op="authentik.flow.stage._get_challenge", | ||||||
|             description=self.__class__.__name__, |             description=self.__class__.__name__, | ||||||
|  | |||||||
| @ -344,11 +344,21 @@ class Outpost(SerializerModel, ManagedModel): | |||||||
|         user_created = False |         user_created = False | ||||||
|         if not user: |         if not user: | ||||||
|             user: User = User.objects.create(username=self.user_identifier) |             user: User = User.objects.create(username=self.user_identifier) | ||||||
|             user.set_unusable_password() |  | ||||||
|             user_created = True |             user_created = True | ||||||
|         user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT |         attrs = { | ||||||
|         user.name = f"Outpost {self.name} Service-Account" |             "type": UserTypes.INTERNAL_SERVICE_ACCOUNT, | ||||||
|         user.path = USER_PATH_OUTPOSTS |             "name": f"Outpost {self.name} Service-Account", | ||||||
|  |             "path": USER_PATH_OUTPOSTS, | ||||||
|  |         } | ||||||
|  |         dirty = False | ||||||
|  |         for key, value in attrs.items(): | ||||||
|  |             if getattr(user, key) != value: | ||||||
|  |                 dirty = True | ||||||
|  |                 setattr(user, key, value) | ||||||
|  |         if user.has_usable_password(): | ||||||
|  |             user.set_unusable_password() | ||||||
|  |             dirty = True | ||||||
|  |         if dirty: | ||||||
|             user.save() |             user.save() | ||||||
|         if user_created: |         if user_created: | ||||||
|             self.build_user_permissions(user) |             self.build_user_permissions(user) | ||||||
|  | |||||||
							
								
								
									
										187
									
								
								authentik/providers/oauth2/tests/test_token_pkce.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								authentik/providers/oauth2/tests/test_token_pkce.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,187 @@ | |||||||
|  | """Test token view""" | ||||||
|  | from base64 import b64encode, urlsafe_b64encode | ||||||
|  | from hashlib import sha256 | ||||||
|  |  | ||||||
|  | from django.test import RequestFactory | ||||||
|  | from django.urls import reverse | ||||||
|  |  | ||||||
|  | from authentik.core.models import Application | ||||||
|  | from authentik.core.tests.utils import create_test_admin_user, create_test_flow | ||||||
|  | from authentik.flows.challenge import ChallengeTypes | ||||||
|  | from authentik.lib.generators import generate_id | ||||||
|  | from authentik.providers.oauth2.constants import GRANT_TYPE_AUTHORIZATION_CODE | ||||||
|  | from authentik.providers.oauth2.models import AuthorizationCode, OAuth2Provider | ||||||
|  | from authentik.providers.oauth2.tests.utils import OAuthTestCase | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestTokenPKCE(OAuthTestCase): | ||||||
|  |     """Test token view""" | ||||||
|  |  | ||||||
|  |     def setUp(self) -> None: | ||||||
|  |         super().setUp() | ||||||
|  |         self.factory = RequestFactory() | ||||||
|  |         self.app = Application.objects.create(name=generate_id(), slug="test") | ||||||
|  |  | ||||||
|  |     def test_pkce_missing_in_token(self): | ||||||
|  |         """Test full with pkce""" | ||||||
|  |         flow = create_test_flow() | ||||||
|  |         provider = OAuth2Provider.objects.create( | ||||||
|  |             name=generate_id(), | ||||||
|  |             client_id="test", | ||||||
|  |             authorization_flow=flow, | ||||||
|  |             redirect_uris="foo://localhost", | ||||||
|  |             access_code_validity="seconds=100", | ||||||
|  |         ) | ||||||
|  |         Application.objects.create(name="app", slug="app", provider=provider) | ||||||
|  |         state = generate_id() | ||||||
|  |         user = create_test_admin_user() | ||||||
|  |         self.client.force_login(user) | ||||||
|  |         challenge = generate_id() | ||||||
|  |         header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode() | ||||||
|  |         # Step 1, initiate params and get redirect to flow | ||||||
|  |         self.client.get( | ||||||
|  |             reverse("authentik_providers_oauth2:authorize"), | ||||||
|  |             data={ | ||||||
|  |                 "response_type": "code", | ||||||
|  |                 "client_id": "test", | ||||||
|  |                 "state": state, | ||||||
|  |                 "redirect_uri": "foo://localhost", | ||||||
|  |                 "code_challenge": challenge, | ||||||
|  |                 "code_challenge_method": "S256", | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |         response = self.client.get( | ||||||
|  |             reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), | ||||||
|  |         ) | ||||||
|  |         code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first() | ||||||
|  |         self.assertJSONEqual( | ||||||
|  |             response.content.decode(), | ||||||
|  |             { | ||||||
|  |                 "component": "xak-flow-redirect", | ||||||
|  |                 "type": ChallengeTypes.REDIRECT.value, | ||||||
|  |                 "to": f"foo://localhost?code={code.code}&state={state}", | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |         response = self.client.post( | ||||||
|  |             reverse("authentik_providers_oauth2:token"), | ||||||
|  |             data={ | ||||||
|  |                 "grant_type": GRANT_TYPE_AUTHORIZATION_CODE, | ||||||
|  |                 "code": code.code, | ||||||
|  |                 # Missing the code_verifier here | ||||||
|  |                 "redirect_uri": "foo://localhost", | ||||||
|  |             }, | ||||||
|  |             HTTP_AUTHORIZATION=f"Basic {header}", | ||||||
|  |         ) | ||||||
|  |         self.assertJSONEqual( | ||||||
|  |             response.content, | ||||||
|  |             {"error": "invalid_request", "error_description": "The request is otherwise malformed"}, | ||||||
|  |         ) | ||||||
|  |         self.assertEqual(response.status_code, 400) | ||||||
|  |  | ||||||
|  |     def test_pkce_correct_s256(self): | ||||||
|  |         """Test full with pkce""" | ||||||
|  |         flow = create_test_flow() | ||||||
|  |         provider = OAuth2Provider.objects.create( | ||||||
|  |             name=generate_id(), | ||||||
|  |             client_id="test", | ||||||
|  |             authorization_flow=flow, | ||||||
|  |             redirect_uris="foo://localhost", | ||||||
|  |             access_code_validity="seconds=100", | ||||||
|  |         ) | ||||||
|  |         Application.objects.create(name="app", slug="app", provider=provider) | ||||||
|  |         state = generate_id() | ||||||
|  |         user = create_test_admin_user() | ||||||
|  |         self.client.force_login(user) | ||||||
|  |         verifier = generate_id() | ||||||
|  |         challenge = ( | ||||||
|  |             urlsafe_b64encode(sha256(verifier.encode("ascii")).digest()) | ||||||
|  |             .decode("utf-8") | ||||||
|  |             .replace("=", "") | ||||||
|  |         ) | ||||||
|  |         header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode() | ||||||
|  |         # Step 1, initiate params and get redirect to flow | ||||||
|  |         self.client.get( | ||||||
|  |             reverse("authentik_providers_oauth2:authorize"), | ||||||
|  |             data={ | ||||||
|  |                 "response_type": "code", | ||||||
|  |                 "client_id": "test", | ||||||
|  |                 "state": state, | ||||||
|  |                 "redirect_uri": "foo://localhost", | ||||||
|  |                 "code_challenge": challenge, | ||||||
|  |                 "code_challenge_method": "S256", | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |         response = self.client.get( | ||||||
|  |             reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), | ||||||
|  |         ) | ||||||
|  |         code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first() | ||||||
|  |         self.assertJSONEqual( | ||||||
|  |             response.content.decode(), | ||||||
|  |             { | ||||||
|  |                 "component": "xak-flow-redirect", | ||||||
|  |                 "type": ChallengeTypes.REDIRECT.value, | ||||||
|  |                 "to": f"foo://localhost?code={code.code}&state={state}", | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |         response = self.client.post( | ||||||
|  |             reverse("authentik_providers_oauth2:token"), | ||||||
|  |             data={ | ||||||
|  |                 "grant_type": GRANT_TYPE_AUTHORIZATION_CODE, | ||||||
|  |                 "code": code.code, | ||||||
|  |                 "code_verifier": verifier, | ||||||
|  |                 "redirect_uri": "foo://localhost", | ||||||
|  |             }, | ||||||
|  |             HTTP_AUTHORIZATION=f"Basic {header}", | ||||||
|  |         ) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|  |     def test_pkce_correct_plain(self): | ||||||
|  |         """Test full with pkce""" | ||||||
|  |         flow = create_test_flow() | ||||||
|  |         provider = OAuth2Provider.objects.create( | ||||||
|  |             name=generate_id(), | ||||||
|  |             client_id="test", | ||||||
|  |             authorization_flow=flow, | ||||||
|  |             redirect_uris="foo://localhost", | ||||||
|  |             access_code_validity="seconds=100", | ||||||
|  |         ) | ||||||
|  |         Application.objects.create(name="app", slug="app", provider=provider) | ||||||
|  |         state = generate_id() | ||||||
|  |         user = create_test_admin_user() | ||||||
|  |         self.client.force_login(user) | ||||||
|  |         verifier = generate_id() | ||||||
|  |         header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode() | ||||||
|  |         # Step 1, initiate params and get redirect to flow | ||||||
|  |         self.client.get( | ||||||
|  |             reverse("authentik_providers_oauth2:authorize"), | ||||||
|  |             data={ | ||||||
|  |                 "response_type": "code", | ||||||
|  |                 "client_id": "test", | ||||||
|  |                 "state": state, | ||||||
|  |                 "redirect_uri": "foo://localhost", | ||||||
|  |                 "code_challenge": verifier, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |         response = self.client.get( | ||||||
|  |             reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), | ||||||
|  |         ) | ||||||
|  |         code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first() | ||||||
|  |         self.assertJSONEqual( | ||||||
|  |             response.content.decode(), | ||||||
|  |             { | ||||||
|  |                 "component": "xak-flow-redirect", | ||||||
|  |                 "type": ChallengeTypes.REDIRECT.value, | ||||||
|  |                 "to": f"foo://localhost?code={code.code}&state={state}", | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |         response = self.client.post( | ||||||
|  |             reverse("authentik_providers_oauth2:token"), | ||||||
|  |             data={ | ||||||
|  |                 "grant_type": GRANT_TYPE_AUTHORIZATION_CODE, | ||||||
|  |                 "code": code.code, | ||||||
|  |                 "code_verifier": verifier, | ||||||
|  |                 "redirect_uri": "foo://localhost", | ||||||
|  |             }, | ||||||
|  |             HTTP_AUTHORIZATION=f"Basic {header}", | ||||||
|  |         ) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
| @ -188,6 +188,7 @@ def authenticate_provider(request: HttpRequest) -> Optional[OAuth2Provider]: | |||||||
|     if client_id != provider.client_id or client_secret != provider.client_secret: |     if client_id != provider.client_id or client_secret != provider.client_secret: | ||||||
|         LOGGER.debug("(basic) Provider for basic auth does not exist") |         LOGGER.debug("(basic) Provider for basic auth does not exist") | ||||||
|         return None |         return None | ||||||
|  |     CTX_AUTH_VIA.set("oauth_client_secret") | ||||||
|     return provider |     return provider | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -17,6 +17,7 @@ from jwt import PyJWK, PyJWT, PyJWTError, decode | |||||||
| from sentry_sdk.hub import Hub | from sentry_sdk.hub import Hub | ||||||
| from structlog.stdlib import get_logger | from structlog.stdlib import get_logger | ||||||
|  |  | ||||||
|  | from authentik.core.middleware import CTX_AUTH_VIA | ||||||
| from authentik.core.models import ( | from authentik.core.models import ( | ||||||
|     USER_ATTRIBUTE_EXPIRES, |     USER_ATTRIBUTE_EXPIRES, | ||||||
|     USER_ATTRIBUTE_GENERATED, |     USER_ATTRIBUTE_GENERATED, | ||||||
| @ -221,7 +222,10 @@ class TokenParams: | |||||||
|             raise TokenError("invalid_grant") |             raise TokenError("invalid_grant") | ||||||
|  |  | ||||||
|         # Validate PKCE parameters. |         # Validate PKCE parameters. | ||||||
|         if self.code_verifier: |         if self.authorization_code.code_challenge: | ||||||
|  |             # Authorization code had PKCE but we didn't get one | ||||||
|  |             if not self.code_verifier: | ||||||
|  |                 raise TokenError("invalid_request") | ||||||
|             if self.authorization_code.code_challenge_method == PKCE_METHOD_S256: |             if self.authorization_code.code_challenge_method == PKCE_METHOD_S256: | ||||||
|                 new_code_challenge = ( |                 new_code_challenge = ( | ||||||
|                     urlsafe_b64encode(sha256(self.code_verifier.encode("ascii")).digest()) |                     urlsafe_b64encode(sha256(self.code_verifier.encode("ascii")).digest()) | ||||||
| @ -448,6 +452,7 @@ class TokenView(View): | |||||||
|                 if not self.provider: |                 if not self.provider: | ||||||
|                     LOGGER.warning("OAuth2Provider does not exist", client_id=client_id) |                     LOGGER.warning("OAuth2Provider does not exist", client_id=client_id) | ||||||
|                     raise TokenError("invalid_client") |                     raise TokenError("invalid_client") | ||||||
|  |                 CTX_AUTH_VIA.set("oauth_client_secret") | ||||||
|                 self.params = TokenParams.parse(request, self.provider, client_id, client_secret) |                 self.params = TokenParams.parse(request, self.provider, client_id, client_secret) | ||||||
|  |  | ||||||
|             with Hub.current.start_span( |             with Hub.current.start_span( | ||||||
|  | |||||||
| @ -46,7 +46,9 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]): | |||||||
|  |  | ||||||
|     def to_scim(self, obj: Group) -> SCIMGroupSchema: |     def to_scim(self, obj: Group) -> SCIMGroupSchema: | ||||||
|         """Convert authentik user into SCIM""" |         """Convert authentik user into SCIM""" | ||||||
|         raw_scim_group = {} |         raw_scim_group = { | ||||||
|  |             "schemas": ("urn:ietf:params:scim:schemas:core:2.0:Group",), | ||||||
|  |         } | ||||||
|         for mapping in ( |         for mapping in ( | ||||||
|             self.provider.property_mappings_group.all().order_by("name").select_subclasses() |             self.provider.property_mappings_group.all().order_by("name").select_subclasses() | ||||||
|         ): |         ): | ||||||
|  | |||||||
| @ -15,12 +15,14 @@ from pydanticscim.user import User as BaseUser | |||||||
| class User(BaseUser): | class User(BaseUser): | ||||||
|     """Modified User schema with added externalId field""" |     """Modified User schema with added externalId field""" | ||||||
|  |  | ||||||
|  |     schemas: tuple[str] = ("urn:ietf:params:scim:schemas:core:2.0:User",) | ||||||
|     externalId: Optional[str] = None |     externalId: Optional[str] = None | ||||||
|  |  | ||||||
|  |  | ||||||
| class Group(BaseGroup): | class Group(BaseGroup): | ||||||
|     """Modified Group schema with added externalId field""" |     """Modified Group schema with added externalId field""" | ||||||
|  |  | ||||||
|  |     schemas: tuple[str] = ("urn:ietf:params:scim:schemas:core:2.0:Group",) | ||||||
|     externalId: Optional[str] = None |     externalId: Optional[str] = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -39,7 +39,9 @@ class SCIMUserClient(SCIMClient[User, SCIMUserSchema]): | |||||||
|  |  | ||||||
|     def to_scim(self, obj: User) -> SCIMUserSchema: |     def to_scim(self, obj: User) -> SCIMUserSchema: | ||||||
|         """Convert authentik user into SCIM""" |         """Convert authentik user into SCIM""" | ||||||
|         raw_scim_user = {} |         raw_scim_user = { | ||||||
|  |             "schemas": ("urn:ietf:params:scim:schemas:core:2.0:User",), | ||||||
|  |         } | ||||||
|         for mapping in self.provider.property_mappings.all().order_by("name").select_subclasses(): |         for mapping in self.provider.property_mappings.all().order_by("name").select_subclasses(): | ||||||
|             if not isinstance(mapping, SCIMMapping): |             if not isinstance(mapping, SCIMMapping): | ||||||
|                 continue |                 continue | ||||||
|  | |||||||
| @ -61,7 +61,11 @@ class SCIMGroupTests(TestCase): | |||||||
|         self.assertEqual(mock.request_history[1].method, "POST") |         self.assertEqual(mock.request_history[1].method, "POST") | ||||||
|         self.assertJSONEqual( |         self.assertJSONEqual( | ||||||
|             mock.request_history[1].body, |             mock.request_history[1].body, | ||||||
|             {"externalId": str(group.pk), "displayName": group.name}, |             { | ||||||
|  |                 "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], | ||||||
|  |                 "externalId": str(group.pk), | ||||||
|  |                 "displayName": group.name, | ||||||
|  |             }, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     @Mocker() |     @Mocker() | ||||||
| @ -96,7 +100,11 @@ class SCIMGroupTests(TestCase): | |||||||
|             validate(body, loads(schema.read())) |             validate(body, loads(schema.read())) | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|             body, |             body, | ||||||
|             {"externalId": str(group.pk), "displayName": group.name}, |             { | ||||||
|  |                 "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], | ||||||
|  |                 "externalId": str(group.pk), | ||||||
|  |                 "displayName": group.name, | ||||||
|  |             }, | ||||||
|         ) |         ) | ||||||
|         group.save() |         group.save() | ||||||
|         self.assertEqual(mock.call_count, 4) |         self.assertEqual(mock.call_count, 4) | ||||||
| @ -129,7 +137,11 @@ class SCIMGroupTests(TestCase): | |||||||
|         self.assertEqual(mock.request_history[1].method, "POST") |         self.assertEqual(mock.request_history[1].method, "POST") | ||||||
|         self.assertJSONEqual( |         self.assertJSONEqual( | ||||||
|             mock.request_history[1].body, |             mock.request_history[1].body, | ||||||
|             {"externalId": str(group.pk), "displayName": group.name}, |             { | ||||||
|  |                 "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], | ||||||
|  |                 "externalId": str(group.pk), | ||||||
|  |                 "displayName": group.name, | ||||||
|  |             }, | ||||||
|         ) |         ) | ||||||
|         group.delete() |         group.delete() | ||||||
|         self.assertEqual(mock.call_count, 4) |         self.assertEqual(mock.call_count, 4) | ||||||
|  | |||||||
| @ -89,6 +89,7 @@ class SCIMMembershipTests(TestCase): | |||||||
|             self.assertJSONEqual( |             self.assertJSONEqual( | ||||||
|                 mocker.request_history[3].body, |                 mocker.request_history[3].body, | ||||||
|                 { |                 { | ||||||
|  |                     "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], | ||||||
|                     "emails": [], |                     "emails": [], | ||||||
|                     "active": True, |                     "active": True, | ||||||
|                     "externalId": user.uid, |                     "externalId": user.uid, | ||||||
| @ -99,7 +100,11 @@ class SCIMMembershipTests(TestCase): | |||||||
|             ) |             ) | ||||||
|             self.assertJSONEqual( |             self.assertJSONEqual( | ||||||
|                 mocker.request_history[5].body, |                 mocker.request_history[5].body, | ||||||
|                 {"externalId": str(group.pk), "displayName": group.name}, |                 { | ||||||
|  |                     "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], | ||||||
|  |                     "externalId": str(group.pk), | ||||||
|  |                     "displayName": group.name, | ||||||
|  |                 }, | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|         with Mocker() as mocker: |         with Mocker() as mocker: | ||||||
| @ -118,6 +123,7 @@ class SCIMMembershipTests(TestCase): | |||||||
|             self.assertJSONEqual( |             self.assertJSONEqual( | ||||||
|                 mocker.request_history[1].body, |                 mocker.request_history[1].body, | ||||||
|                 { |                 { | ||||||
|  |                     "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], | ||||||
|                     "Operations": [ |                     "Operations": [ | ||||||
|                         { |                         { | ||||||
|                             "op": "add", |                             "op": "add", | ||||||
| @ -125,7 +131,6 @@ class SCIMMembershipTests(TestCase): | |||||||
|                             "value": [{"value": user_scim_id}], |                             "value": [{"value": user_scim_id}], | ||||||
|                         } |                         } | ||||||
|                     ], |                     ], | ||||||
|                     "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], |  | ||||||
|                 }, |                 }, | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
| @ -174,6 +179,7 @@ class SCIMMembershipTests(TestCase): | |||||||
|             self.assertJSONEqual( |             self.assertJSONEqual( | ||||||
|                 mocker.request_history[3].body, |                 mocker.request_history[3].body, | ||||||
|                 { |                 { | ||||||
|  |                     "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], | ||||||
|                     "active": True, |                     "active": True, | ||||||
|                     "displayName": "", |                     "displayName": "", | ||||||
|                     "emails": [], |                     "emails": [], | ||||||
| @ -184,7 +190,11 @@ class SCIMMembershipTests(TestCase): | |||||||
|             ) |             ) | ||||||
|             self.assertJSONEqual( |             self.assertJSONEqual( | ||||||
|                 mocker.request_history[5].body, |                 mocker.request_history[5].body, | ||||||
|                 {"externalId": str(group.pk), "displayName": group.name}, |                 { | ||||||
|  |                     "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], | ||||||
|  |                     "externalId": str(group.pk), | ||||||
|  |                     "displayName": group.name, | ||||||
|  |                 }, | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|         with Mocker() as mocker: |         with Mocker() as mocker: | ||||||
| @ -203,6 +213,7 @@ class SCIMMembershipTests(TestCase): | |||||||
|             self.assertJSONEqual( |             self.assertJSONEqual( | ||||||
|                 mocker.request_history[1].body, |                 mocker.request_history[1].body, | ||||||
|                 { |                 { | ||||||
|  |                     "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], | ||||||
|                     "Operations": [ |                     "Operations": [ | ||||||
|                         { |                         { | ||||||
|                             "op": "add", |                             "op": "add", | ||||||
| @ -210,7 +221,6 @@ class SCIMMembershipTests(TestCase): | |||||||
|                             "value": [{"value": user_scim_id}], |                             "value": [{"value": user_scim_id}], | ||||||
|                         } |                         } | ||||||
|                     ], |                     ], | ||||||
|                     "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], |  | ||||||
|                 }, |                 }, | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
| @ -230,6 +240,7 @@ class SCIMMembershipTests(TestCase): | |||||||
|             self.assertJSONEqual( |             self.assertJSONEqual( | ||||||
|                 mocker.request_history[1].body, |                 mocker.request_history[1].body, | ||||||
|                 { |                 { | ||||||
|  |                     "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], | ||||||
|                     "Operations": [ |                     "Operations": [ | ||||||
|                         { |                         { | ||||||
|                             "op": "remove", |                             "op": "remove", | ||||||
| @ -237,6 +248,5 @@ class SCIMMembershipTests(TestCase): | |||||||
|                             "value": [{"value": user_scim_id}], |                             "value": [{"value": user_scim_id}], | ||||||
|                         } |                         } | ||||||
|                     ], |                     ], | ||||||
|                     "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], |  | ||||||
|                 }, |                 }, | ||||||
|             ) |             ) | ||||||
|  | |||||||
| @ -57,7 +57,7 @@ class SCIMUserTests(TestCase): | |||||||
|         uid = generate_id() |         uid = generate_id() | ||||||
|         user = User.objects.create( |         user = User.objects.create( | ||||||
|             username=uid, |             username=uid, | ||||||
|             name=uid, |             name=f"{uid} {uid}", | ||||||
|             email=f"{uid}@goauthentik.io", |             email=f"{uid}@goauthentik.io", | ||||||
|         ) |         ) | ||||||
|         self.assertEqual(mock.call_count, 2) |         self.assertEqual(mock.call_count, 2) | ||||||
| @ -66,6 +66,7 @@ class SCIMUserTests(TestCase): | |||||||
|         self.assertJSONEqual( |         self.assertJSONEqual( | ||||||
|             mock.request_history[1].body, |             mock.request_history[1].body, | ||||||
|             { |             { | ||||||
|  |                 "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], | ||||||
|                 "active": True, |                 "active": True, | ||||||
|                 "emails": [ |                 "emails": [ | ||||||
|                     { |                     { | ||||||
| @ -76,11 +77,11 @@ class SCIMUserTests(TestCase): | |||||||
|                 ], |                 ], | ||||||
|                 "externalId": user.uid, |                 "externalId": user.uid, | ||||||
|                 "name": { |                 "name": { | ||||||
|                     "familyName": "", |                     "familyName": uid, | ||||||
|                     "formatted": uid, |                     "formatted": f"{uid} {uid}", | ||||||
|                     "givenName": uid, |                     "givenName": uid, | ||||||
|                 }, |                 }, | ||||||
|                 "displayName": uid, |                 "displayName": f"{uid} {uid}", | ||||||
|                 "userName": uid, |                 "userName": uid, | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
| @ -109,7 +110,7 @@ class SCIMUserTests(TestCase): | |||||||
|         uid = generate_id() |         uid = generate_id() | ||||||
|         user = User.objects.create( |         user = User.objects.create( | ||||||
|             username=uid, |             username=uid, | ||||||
|             name=uid, |             name=f"{uid} {uid}", | ||||||
|             email=f"{uid}@goauthentik.io", |             email=f"{uid}@goauthentik.io", | ||||||
|         ) |         ) | ||||||
|         self.assertEqual(mock.call_count, 2) |         self.assertEqual(mock.call_count, 2) | ||||||
| @ -121,6 +122,7 @@ class SCIMUserTests(TestCase): | |||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|             body, |             body, | ||||||
|             { |             { | ||||||
|  |                 "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], | ||||||
|                 "active": True, |                 "active": True, | ||||||
|                 "emails": [ |                 "emails": [ | ||||||
|                     { |                     { | ||||||
| @ -129,11 +131,11 @@ class SCIMUserTests(TestCase): | |||||||
|                         "value": f"{uid}@goauthentik.io", |                         "value": f"{uid}@goauthentik.io", | ||||||
|                     } |                     } | ||||||
|                 ], |                 ], | ||||||
|                 "displayName": uid, |                 "displayName": f"{uid} {uid}", | ||||||
|                 "externalId": user.uid, |                 "externalId": user.uid, | ||||||
|                 "name": { |                 "name": { | ||||||
|                     "familyName": "", |                     "familyName": uid, | ||||||
|                     "formatted": uid, |                     "formatted": f"{uid} {uid}", | ||||||
|                     "givenName": uid, |                     "givenName": uid, | ||||||
|                 }, |                 }, | ||||||
|                 "userName": uid, |                 "userName": uid, | ||||||
| @ -164,7 +166,7 @@ class SCIMUserTests(TestCase): | |||||||
|         uid = generate_id() |         uid = generate_id() | ||||||
|         user = User.objects.create( |         user = User.objects.create( | ||||||
|             username=uid, |             username=uid, | ||||||
|             name=uid, |             name=f"{uid} {uid}", | ||||||
|             email=f"{uid}@goauthentik.io", |             email=f"{uid}@goauthentik.io", | ||||||
|         ) |         ) | ||||||
|         self.assertEqual(mock.call_count, 2) |         self.assertEqual(mock.call_count, 2) | ||||||
| @ -173,6 +175,7 @@ class SCIMUserTests(TestCase): | |||||||
|         self.assertJSONEqual( |         self.assertJSONEqual( | ||||||
|             mock.request_history[1].body, |             mock.request_history[1].body, | ||||||
|             { |             { | ||||||
|  |                 "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], | ||||||
|                 "active": True, |                 "active": True, | ||||||
|                 "emails": [ |                 "emails": [ | ||||||
|                     { |                     { | ||||||
| @ -183,11 +186,11 @@ class SCIMUserTests(TestCase): | |||||||
|                 ], |                 ], | ||||||
|                 "externalId": user.uid, |                 "externalId": user.uid, | ||||||
|                 "name": { |                 "name": { | ||||||
|                     "familyName": "", |                     "familyName": uid, | ||||||
|                     "formatted": uid, |                     "formatted": f"{uid} {uid}", | ||||||
|                     "givenName": uid, |                     "givenName": uid, | ||||||
|                 }, |                 }, | ||||||
|                 "displayName": uid, |                 "displayName": f"{uid} {uid}", | ||||||
|                 "userName": uid, |                 "userName": uid, | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
| @ -227,7 +230,7 @@ class SCIMUserTests(TestCase): | |||||||
|         ) |         ) | ||||||
|         user = User.objects.create( |         user = User.objects.create( | ||||||
|             username=uid, |             username=uid, | ||||||
|             name=uid, |             name=f"{uid} {uid}", | ||||||
|             email=f"{uid}@goauthentik.io", |             email=f"{uid}@goauthentik.io", | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
| @ -240,6 +243,7 @@ class SCIMUserTests(TestCase): | |||||||
|         self.assertJSONEqual( |         self.assertJSONEqual( | ||||||
|             mock.request_history[1].body, |             mock.request_history[1].body, | ||||||
|             { |             { | ||||||
|  |                 "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], | ||||||
|                 "active": True, |                 "active": True, | ||||||
|                 "emails": [ |                 "emails": [ | ||||||
|                     { |                     { | ||||||
| @ -250,11 +254,11 @@ class SCIMUserTests(TestCase): | |||||||
|                 ], |                 ], | ||||||
|                 "externalId": user.uid, |                 "externalId": user.uid, | ||||||
|                 "name": { |                 "name": { | ||||||
|                     "familyName": "", |                     "familyName": uid, | ||||||
|                     "formatted": uid, |                     "formatted": f"{uid} {uid}", | ||||||
|                     "givenName": uid, |                     "givenName": uid, | ||||||
|                 }, |                 }, | ||||||
|                 "displayName": uid, |                 "displayName": f"{uid} {uid}", | ||||||
|                 "userName": uid, |                 "userName": uid, | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
|  | |||||||
| @ -32,13 +32,19 @@ class PermissionSerializer(ModelSerializer): | |||||||
|  |  | ||||||
|     def get_app_label_verbose(self, instance: Permission) -> str: |     def get_app_label_verbose(self, instance: Permission) -> str: | ||||||
|         """Human-readable app label""" |         """Human-readable app label""" | ||||||
|  |         try: | ||||||
|             return apps.get_app_config(instance.content_type.app_label).verbose_name |             return apps.get_app_config(instance.content_type.app_label).verbose_name | ||||||
|  |         except LookupError: | ||||||
|  |             return f"{instance.content_type.app_label}.{instance.content_type.model}" | ||||||
|  |  | ||||||
|     def get_model_verbose(self, instance: Permission) -> str: |     def get_model_verbose(self, instance: Permission) -> str: | ||||||
|         """Human-readable model name""" |         """Human-readable model name""" | ||||||
|  |         try: | ||||||
|             return apps.get_model( |             return apps.get_model( | ||||||
|                 instance.content_type.app_label, instance.content_type.model |                 instance.content_type.app_label, instance.content_type.model | ||||||
|             )._meta.verbose_name |             )._meta.verbose_name | ||||||
|  |         except LookupError: | ||||||
|  |             return f"{instance.content_type.app_label}.{instance.content_type.model}" | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = Permission |         model = Permission | ||||||
|  | |||||||
| @ -28,9 +28,12 @@ class ExtraRoleObjectPermissionSerializer(RoleObjectPermissionSerializer): | |||||||
|  |  | ||||||
|     def get_model_verbose(self, instance: GroupObjectPermission) -> str: |     def get_model_verbose(self, instance: GroupObjectPermission) -> str: | ||||||
|         """Get model label from permission's model""" |         """Get model label from permission's model""" | ||||||
|  |         try: | ||||||
|             return apps.get_model( |             return apps.get_model( | ||||||
|                 instance.content_type.app_label, instance.content_type.model |                 instance.content_type.app_label, instance.content_type.model | ||||||
|             )._meta.verbose_name |             )._meta.verbose_name | ||||||
|  |         except LookupError: | ||||||
|  |             return f"{instance.content_type.app_label}.{instance.content_type.model}" | ||||||
|  |  | ||||||
|     def get_object_description(self, instance: GroupObjectPermission) -> Optional[str]: |     def get_object_description(self, instance: GroupObjectPermission) -> Optional[str]: | ||||||
|         """Get model description from attached model. This operation takes at least |         """Get model description from attached model. This operation takes at least | ||||||
| @ -38,7 +41,10 @@ class ExtraRoleObjectPermissionSerializer(RoleObjectPermissionSerializer): | |||||||
|         view_ permission on the object""" |         view_ permission on the object""" | ||||||
|         app_label = instance.content_type.app_label |         app_label = instance.content_type.app_label | ||||||
|         model = instance.content_type.model |         model = instance.content_type.model | ||||||
|  |         try: | ||||||
|             model_class = apps.get_model(app_label, model) |             model_class = apps.get_model(app_label, model) | ||||||
|  |         except LookupError: | ||||||
|  |             return None | ||||||
|         objects = get_objects_for_group(instance.group, f"{app_label}.view_{model}", model_class) |         objects = get_objects_for_group(instance.group, f"{app_label}.view_{model}", model_class) | ||||||
|         obj = objects.first() |         obj = objects.first() | ||||||
|         if not obj: |         if not obj: | ||||||
|  | |||||||
| @ -28,9 +28,12 @@ class ExtraUserObjectPermissionSerializer(UserObjectPermissionSerializer): | |||||||
|  |  | ||||||
|     def get_model_verbose(self, instance: UserObjectPermission) -> str: |     def get_model_verbose(self, instance: UserObjectPermission) -> str: | ||||||
|         """Get model label from permission's model""" |         """Get model label from permission's model""" | ||||||
|  |         try: | ||||||
|             return apps.get_model( |             return apps.get_model( | ||||||
|                 instance.content_type.app_label, instance.content_type.model |                 instance.content_type.app_label, instance.content_type.model | ||||||
|             )._meta.verbose_name |             )._meta.verbose_name | ||||||
|  |         except LookupError: | ||||||
|  |             return f"{instance.content_type.app_label}.{instance.content_type.model}" | ||||||
|  |  | ||||||
|     def get_object_description(self, instance: UserObjectPermission) -> Optional[str]: |     def get_object_description(self, instance: UserObjectPermission) -> Optional[str]: | ||||||
|         """Get model description from attached model. This operation takes at least |         """Get model description from attached model. This operation takes at least | ||||||
| @ -38,7 +41,10 @@ class ExtraUserObjectPermissionSerializer(UserObjectPermissionSerializer): | |||||||
|         view_ permission on the object""" |         view_ permission on the object""" | ||||||
|         app_label = instance.content_type.app_label |         app_label = instance.content_type.app_label | ||||||
|         model = instance.content_type.model |         model = instance.content_type.model | ||||||
|  |         try: | ||||||
|             model_class = apps.get_model(app_label, model) |             model_class = apps.get_model(app_label, model) | ||||||
|  |         except LookupError: | ||||||
|  |             return None | ||||||
|         objects = get_objects_for_user(instance.user, f"{app_label}.view_{model}", model_class) |         objects = get_objects_for_user(instance.user, f"{app_label}.view_{model}", model_class) | ||||||
|         obj = objects.first() |         obj = objects.first() | ||||||
|         if not obj: |         if not obj: | ||||||
|  | |||||||
| @ -12,8 +12,9 @@ class PatreonOAuthRedirect(OAuthRedirect): | |||||||
|     """Patreon OAuth2 Redirect""" |     """Patreon OAuth2 Redirect""" | ||||||
|  |  | ||||||
|     def get_additional_parameters(self, source: OAuthSource):  # pragma: no cover |     def get_additional_parameters(self, source: OAuthSource):  # pragma: no cover | ||||||
|  |         # https://docs.patreon.com/#scopes | ||||||
|         return { |         return { | ||||||
|             "scope": ["openid", "email", "profile"], |             "scope": ["identity", "identity[email]"], | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -69,7 +69,6 @@ class AuthenticatorSMSStageView(ChallengeStageView): | |||||||
|         stage: AuthenticatorSMSStage = self.executor.current_stage |         stage: AuthenticatorSMSStage = self.executor.current_stage | ||||||
|         hashed_number = hash_phone_number(phone_number) |         hashed_number = hash_phone_number(phone_number) | ||||||
|         query = Q(phone_number=hashed_number) | Q(phone_number=phone_number) |         query = Q(phone_number=hashed_number) | Q(phone_number=phone_number) | ||||||
|         print(SMSDevice.objects.filter(query, stage=stage.pk)) |  | ||||||
|         if SMSDevice.objects.filter(query, stage=stage.pk).exists(): |         if SMSDevice.objects.filter(query, stage=stage.pk).exists(): | ||||||
|             raise ValidationError(_("Invalid phone number")) |             raise ValidationError(_("Invalid phone number")) | ||||||
|         # No code yet, but we have a phone number, so send a verification message |         # No code yet, but we have a phone number, so send a verification message | ||||||
|  | |||||||
| @ -199,11 +199,9 @@ class AuthenticatorSMSStageTests(FlowTestCase): | |||||||
|                 sms_send_mock, |                 sms_send_mock, | ||||||
|             ), |             ), | ||||||
|         ): |         ): | ||||||
|             print(self.client.session[SESSION_KEY_PLAN]) |  | ||||||
|             response = self.client.get( |             response = self.client.get( | ||||||
|                 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), |                 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), | ||||||
|             ) |             ) | ||||||
|         print(response.content.decode()) |  | ||||||
|         self.assertStageResponse( |         self.assertStageResponse( | ||||||
|             response, |             response, | ||||||
|             self.flow, |             self.flow, | ||||||
|  | |||||||
| @ -184,6 +184,7 @@ class AuthenticatorValidateStageDuoTests(FlowTestCase): | |||||||
|                     "args": {}, |                     "args": {}, | ||||||
|                     "method": "GET", |                     "method": "GET", | ||||||
|                     "path": f"/api/v3/flows/executor/{flow.slug}/", |                     "path": f"/api/v3/flows/executor/{flow.slug}/", | ||||||
|  |                     "user_agent": "", | ||||||
|                 }, |                 }, | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
|  | |||||||
| @ -1,9 +1,11 @@ | |||||||
| """authentik multi-stage authentication engine""" | """authentik multi-stage authentication engine""" | ||||||
| from datetime import timedelta | from datetime import timedelta | ||||||
|  | from uuid import uuid4 | ||||||
|  |  | ||||||
| from django.contrib import messages | from django.contrib import messages | ||||||
| from django.http import HttpRequest, HttpResponse | from django.http import HttpRequest, HttpResponse | ||||||
| from django.http.request import QueryDict | from django.http.request import QueryDict | ||||||
|  | from django.template.exceptions import TemplateSyntaxError | ||||||
| from django.urls import reverse | from django.urls import reverse | ||||||
| from django.utils.text import slugify | from django.utils.text import slugify | ||||||
| from django.utils.timezone import now | from django.utils.timezone import now | ||||||
| @ -11,11 +13,14 @@ from django.utils.translation import gettext as _ | |||||||
| from rest_framework.fields import CharField | from rest_framework.fields import CharField | ||||||
| from rest_framework.serializers import ValidationError | from rest_framework.serializers import ValidationError | ||||||
|  |  | ||||||
|  | from authentik.events.models import Event, EventAction | ||||||
| from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes | from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes | ||||||
|  | from authentik.flows.exceptions import StageInvalidException | ||||||
| from authentik.flows.models import FlowDesignation, FlowToken | from authentik.flows.models import FlowDesignation, FlowToken | ||||||
| from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER | from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER | ||||||
| from authentik.flows.stage import ChallengeStageView | from authentik.flows.stage import ChallengeStageView | ||||||
| from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY | from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY | ||||||
|  | from authentik.lib.utils.errors import exception_to_string | ||||||
| from authentik.stages.email.models import EmailStage | from authentik.stages.email.models import EmailStage | ||||||
| from authentik.stages.email.tasks import send_mails | from authentik.stages.email.tasks import send_mails | ||||||
| from authentik.stages.email.utils import TemplateEmailMessage | from authentik.stages.email.utils import TemplateEmailMessage | ||||||
| @ -52,16 +57,11 @@ class EmailStageView(ChallengeStageView): | |||||||
|             kwargs={"flow_slug": self.executor.flow.slug}, |             kwargs={"flow_slug": self.executor.flow.slug}, | ||||||
|         ) |         ) | ||||||
|         # Parse query string from current URL (full query string) |         # Parse query string from current URL (full query string) | ||||||
|         query_params = QueryDict(self.request.META.get("QUERY_STRING", ""), mutable=True) |         # this view is only run within a flow executor, where we need to get the query string | ||||||
|  |         # from the query= parameter (double encoded); but for the redirect | ||||||
|  |         # we need to expand it since it'll go through the flow interface | ||||||
|  |         query_params = QueryDict(self.request.GET.get(QS_QUERY), mutable=True) | ||||||
|         query_params.pop(QS_KEY_TOKEN, None) |         query_params.pop(QS_KEY_TOKEN, None) | ||||||
|  |  | ||||||
|         # Check for nested query string used by flow executor, and remove any |  | ||||||
|         # kind of flow token from that |  | ||||||
|         if QS_QUERY in query_params: |  | ||||||
|             inner_query_params = QueryDict(query_params.get(QS_QUERY), mutable=True) |  | ||||||
|             inner_query_params.pop(QS_KEY_TOKEN, None) |  | ||||||
|             query_params[QS_QUERY] = inner_query_params.urlencode() |  | ||||||
|  |  | ||||||
|         query_params.update(kwargs) |         query_params.update(kwargs) | ||||||
|         full_url = base_url |         full_url = base_url | ||||||
|         if len(query_params) > 0: |         if len(query_params) > 0: | ||||||
| @ -75,7 +75,7 @@ class EmailStageView(ChallengeStageView): | |||||||
|         valid_delta = timedelta( |         valid_delta = timedelta( | ||||||
|             minutes=current_stage.token_expiry + 1 |             minutes=current_stage.token_expiry + 1 | ||||||
|         )  # + 1 because django timesince always rounds down |         )  # + 1 because django timesince always rounds down | ||||||
|         identifier = slugify(f"ak-email-stage-{current_stage.name}-{pending_user}") |         identifier = slugify(f"ak-email-stage-{current_stage.name}-{str(uuid4())}") | ||||||
|         # Don't check for validity here, we only care if the token exists |         # Don't check for validity here, we only care if the token exists | ||||||
|         tokens = FlowToken.objects.filter(identifier=identifier) |         tokens = FlowToken.objects.filter(identifier=identifier) | ||||||
|         if not tokens.exists(): |         if not tokens.exists(): | ||||||
| @ -107,6 +107,7 @@ class EmailStageView(ChallengeStageView): | |||||||
|         current_stage: EmailStage = self.executor.current_stage |         current_stage: EmailStage = self.executor.current_stage | ||||||
|         token = self.get_token() |         token = self.get_token() | ||||||
|         # Send mail to user |         # Send mail to user | ||||||
|  |         try: | ||||||
|             message = TemplateEmailMessage( |             message = TemplateEmailMessage( | ||||||
|                 subject=_(current_stage.subject), |                 subject=_(current_stage.subject), | ||||||
|                 to=[email], |                 to=[email], | ||||||
| @ -119,6 +120,14 @@ class EmailStageView(ChallengeStageView): | |||||||
|                 }, |                 }, | ||||||
|             ) |             ) | ||||||
|             send_mails(current_stage, message) |             send_mails(current_stage, message) | ||||||
|  |         except TemplateSyntaxError as exc: | ||||||
|  |             Event.new( | ||||||
|  |                 EventAction.CONFIGURATION_ERROR, | ||||||
|  |                 message=_("Exception occurred while rendering E-mail template"), | ||||||
|  |                 error=exception_to_string(exc), | ||||||
|  |                 template=current_stage.template, | ||||||
|  |             ).from_http(self.request) | ||||||
|  |             raise StageInvalidException from exc | ||||||
|  |  | ||||||
|     def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: |     def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: | ||||||
|         # Check if the user came back from the email link to verify |         # Check if the user came back from the email link to verify | ||||||
| @ -139,7 +148,11 @@ class EmailStageView(ChallengeStageView): | |||||||
|             return self.executor.stage_invalid() |             return self.executor.stage_invalid() | ||||||
|         # Check if we've already sent the initial e-mail |         # Check if we've already sent the initial e-mail | ||||||
|         if PLAN_CONTEXT_EMAIL_SENT not in self.executor.plan.context: |         if PLAN_CONTEXT_EMAIL_SENT not in self.executor.plan.context: | ||||||
|  |             try: | ||||||
|                 self.send_email() |                 self.send_email() | ||||||
|  |             except StageInvalidException as exc: | ||||||
|  |                 self.logger.debug("Got StageInvalidException", exc=exc) | ||||||
|  |                 return self.executor.stage_invalid() | ||||||
|             self.executor.plan.context[PLAN_CONTEXT_EMAIL_SENT] = True |             self.executor.plan.context[PLAN_CONTEXT_EMAIL_SENT] = True | ||||||
|         return super().get(request, *args, **kwargs) |         return super().get(request, *args, **kwargs) | ||||||
|  |  | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ from authentik.events.models import Event, EventAction | |||||||
| from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus | from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus | ||||||
| from authentik.root.celery import CELERY_APP | from authentik.root.celery import CELERY_APP | ||||||
| from authentik.stages.email.models import EmailStage | from authentik.stages.email.models import EmailStage | ||||||
|  | from authentik.stages.email.utils import logo_data | ||||||
|  |  | ||||||
| LOGGER = get_logger() | LOGGER = get_logger() | ||||||
|  |  | ||||||
| @ -81,6 +82,10 @@ def send_mail(self: MonitoredTask, message: dict[Any, Any], email_stage_pk: Opti | |||||||
|         # Because we use the Message-ID as UID for the task, manually assign it |         # Because we use the Message-ID as UID for the task, manually assign it | ||||||
|         message_object.extra_headers["Message-ID"] = message_id |         message_object.extra_headers["Message-ID"] = message_id | ||||||
|  |  | ||||||
|  |         # Add the logo (we can't add it in the previous message since MIMEImage | ||||||
|  |         # can't be converted to json) | ||||||
|  |         message_object.attach(logo_data()) | ||||||
|  |  | ||||||
|         LOGGER.debug("Sending mail", to=message_object.to) |         LOGGER.debug("Sending mail", to=message_object.to) | ||||||
|         backend.send_messages([message_object]) |         backend.send_messages([message_object]) | ||||||
|         Event.new( |         Event.new( | ||||||
|  | |||||||
| @ -259,7 +259,7 @@ class TestEmailStage(FlowTestCase): | |||||||
|         session.save() |         session.save() | ||||||
|  |  | ||||||
|         url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) |         url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) | ||||||
|         url += "?foo=bar" |         url += "?query=" + urlencode({"foo": "bar"}) | ||||||
|         request = self.factory.get(url) |         request = self.factory.get(url) | ||||||
|         stage_view = EmailStageView( |         stage_view = EmailStageView( | ||||||
|             FlowExecutorView( |             FlowExecutorView( | ||||||
| @ -273,31 +273,3 @@ class TestEmailStage(FlowTestCase): | |||||||
|             stage_view.get_full_url(**{QS_KEY_TOKEN: token}), |             stage_view.get_full_url(**{QS_KEY_TOKEN: token}), | ||||||
|             f"http://testserver/if/flow/{self.flow.slug}/?foo=bar&flow_token={token}", |             f"http://testserver/if/flow/{self.flow.slug}/?foo=bar&flow_token={token}", | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def test_url_existing_params_nested(self): |  | ||||||
|         """Test to ensure that URL params are preserved in the URL being sent (including nested)""" |  | ||||||
|         plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) |  | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |  | ||||||
|         session = self.client.session |  | ||||||
|         session[SESSION_KEY_PLAN] = plan |  | ||||||
|         session.save() |  | ||||||
|  |  | ||||||
|         url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) |  | ||||||
|         url += "?foo=bar&" |  | ||||||
|         url += "query=" + urlencode({"nested": "value"}) |  | ||||||
|         request = self.factory.get(url) |  | ||||||
|         stage_view = EmailStageView( |  | ||||||
|             FlowExecutorView( |  | ||||||
|                 request=request, |  | ||||||
|                 flow=self.flow, |  | ||||||
|             ), |  | ||||||
|             request=request, |  | ||||||
|         ) |  | ||||||
|         token = generate_id() |  | ||||||
|         self.assertEqual( |  | ||||||
|             stage_view.get_full_url(**{QS_KEY_TOKEN: token}), |  | ||||||
|             ( |  | ||||||
|                 f"http://testserver/if/flow/{self.flow.slug}" |  | ||||||
|                 f"/?foo=bar&query=nested%3Dvalue&flow_token={token}" |  | ||||||
|             ), |  | ||||||
|         ) |  | ||||||
|  | |||||||
| @ -4,11 +4,20 @@ from pathlib import Path | |||||||
| from shutil import rmtree | from shutil import rmtree | ||||||
| from tempfile import mkdtemp, mkstemp | from tempfile import mkdtemp, mkstemp | ||||||
| from typing import Any | from typing import Any | ||||||
|  | from unittest.mock import PropertyMock, patch | ||||||
|  |  | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.test import TestCase | from django.core.mail.backends.locmem import EmailBackend | ||||||
|  | from django.urls import reverse | ||||||
|  |  | ||||||
| from authentik.stages.email.models import get_template_choices | from authentik.core.tests.utils import create_test_admin_user, create_test_flow | ||||||
|  | from authentik.events.models import Event, EventAction | ||||||
|  | from authentik.flows.markers import StageMarker | ||||||
|  | from authentik.flows.models import FlowDesignation, FlowStageBinding | ||||||
|  | from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan | ||||||
|  | from authentik.flows.tests import FlowTestCase | ||||||
|  | from authentik.flows.views.executor import SESSION_KEY_PLAN | ||||||
|  | from authentik.stages.email.models import EmailStage, get_template_choices | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_templates_setting(temp_dir: str) -> dict[str, Any]: | def get_templates_setting(temp_dir: str) -> dict[str, Any]: | ||||||
| @ -18,11 +27,18 @@ def get_templates_setting(temp_dir: str) -> dict[str, Any]: | |||||||
|     return templates_setting |     return templates_setting | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestEmailStageTemplates(TestCase): | class TestEmailStageTemplates(FlowTestCase): | ||||||
|     """Email tests""" |     """Email tests""" | ||||||
|  |  | ||||||
|     def setUp(self) -> None: |     def setUp(self) -> None: | ||||||
|         self.dir = mkdtemp() |         self.dir = Path(mkdtemp()) | ||||||
|  |         self.user = create_test_admin_user() | ||||||
|  |  | ||||||
|  |         self.flow = create_test_flow(FlowDesignation.AUTHENTICATION) | ||||||
|  |         self.stage = EmailStage.objects.create( | ||||||
|  |             name="email", | ||||||
|  |         ) | ||||||
|  |         self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) | ||||||
|  |  | ||||||
|     def tearDown(self) -> None: |     def tearDown(self) -> None: | ||||||
|         rmtree(self.dir) |         rmtree(self.dir) | ||||||
| @ -38,3 +54,37 @@ class TestEmailStageTemplates(TestCase): | |||||||
|             self.assertEqual(len(choices), 3) |             self.assertEqual(len(choices), 3) | ||||||
|             unlink(file) |             unlink(file) | ||||||
|             unlink(file2) |             unlink(file2) | ||||||
|  |  | ||||||
|  |     def test_custom_template_invalid_syntax(self): | ||||||
|  |         """Test with custom template""" | ||||||
|  |         with open(self.dir / Path("invalid.html"), "w+", encoding="utf-8") as _invalid: | ||||||
|  |             _invalid.write("{% blocktranslate %}") | ||||||
|  |         with self.settings(TEMPLATES=get_templates_setting(self.dir)): | ||||||
|  |             self.stage.template = "invalid.html" | ||||||
|  |             plan = FlowPlan( | ||||||
|  |                 flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|  |             ) | ||||||
|  |             plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|  |             session = self.client.session | ||||||
|  |             session[SESSION_KEY_PLAN] = plan | ||||||
|  |             session.save() | ||||||
|  |  | ||||||
|  |             url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) | ||||||
|  |             with patch( | ||||||
|  |                 "authentik.stages.email.models.EmailStage.backend_class", | ||||||
|  |                 PropertyMock(return_value=EmailBackend), | ||||||
|  |             ): | ||||||
|  |                 response = self.client.get(url) | ||||||
|  |                 self.assertEqual(response.status_code, 200) | ||||||
|  |                 self.assertStageResponse( | ||||||
|  |                     response, | ||||||
|  |                     self.flow, | ||||||
|  |                     error_message="Unknown error", | ||||||
|  |                 ) | ||||||
|  |                 events = Event.objects.filter(action=EventAction.CONFIGURATION_ERROR) | ||||||
|  |                 self.assertEqual(len(events), 1) | ||||||
|  |                 event = events.first() | ||||||
|  |                 self.assertEqual( | ||||||
|  |                     event.context["message"], "Exception occurred while rendering E-mail template" | ||||||
|  |                 ) | ||||||
|  |                 self.assertEqual(event.context["template"], "invalid.html") | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ from django.utils import translation | |||||||
|  |  | ||||||
|  |  | ||||||
| @lru_cache() | @lru_cache() | ||||||
| def logo_data(): | def logo_data() -> MIMEImage: | ||||||
|     """Get logo as MIME Image for emails""" |     """Get logo as MIME Image for emails""" | ||||||
|     path = Path("web/icons/icon_left_brand.png") |     path = Path("web/icons/icon_left_brand.png") | ||||||
|     if not path.exists(): |     if not path.exists(): | ||||||
| @ -29,5 +29,4 @@ class TemplateEmailMessage(EmailMultiAlternatives): | |||||||
|         super().__init__(**kwargs) |         super().__init__(**kwargs) | ||||||
|         self.content_subtype = "html" |         self.content_subtype = "html" | ||||||
|         self.mixed_subtype = "related" |         self.mixed_subtype = "related" | ||||||
|         self.attach(logo_data()) |  | ||||||
|         self.attach_alternative(html_content, "text/html") |         self.attach_alternative(html_content, "text/html") | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ from django.urls import reverse | |||||||
| from authentik.core.models import USER_ATTRIBUTE_SOURCES, Group, Source, User, UserSourceConnection | from authentik.core.models import USER_ATTRIBUTE_SOURCES, Group, Source, User, UserSourceConnection | ||||||
| from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION | from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION | ||||||
| from authentik.core.tests.utils import create_test_admin_user, create_test_flow | from authentik.core.tests.utils import create_test_admin_user, create_test_flow | ||||||
|  | from authentik.events.models import Event, EventAction | ||||||
| from authentik.flows.markers import StageMarker | from authentik.flows.markers import StageMarker | ||||||
| from authentik.flows.models import FlowStageBinding | from authentik.flows.models import FlowStageBinding | ||||||
| from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan | from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan | ||||||
| @ -58,11 +59,33 @@ class TestUserWriteStage(FlowTestCase): | |||||||
|         self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) |         self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) | ||||||
|         user_qs = User.objects.filter(username=plan.context[PLAN_CONTEXT_PROMPT]["username"]) |         user_qs = User.objects.filter(username=plan.context[PLAN_CONTEXT_PROMPT]["username"]) | ||||||
|         self.assertTrue(user_qs.exists()) |         self.assertTrue(user_qs.exists()) | ||||||
|         self.assertTrue(user_qs.first().check_password(password)) |         user = user_qs.first() | ||||||
|         self.assertEqual( |         self.assertTrue(user.check_password(password)) | ||||||
|             list(user_qs.first().ak_groups.order_by("name")), [self.other_group, self.group] |         self.assertEqual(list(user.ak_groups.order_by("name")), [self.other_group, self.group]) | ||||||
|  |         self.assertEqual(user.attributes, {USER_ATTRIBUTE_SOURCES: [self.source.name]}) | ||||||
|  |  | ||||||
|  |         self.assertTrue( | ||||||
|  |             Event.objects.filter( | ||||||
|  |                 action=EventAction.MODEL_CREATED, | ||||||
|  |                 context__model={ | ||||||
|  |                     "app": "authentik_core", | ||||||
|  |                     "model_name": "user", | ||||||
|  |                     "pk": user.pk, | ||||||
|  |                     "name": "name", | ||||||
|  |                 }, | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |         self.assertTrue( | ||||||
|  |             Event.objects.filter( | ||||||
|  |                 action=EventAction.MODEL_UPDATED, | ||||||
|  |                 context__model={ | ||||||
|  |                     "app": "authentik_core", | ||||||
|  |                     "model_name": "user", | ||||||
|  |                     "pk": user.pk, | ||||||
|  |                     "name": "name", | ||||||
|  |                 }, | ||||||
|  |             ) | ||||||
|         ) |         ) | ||||||
|         self.assertEqual(user_qs.first().attributes, {USER_ATTRIBUTE_SOURCES: [self.source.name]}) |  | ||||||
|  |  | ||||||
|     def test_user_update(self): |     def test_user_update(self): | ||||||
|         """Test update of existing user""" |         """Test update of existing user""" | ||||||
|  | |||||||
| @ -85,6 +85,19 @@ entries: | |||||||
|   identifiers: |   identifiers: | ||||||
|     name: default-oobe-password-usable |     name: default-oobe-password-usable | ||||||
|   model: authentik_policies_expression.expressionpolicy |   model: authentik_policies_expression.expressionpolicy | ||||||
|  | - attrs: | ||||||
|  |     expression: | | ||||||
|  |       # This policy ensures that the setup flow can only be | ||||||
|  |       # used one time | ||||||
|  |       from authentik.flows.models import Flow, FlowAuthenticationRequirement | ||||||
|  |       Flow.objects.filter(slug="initial-setup").update( | ||||||
|  |           authentication=FlowAuthenticationRequirement.REQUIRE_SUPERUSER, | ||||||
|  |       ) | ||||||
|  |       return True | ||||||
|  |   id: policy-default-oobe-flow-set-authentication | ||||||
|  |   identifiers: | ||||||
|  |     name: default-oobe-flow-set-authentication | ||||||
|  |   model: authentik_policies_expression.expressionpolicy | ||||||
| - attrs: | - attrs: | ||||||
|     fields: |     fields: | ||||||
|     - !KeyOf prompt-field-header |     - !KeyOf prompt-field-header | ||||||
| @ -129,6 +142,7 @@ entries: | |||||||
|     evaluate_on_plan: true |     evaluate_on_plan: true | ||||||
|     invalid_response_action: retry |     invalid_response_action: retry | ||||||
|     re_evaluate_policies: false |     re_evaluate_policies: false | ||||||
|  |   id: binding-login | ||||||
|   identifiers: |   identifiers: | ||||||
|     order: 100 |     order: 100 | ||||||
|     stage: !KeyOf stage-default-authentication-login |     stage: !KeyOf stage-default-authentication-login | ||||||
| @ -144,3 +158,8 @@ entries: | |||||||
|     policy: !KeyOf policy-default-oobe-prefill-user |     policy: !KeyOf policy-default-oobe-prefill-user | ||||||
|     target: !KeyOf binding-password-write |     target: !KeyOf binding-password-write | ||||||
|   model: authentik_policies.policybinding |   model: authentik_policies.policybinding | ||||||
|  | - identifiers: | ||||||
|  |     order: 0 | ||||||
|  |     policy: !KeyOf policy-default-oobe-flow-set-authentication | ||||||
|  |     target: !KeyOf binding-login | ||||||
|  |   model: authentik_policies.policybinding | ||||||
|  | |||||||
| @ -42,9 +42,3 @@ entries: | |||||||
|       user: !KeyOf admin-user |       user: !KeyOf admin-user | ||||||
|     attrs: |     attrs: | ||||||
|       key: !Context token |       key: !Context token | ||||||
|   - model: authentik_blueprints.blueprintinstance |  | ||||||
|     identifiers: |  | ||||||
|       metadata: |  | ||||||
|         labels: |  | ||||||
|           blueprints.goauthentik.io/system-bootstrap: "true" |  | ||||||
|     state: absent |  | ||||||
|  | |||||||
| @ -12,12 +12,14 @@ entries: | |||||||
|       expression: | |       expression: | | ||||||
|         # Some implementations require givenName and familyName to be set |         # Some implementations require givenName and familyName to be set | ||||||
|         givenName, familyName = request.user.name, " " |         givenName, familyName = request.user.name, " " | ||||||
|  |         formatted = request.user.name + " " | ||||||
|         # This default sets givenName to the name before the first space |         # This default sets givenName to the name before the first space | ||||||
|         # and the remainder as family name |         # and the remainder as family name | ||||||
|         # if the user's name has no space the givenName is the entire name |         # if the user's name has no space the givenName is the entire name | ||||||
|         # (this might cause issues with some SCIM implementations) |         # (this might cause issues with some SCIM implementations) | ||||||
|         if " " in request.user.name: |         if " " in request.user.name: | ||||||
|             givenName, _, familyName = request.user.name.partition(" ") |             givenName, _, familyName = request.user.name.partition(" ") | ||||||
|  |             formatted = request.user.name | ||||||
|  |  | ||||||
|         # photos supports URLs to images, however authentik might return data URIs |         # photos supports URLs to images, however authentik might return data URIs | ||||||
|         avatar = request.user.avatar |         avatar = request.user.avatar | ||||||
| @ -39,7 +41,7 @@ entries: | |||||||
|         return { |         return { | ||||||
|             "userName": request.user.username, |             "userName": request.user.username, | ||||||
|             "name": { |             "name": { | ||||||
|                 "formatted": request.user.name, |                 "formatted": formatted, | ||||||
|                 "givenName": givenName, |                 "givenName": givenName, | ||||||
|                 "familyName": familyName, |                 "familyName": familyName, | ||||||
|             }, |             }, | ||||||
|  | |||||||
| @ -32,7 +32,7 @@ services: | |||||||
|     volumes: |     volumes: | ||||||
|       - redis:/data |       - redis:/data | ||||||
|   server: |   server: | ||||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.1} |     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.5} | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     command: server |     command: server | ||||||
|     environment: |     environment: | ||||||
| @ -53,7 +53,7 @@ services: | |||||||
|       - postgresql |       - postgresql | ||||||
|       - redis |       - redis | ||||||
|   worker: |   worker: | ||||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.1} |     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.5} | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     command: worker |     command: worker | ||||||
|     environment: |     environment: | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							| @ -12,7 +12,7 @@ require ( | |||||||
| 	github.com/go-openapi/runtime v0.26.0 | 	github.com/go-openapi/runtime v0.26.0 | ||||||
| 	github.com/go-openapi/strfmt v0.21.7 | 	github.com/go-openapi/strfmt v0.21.7 | ||||||
| 	github.com/golang-jwt/jwt v3.2.2+incompatible | 	github.com/golang-jwt/jwt v3.2.2+incompatible | ||||||
| 	github.com/google/uuid v1.3.1 | 	github.com/google/uuid v1.4.0 | ||||||
| 	github.com/gorilla/handlers v1.5.1 | 	github.com/gorilla/handlers v1.5.1 | ||||||
| 	github.com/gorilla/mux v1.8.0 | 	github.com/gorilla/mux v1.8.0 | ||||||
| 	github.com/gorilla/securecookie v1.1.1 | 	github.com/gorilla/securecookie v1.1.1 | ||||||
| @ -27,7 +27,7 @@ require ( | |||||||
| 	github.com/sirupsen/logrus v1.9.3 | 	github.com/sirupsen/logrus v1.9.3 | ||||||
| 	github.com/spf13/cobra v1.7.0 | 	github.com/spf13/cobra v1.7.0 | ||||||
| 	github.com/stretchr/testify v1.8.4 | 	github.com/stretchr/testify v1.8.4 | ||||||
| 	goauthentik.io/api/v3 v3.2023083.10 | 	goauthentik.io/api/v3 v3.2023101.1 | ||||||
| 	golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab | 	golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab | ||||||
| 	golang.org/x/oauth2 v0.13.0 | 	golang.org/x/oauth2 v0.13.0 | ||||||
| 	golang.org/x/sync v0.4.0 | 	golang.org/x/sync v0.4.0 | ||||||
|  | |||||||
							
								
								
									
										7
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								go.sum
									
									
									
									
									
								
							| @ -211,8 +211,9 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf | |||||||
| github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= | ||||||
| github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= | ||||||
| github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
| github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= |  | ||||||
| github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
|  | github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= | ||||||
|  | github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
| github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= | ||||||
| github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= | ||||||
| github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= | github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= | ||||||
| @ -355,8 +356,8 @@ go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyK | |||||||
| go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= | go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= | ||||||
| go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= | go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= | ||||||
| go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= | go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= | ||||||
| goauthentik.io/api/v3 v3.2023083.10 h1:mMCOfsqjouSSxedSkCK4k0Cwtt68CWzQgR7Um6ooOQs= | goauthentik.io/api/v3 v3.2023101.1 h1:KIQ4wmxjE+geAVB0wBfmxW9Uzo/tA0dbd2hSUJ7YJ3M= | ||||||
| goauthentik.io/api/v3 v3.2023083.10/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | goauthentik.io/api/v3 v3.2023101.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | ||||||
| golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||||
| golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= | golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= | ||||||
|  | |||||||
| @ -29,4 +29,4 @@ func UserAgent() string { | |||||||
| 	return fmt.Sprintf("authentik@%s", FullVersion()) | 	return fmt.Sprintf("authentik@%s", FullVersion()) | ||||||
| } | } | ||||||
|  |  | ||||||
| const VERSION = "2023.10.1" | const VERSION = "2023.10.5" | ||||||
|  | |||||||
| @ -50,7 +50,7 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) | |||||||
| 			Domain:   *p.CookieDomain, | 			Domain:   *p.CookieDomain, | ||||||
| 			SameSite: http.SameSiteLaxMode, | 			SameSite: http.SameSiteLaxMode, | ||||||
| 			MaxAge:   maxAge, | 			MaxAge:   maxAge, | ||||||
| 			Path:     externalHost.Path, | 			Path:     "/", | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 		a.log.Trace("using redis session backend") | 		a.log.Trace("using redis session backend") | ||||||
| @ -71,7 +71,7 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL) | |||||||
| 	cs.Options.Domain = *p.CookieDomain | 	cs.Options.Domain = *p.CookieDomain | ||||||
| 	cs.Options.SameSite = http.SameSiteLaxMode | 	cs.Options.SameSite = http.SameSiteLaxMode | ||||||
| 	cs.Options.MaxAge = maxAge | 	cs.Options.MaxAge = maxAge | ||||||
| 	cs.Options.Path = externalHost.Path | 	cs.Options.Path = "/" | ||||||
| 	a.log.WithField("dir", dir).Trace("using filesystem session backend") | 	a.log.WithField("dir", dir).Trace("using filesystem session backend") | ||||||
| 	return cs | 	return cs | ||||||
| } | } | ||||||
| @ -131,7 +131,6 @@ func (a *Application) Logout(ctx context.Context, filter func(c Claims) bool) er | |||||||
| 	} | 	} | ||||||
| 	if rs, ok := a.sessions.(*redisstore.RedisStore); ok { | 	if rs, ok := a.sessions.(*redisstore.RedisStore); ok { | ||||||
| 		client := rs.Client() | 		client := rs.Client() | ||||||
| 		defer client.Close() |  | ||||||
| 		keys, err := client.Keys(ctx, fmt.Sprintf("%s*", RedisKeyPrefix)).Result() | 		keys, err := client.Keys(ctx, fmt.Sprintf("%s*", RedisKeyPrefix)).Result() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
|  | |||||||
| @ -1,5 +1,14 @@ | |||||||
|  | # syntax=docker/dockerfile:1 | ||||||
|  |  | ||||||
| # Stage 1: Build | # Stage 1: Build | ||||||
| FROM docker.io/golang:1.21.3-bookworm AS builder | FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.4-bookworm AS builder | ||||||
|  |  | ||||||
|  | ARG TARGETOS | ||||||
|  | ARG TARGETARCH | ||||||
|  | ARG TARGETVARIANT | ||||||
|  |  | ||||||
|  | ARG GOOS=$TARGETOS | ||||||
|  | ARG GOARCH=$TARGETARCH | ||||||
|  |  | ||||||
| WORKDIR /go/src/goauthentik.io | WORKDIR /go/src/goauthentik.io | ||||||
|  |  | ||||||
| @ -11,9 +20,9 @@ RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \ | |||||||
|  |  | ||||||
| ENV CGO_ENABLED=0 | ENV CGO_ENABLED=0 | ||||||
| COPY . . | COPY . . | ||||||
| RUN --mount=type=cache,target=/go/pkg/mod \ | RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \ | ||||||
|     --mount=type=cache,target=/root/.cache/go-build \ |     --mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \ | ||||||
|     go build -o /go/ldap ./cmd/ldap |     GOARM="${TARGETVARIANT#v}" go build -o /go/ldap ./cmd/ldap | ||||||
|  |  | ||||||
| # Stage 2: Run | # Stage 2: Run | ||||||
| FROM gcr.io/distroless/static-debian11:debug | FROM gcr.io/distroless/static-debian11:debug | ||||||
|  | |||||||
| @ -3,13 +3,17 @@ from lifecycle.migrate import BaseMigration | |||||||
|  |  | ||||||
| SQL_STATEMENT = """ | SQL_STATEMENT = """ | ||||||
| BEGIN TRANSACTION; | BEGIN TRANSACTION; | ||||||
| DELETE FROM django_migrations WHERE app = 'otp_static'; | -- Update migrations (static) | ||||||
| DELETE FROM django_migrations WHERE app = 'otp_totp'; | UPDATE django_migrations SET app = 'authentik_stages_authenticator_static', name = '0008_initial' WHERE app = 'otp_static' AND name = '0001_initial'; | ||||||
|  | UPDATE django_migrations SET app = 'authentik_stages_authenticator_static', name = '0009_throttling' WHERE app = 'otp_static' AND name = '0002_throttling'; | ||||||
| -- Rename tables (static) | -- Rename tables (static) | ||||||
| ALTER TABLE otp_static_staticdevice RENAME TO authentik_stages_authenticator_static_staticdevice; | ALTER TABLE otp_static_staticdevice RENAME TO authentik_stages_authenticator_static_staticdevice; | ||||||
| ALTER TABLE otp_static_statictoken RENAME TO authentik_stages_authenticator_static_statictoken; | ALTER TABLE otp_static_statictoken RENAME TO authentik_stages_authenticator_static_statictoken; | ||||||
| ALTER SEQUENCE otp_static_statictoken_id_seq RENAME TO authentik_stages_authenticator_static_statictoken_id_seq; | ALTER SEQUENCE otp_static_statictoken_id_seq RENAME TO authentik_stages_authenticator_static_statictoken_id_seq; | ||||||
| ALTER SEQUENCE otp_static_staticdevice_id_seq RENAME TO authentik_stages_authenticator_static_staticdevice_id_seq; | ALTER SEQUENCE otp_static_staticdevice_id_seq RENAME TO authentik_stages_authenticator_static_staticdevice_id_seq; | ||||||
|  | -- Update migrations (totp) | ||||||
|  | UPDATE django_migrations SET app = 'authentik_stages_authenticator_totp', name = '0008_initial' WHERE app = 'otp_totp' AND name = '0001_initial'; | ||||||
|  | UPDATE django_migrations SET app = 'authentik_stages_authenticator_totp', name = '0009_auto_20190420_0723' WHERE app = 'otp_totp' AND name = '0002_auto_20190420_0723'; | ||||||
| -- Rename tables (totp) | -- Rename tables (totp) | ||||||
| ALTER TABLE otp_totp_totpdevice RENAME TO authentik_stages_authenticator_totp_totpdevice; | ALTER TABLE otp_totp_totpdevice RENAME TO authentik_stages_authenticator_totp_totpdevice; | ||||||
| ALTER SEQUENCE otp_totp_totpdevice_id_seq RENAME TO authentik_stages_authenticator_totp_totpdevice_id_seq; | ALTER SEQUENCE otp_totp_totpdevice_id_seq RENAME TO authentik_stages_authenticator_totp_totpdevice_id_seq; | ||||||
| @ -25,22 +29,9 @@ class Migration(BaseMigration): | |||||||
|         return bool(self.cur.rowcount) |         return bool(self.cur.rowcount) | ||||||
|  |  | ||||||
|     def run(self): |     def run(self): | ||||||
|         self.cur.execute(SQL_STATEMENT) |         self.cur.execute( | ||||||
|         self.fake_migration( |             "SELECT * FROM django_migrations WHERE app = 'authentik_stages_authenticator_static' AND name = '0007_authenticatorstaticstage_token_length_and_more';" | ||||||
|             ( |  | ||||||
|                 "authentik_stages_authenticator_static", |  | ||||||
|                 "0008_initial", |  | ||||||
|             ), |  | ||||||
|             ( |  | ||||||
|                 "authentik_stages_authenticator_static", |  | ||||||
|                 "0009_throttling", |  | ||||||
|             ), |  | ||||||
|             ( |  | ||||||
|                 "authentik_stages_authenticator_totp", |  | ||||||
|                 "0008_initial", |  | ||||||
|             ), |  | ||||||
|             ( |  | ||||||
|                 "authentik_stages_authenticator_totp", |  | ||||||
|                 "0009_auto_20190420_0723", |  | ||||||
|             ), |  | ||||||
|         ) |         ) | ||||||
|  |         if not bool(self.cur.rowcount): | ||||||
|  |             raise Exception("Please upgrade to 2023.8 before upgrading to 2023.10") | ||||||
|  |         self.cur.execute(SQL_STATEMENT) | ||||||
|  | |||||||
							
								
								
									
										73
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										73
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. | # This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aiohttp" | name = "aiohttp" | ||||||
| @ -2096,16 +2096,6 @@ files = [ | |||||||
|     {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, |     {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, | ||||||
|     {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, |     {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, | ||||||
|     {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, |     {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, | ||||||
|     {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, |  | ||||||
|     {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, |  | ||||||
|     {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, |  | ||||||
|     {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, |  | ||||||
|     {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, |  | ||||||
|     {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, |  | ||||||
|     {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, |  | ||||||
|     {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, |  | ||||||
|     {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, |  | ||||||
|     {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, |  | ||||||
|     {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, |     {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, | ||||||
|     {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, |     {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, | ||||||
|     {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, |     {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, | ||||||
| @ -2780,13 +2770,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "pydantic-scim" | name = "pydantic-scim" | ||||||
| version = "0.0.7" | version = "0.0.8" | ||||||
| description = "Pydantic types for SCIM" | description = "Pydantic types for SCIM" | ||||||
| optional = false | optional = false | ||||||
| python-versions = ">=3.8.0" | python-versions = ">=3.8.0" | ||||||
| files = [ | files = [ | ||||||
|     {file = "pydantic-scim-0.0.7.tar.gz", hash = "sha256:bc043da51c346051dfd372f12d1837c0846b815236340156d663a8514cba5761"}, |     {file = "pydantic-scim-0.0.8.tar.gz", hash = "sha256:b6c62031126e8c54f0fc7df837678e63934a5b068533fc52e5dfb6cfc24d59e9"}, | ||||||
|     {file = "pydantic_scim-0.0.7-py3-none-any.whl", hash = "sha256:058eb195f75ef32d04eaf6369c125d5fb7052891694686f8e55e04d184ab1360"}, |     {file = "pydantic_scim-0.0.8-py3-none-any.whl", hash = "sha256:407b3bf55240947155c77a6dd839881d63368c61d64076d6b167ef124ceac79a"}, | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [package.dependencies] | [package.dependencies] | ||||||
| @ -2840,10 +2830,7 @@ files = [ | |||||||
| [package.dependencies] | [package.dependencies] | ||||||
| astroid = ">=3.0.1,<=3.1.0-dev0" | astroid = ">=3.0.1,<=3.1.0-dev0" | ||||||
| colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} | colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} | ||||||
| dill = [ | dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""} | ||||||
|     {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, |  | ||||||
|     {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, |  | ||||||
| ] |  | ||||||
| isort = ">=4.2.5,<6" | isort = ">=4.2.5,<6" | ||||||
| mccabe = ">=0.6,<0.8" | mccabe = ">=0.6,<0.8" | ||||||
| platformdirs = ">=2.2.0" | platformdirs = ">=2.2.0" | ||||||
| @ -3096,7 +3083,6 @@ files = [ | |||||||
|     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, |     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, | ||||||
|     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, |     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, | ||||||
|     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, |     {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, | ||||||
|     {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, |  | ||||||
|     {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, |     {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, | ||||||
|     {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, |     {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, | ||||||
|     {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, |     {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, | ||||||
| @ -3104,15 +3090,8 @@ files = [ | |||||||
|     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, |     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, | ||||||
|     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, |     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, | ||||||
|     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, |     {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, | ||||||
|     {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, |  | ||||||
|     {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, |     {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, | ||||||
|     {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, |     {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, | ||||||
|     {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, |  | ||||||
|     {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, |  | ||||||
|     {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, |  | ||||||
|     {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, |  | ||||||
|     {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, |  | ||||||
|     {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, |  | ||||||
|     {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, |     {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, | ||||||
|     {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, |     {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, | ||||||
|     {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, |     {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, | ||||||
| @ -3129,7 +3108,6 @@ files = [ | |||||||
|     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, |     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, | ||||||
|     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, |     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, | ||||||
|     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, |     {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, | ||||||
|     {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, |  | ||||||
|     {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, |     {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, | ||||||
|     {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, |     {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, | ||||||
|     {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, |     {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, | ||||||
| @ -3137,7 +3115,6 @@ files = [ | |||||||
|     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, |     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, | ||||||
|     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, |     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, | ||||||
|     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, |     {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, | ||||||
|     {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, |  | ||||||
|     {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, |     {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, | ||||||
|     {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, |     {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, | ||||||
|     {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, |     {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, | ||||||
| @ -3374,28 +3351,28 @@ pyasn1 = ">=0.1.3" | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "ruff" | name = "ruff" | ||||||
| version = "0.1.2" | version = "0.1.3" | ||||||
| description = "An extremely fast Python linter, written in Rust." | description = "An extremely fast Python linter, written in Rust." | ||||||
| optional = false | optional = false | ||||||
| python-versions = ">=3.7" | python-versions = ">=3.7" | ||||||
| files = [ | files = [ | ||||||
|     {file = "ruff-0.1.2-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:0d3ee66b825b713611f89aa35d16de984f76f26c50982a25d52cd0910dff3923"}, |     {file = "ruff-0.1.3-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b46d43d51f7061652eeadb426a9e3caa1e0002470229ab2fc19de8a7b0766901"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:f85f850a320ff532b8f93e8d1da6a36ef03698c446357c8c43b46ef90bb321eb"}, |     {file = "ruff-0.1.3-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:b8afeb9abd26b4029c72adc9921b8363374f4e7edb78385ffaa80278313a15f9"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:809c6d4e45683696d19ca79e4c6bd3b2e9204fe9546923f2eb3b126ec314b0dc"}, |     {file = "ruff-0.1.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca3cf365bf32e9ba7e6db3f48a4d3e2c446cd19ebee04f05338bc3910114528b"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46005e4abb268e93cad065244e17e2ea16b6fcb55a5c473f34fbc1fd01ae34cb"}, |     {file = "ruff-0.1.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4874c165f96c14a00590dcc727a04dca0cfd110334c24b039458c06cf78a672e"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10cdb302f519664d5e2cf954562ac86c9d20ca05855e5b5c2f9d542228f45da4"}, |     {file = "ruff-0.1.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eec2dd31eed114e48ea42dbffc443e9b7221976554a504767ceaee3dd38edeb8"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f89ebcbe57a1eab7d7b4ceb57ddf0af9ed13eae24e443a7c1dc078000bd8cc6b"}, |     {file = "ruff-0.1.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dc3ec4edb3b73f21b4aa51337e16674c752f1d76a4a543af56d7d04e97769613"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7344eaca057d4c32373c9c3a7afb7274f56040c225b6193dd495fcf69453b436"}, |     {file = "ruff-0.1.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e3de9ed2e39160800281848ff4670e1698037ca039bda7b9274f849258d26ce"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dffa25f6e03c4950b6ac6f216bc0f98a4be9719cb0c5260c8e88d1bac36f1683"}, |     {file = "ruff-0.1.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c595193881922cc0556a90f3af99b1c5681f0c552e7a2a189956141d8666fe8"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42ddaea52cb7ba7c785e8593a7532866c193bc774fe570f0e4b1ccedd95b83c5"}, |     {file = "ruff-0.1.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f75e670d529aa2288cd00fc0e9b9287603d95e1536d7a7e0cafe00f75e0dd9d"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8533efda625bbec0bf27da2886bd641dae0c209104f6c39abc4be5b7b22de2a"}, |     {file = "ruff-0.1.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:76dd49f6cd945d82d9d4a9a6622c54a994689d8d7b22fa1322983389b4892e20"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b0b1b82221ba7c50e03b7a86b983157b5d3f4d8d4f16728132bdf02c6d651f77"}, |     {file = "ruff-0.1.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:918b454bc4f8874a616f0d725590277c42949431ceb303950e87fef7a7d94cb3"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6c1362eb9288f8cc95535294cb03bd4665c8cef86ec32745476a4e5c6817034c"}, |     {file = "ruff-0.1.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d8859605e729cd5e53aa38275568dbbdb4fe882d2ea2714c5453b678dca83784"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ffa7ef5ded0563329a35bd5a1cfdae40f05a75c0cc2dd30f00b1320b1fb461fc"}, |     {file = "ruff-0.1.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0b6c55f5ef8d9dd05b230bb6ab80bc4381ecb60ae56db0330f660ea240cb0d4a"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-win32.whl", hash = "sha256:6e8073f85e47072256e2e1909f1ae515cf61ff5a4d24730a63b8b4ac24b6704a"}, |     {file = "ruff-0.1.3-py3-none-win32.whl", hash = "sha256:3e7afcbdcfbe3399c34e0f6370c30f6e529193c731b885316c5a09c9e4317eef"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-win_amd64.whl", hash = "sha256:b836ddff662a45385948ee0878b0a04c3a260949905ad861a37b931d6ee1c210"}, |     {file = "ruff-0.1.3-py3-none-win_amd64.whl", hash = "sha256:7a18df6638cec4a5bd75350639b2bb2a2366e01222825562c7346674bdceb7ea"}, | ||||||
|     {file = "ruff-0.1.2-py3-none-win_arm64.whl", hash = "sha256:b0c42d00db5639dbd5f7f9923c63648682dd197bf5de1151b595160c96172691"}, |     {file = "ruff-0.1.3-py3-none-win_arm64.whl", hash = "sha256:12fd53696c83a194a2db7f9a46337ce06445fb9aa7d25ea6f293cf75b21aca9f"}, | ||||||
|     {file = "ruff-0.1.2.tar.gz", hash = "sha256:afd4785ae060ce6edcd52436d0c197628a918d6d09e3107a892a1bad6a4c6608"}, |     {file = "ruff-0.1.3.tar.gz", hash = "sha256:3ba6145369a151401d5db79f0a47d50e470384d0d89d0d6f7fab0b589ad07c34"}, | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| @ -4331,5 +4308,5 @@ files = [ | |||||||
|  |  | ||||||
| [metadata] | [metadata] | ||||||
| lock-version = "2.0" | lock-version = "2.0" | ||||||
| python-versions = "^3.11" | python-versions = "~3.11" | ||||||
| content-hash = "e6b1df989cb5c50609540c1229d05d8458ef1cc343fb5868402db8b7679ad73c" | content-hash = "5a57dede617d149e0f307fc42580dcfd0d4b76161009dc447d6f10b048426c98" | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | # syntax=docker/dockerfile:1 | ||||||
|  |  | ||||||
| # Stage 1: Build website | # Stage 1: Build website | ||||||
| FROM --platform=${BUILDPLATFORM} docker.io/node:21 as web-builder | FROM --platform=${BUILDPLATFORM} docker.io/node:21 as web-builder | ||||||
|  |  | ||||||
| @ -15,7 +17,14 @@ COPY web . | |||||||
| RUN npm run build-proxy | RUN npm run build-proxy | ||||||
|  |  | ||||||
| # Stage 2: Build | # Stage 2: Build | ||||||
| FROM docker.io/golang:1.21.3-bookworm AS builder | FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.4-bookworm AS builder | ||||||
|  |  | ||||||
|  | ARG TARGETOS | ||||||
|  | ARG TARGETARCH | ||||||
|  | ARG TARGETVARIANT | ||||||
|  |  | ||||||
|  | ARG GOOS=$TARGETOS | ||||||
|  | ARG GOARCH=$TARGETARCH | ||||||
|  |  | ||||||
| WORKDIR /go/src/goauthentik.io | WORKDIR /go/src/goauthentik.io | ||||||
|  |  | ||||||
| @ -27,9 +36,9 @@ RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \ | |||||||
|  |  | ||||||
| ENV CGO_ENABLED=0 | ENV CGO_ENABLED=0 | ||||||
| COPY . . | COPY . . | ||||||
| RUN --mount=type=cache,target=/go/pkg/mod \ | RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \ | ||||||
|     --mount=type=cache,target=/root/.cache/go-build \ |     --mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \ | ||||||
|     go build -o /go/proxy ./cmd/proxy |     GOARM="${TARGETVARIANT#v}" go build -o /go/proxy ./cmd/proxy | ||||||
|  |  | ||||||
| # Stage 3: Run | # Stage 3: Run | ||||||
| FROM gcr.io/distroless/static-debian11:debug | FROM gcr.io/distroless/static-debian11:debug | ||||||
|  | |||||||
| @ -97,7 +97,7 @@ const-rgx = "[a-zA-Z0-9_]{1,40}$" | |||||||
|  |  | ||||||
| ignored-modules = ["binascii", "socket", "zlib"] | ignored-modules = ["binascii", "socket", "zlib"] | ||||||
| generated-members = ["xmlsec.constants.*", "xmlsec.tree.*", "xmlsec.template.*"] | generated-members = ["xmlsec.constants.*", "xmlsec.tree.*", "xmlsec.template.*"] | ||||||
| ignore = "migrations" | ignore = ["migrations", "tests"] | ||||||
| max-attributes = 12 | max-attributes = 12 | ||||||
| max-branches = 20 | max-branches = 20 | ||||||
|  |  | ||||||
| @ -113,7 +113,7 @@ filterwarnings = [ | |||||||
|  |  | ||||||
| [tool.poetry] | [tool.poetry] | ||||||
| name = "authentik" | name = "authentik" | ||||||
| version = "2023.10.1" | version = "2023.10.5" | ||||||
| description = "" | description = "" | ||||||
| authors = ["authentik Team <hello@goauthentik.io>"] | authors = ["authentik Team <hello@goauthentik.io>"] | ||||||
|  |  | ||||||
| @ -151,10 +151,10 @@ packaging = "*" | |||||||
| paramiko = "*" | paramiko = "*" | ||||||
| psycopg = { extras = ["c"], version = "*" } | psycopg = { extras = ["c"], version = "*" } | ||||||
| pycryptodome = "*" | pycryptodome = "*" | ||||||
| pydantic = "<3.0.0" | pydantic = "*" | ||||||
| pydantic-scim = "^0.0.7" | pydantic-scim = "*" | ||||||
| pyjwt = "*" | pyjwt = "*" | ||||||
| python = "^3.11" | python = "~3.11" | ||||||
| pyyaml = "*" | pyyaml = "*" | ||||||
| requests-oauthlib = "*" | requests-oauthlib = "*" | ||||||
| sentry-sdk = "*" | sentry-sdk = "*" | ||||||
|  | |||||||
| @ -1,5 +1,14 @@ | |||||||
|  | # syntax=docker/dockerfile:1 | ||||||
|  |  | ||||||
| # Stage 1: Build | # Stage 1: Build | ||||||
| FROM docker.io/golang:1.21.3-bookworm AS builder | FROM --platform=${BUILDPLATFORM} docker.io/golang:1.21.4-bookworm AS builder | ||||||
|  |  | ||||||
|  | ARG TARGETOS | ||||||
|  | ARG TARGETARCH | ||||||
|  | ARG TARGETVARIANT | ||||||
|  |  | ||||||
|  | ARG GOOS=$TARGETOS | ||||||
|  | ARG GOARCH=$TARGETARCH | ||||||
|  |  | ||||||
| WORKDIR /go/src/goauthentik.io | WORKDIR /go/src/goauthentik.io | ||||||
|  |  | ||||||
| @ -11,9 +20,9 @@ RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \ | |||||||
|  |  | ||||||
| ENV CGO_ENABLED=0 | ENV CGO_ENABLED=0 | ||||||
| COPY . . | COPY . . | ||||||
| RUN --mount=type=cache,target=/go/pkg/mod \ | RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \ | ||||||
|     --mount=type=cache,target=/root/.cache/go-build \ |     --mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \ | ||||||
|     go build -o /go/radius ./cmd/radius |     GOARM="${TARGETVARIANT#v}" go build -o /go/radius ./cmd/radius | ||||||
|  |  | ||||||
| # Stage 2: Run | # Stage 2: Run | ||||||
| FROM gcr.io/distroless/static-debian11:debug | FROM gcr.io/distroless/static-debian11:debug | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| openapi: 3.0.3 | openapi: 3.0.3 | ||||||
| info: | info: | ||||||
|   title: authentik |   title: authentik | ||||||
|   version: 2023.10.1 |   version: 2023.10.5 | ||||||
|   description: Making authentication simple. |   description: Making authentication simple. | ||||||
|   contact: |   contact: | ||||||
|     email: hello@goauthentik.io |     email: hello@goauthentik.io | ||||||
|  | |||||||
| @ -36,8 +36,8 @@ class TestProviderOAuth2Github(SeleniumTestCase): | |||||||
|             "auto_remove": True, |             "auto_remove": True, | ||||||
|             "healthcheck": Healthcheck( |             "healthcheck": Healthcheck( | ||||||
|                 test=["CMD", "wget", "--spider", "http://localhost:3000"], |                 test=["CMD", "wget", "--spider", "http://localhost:3000"], | ||||||
|                 interval=5 * 100 * 1000000, |                 interval=5 * 1_000 * 1_000_000, | ||||||
|                 start_period=1 * 100 * 1000000, |                 start_period=1 * 1_000 * 1_000_000, | ||||||
|             ), |             ), | ||||||
|             "environment": { |             "environment": { | ||||||
|                 "GF_AUTH_GITHUB_ENABLED": "true", |                 "GF_AUTH_GITHUB_ENABLED": "true", | ||||||
|  | |||||||
| @ -42,8 +42,8 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): | |||||||
|             "auto_remove": True, |             "auto_remove": True, | ||||||
|             "healthcheck": Healthcheck( |             "healthcheck": Healthcheck( | ||||||
|                 test=["CMD", "wget", "--spider", "http://localhost:3000"], |                 test=["CMD", "wget", "--spider", "http://localhost:3000"], | ||||||
|                 interval=5 * 100 * 1000000, |                 interval=5 * 1_000 * 1_000_000, | ||||||
|                 start_period=1 * 100 * 1000000, |                 start_period=1 * 1_000 * 1_000_000, | ||||||
|             ), |             ), | ||||||
|             "environment": { |             "environment": { | ||||||
|                 "GF_AUTH_GENERIC_OAUTH_ENABLED": "true", |                 "GF_AUTH_GENERIC_OAUTH_ENABLED": "true", | ||||||
|  | |||||||
| @ -113,8 +113,8 @@ class TestSourceOAuth2(SeleniumTestCase): | |||||||
|             "command": "dex serve /config.yml", |             "command": "dex serve /config.yml", | ||||||
|             "healthcheck": Healthcheck( |             "healthcheck": Healthcheck( | ||||||
|                 test=["CMD", "wget", "--spider", "http://localhost:5556/dex/healthz"], |                 test=["CMD", "wget", "--spider", "http://localhost:5556/dex/healthz"], | ||||||
|                 interval=5 * 100 * 1000000, |                 interval=5 * 1_000 * 1_000_000, | ||||||
|                 start_period=1 * 100 * 1000000, |                 start_period=1 * 1_000 * 1_000_000, | ||||||
|             ), |             ), | ||||||
|             "volumes": {str(Path(CONFIG_PATH).absolute()): {"bind": "/config.yml", "mode": "ro"}}, |             "volumes": {str(Path(CONFIG_PATH).absolute()): {"bind": "/config.yml", "mode": "ro"}}, | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -83,8 +83,8 @@ class TestSourceSAML(SeleniumTestCase): | |||||||
|             "auto_remove": True, |             "auto_remove": True, | ||||||
|             "healthcheck": Healthcheck( |             "healthcheck": Healthcheck( | ||||||
|                 test=["CMD", "curl", "http://localhost:8080"], |                 test=["CMD", "curl", "http://localhost:8080"], | ||||||
|                 interval=5 * 100 * 1000000, |                 interval=5 * 1_000 * 1_000_000, | ||||||
|                 start_period=1 * 100 * 1000000, |                 start_period=1 * 1_000 * 1_000_000, | ||||||
|             ), |             ), | ||||||
|             "environment": { |             "environment": { | ||||||
|                 "SIMPLESAMLPHP_SP_ENTITY_ID": "entity-id", |                 "SIMPLESAMLPHP_SP_ENTITY_ID": "entity-id", | ||||||
|  | |||||||
| @ -34,8 +34,8 @@ class OutpostDockerTests(DockerTestCase, ChannelsLiveServerTestCase): | |||||||
|             privileged=True, |             privileged=True, | ||||||
|             healthcheck=Healthcheck( |             healthcheck=Healthcheck( | ||||||
|                 test=["CMD", "docker", "info"], |                 test=["CMD", "docker", "info"], | ||||||
|                 interval=5 * 100 * 1000000, |                 interval=5 * 1_000 * 1_000_000, | ||||||
|                 start_period=5 * 100 * 1000000, |                 start_period=5 * 1_000 * 1_000_000, | ||||||
|             ), |             ), | ||||||
|             environment={"DOCKER_TLS_CERTDIR": "/ssl"}, |             environment={"DOCKER_TLS_CERTDIR": "/ssl"}, | ||||||
|             volumes={ |             volumes={ | ||||||
|  | |||||||
| @ -34,8 +34,8 @@ class TestProxyDocker(DockerTestCase, ChannelsLiveServerTestCase): | |||||||
|             privileged=True, |             privileged=True, | ||||||
|             healthcheck=Healthcheck( |             healthcheck=Healthcheck( | ||||||
|                 test=["CMD", "docker", "info"], |                 test=["CMD", "docker", "info"], | ||||||
|                 interval=5 * 100 * 1000000, |                 interval=5 * 1_000 * 1_000_000, | ||||||
|                 start_period=5 * 100 * 1000000, |                 start_period=5 * 1_000 * 1_000_000, | ||||||
|             ), |             ), | ||||||
|             environment={"DOCKER_TLS_CERTDIR": "/ssl"}, |             environment={"DOCKER_TLS_CERTDIR": "/ssl"}, | ||||||
|             volumes={ |             volumes={ | ||||||
|  | |||||||
| @ -27,5 +27,8 @@ | |||||||
|         "precommit": "run-s lint:precommit lint:spelling prettier", |         "precommit": "run-s lint:precommit lint:spelling prettier", | ||||||
|         "prettier-check": "prettier --check .", |         "prettier-check": "prettier --check .", | ||||||
|         "prettier": "prettier --write ." |         "prettier": "prettier --write ." | ||||||
|  |     }, | ||||||
|  |     "engines": { | ||||||
|  |         "node": ">=20" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -17,7 +17,7 @@ | |||||||
|                 "@codemirror/theme-one-dark": "^6.1.2", |                 "@codemirror/theme-one-dark": "^6.1.2", | ||||||
|                 "@formatjs/intl-listformat": "^7.5.0", |                 "@formatjs/intl-listformat": "^7.5.0", | ||||||
|                 "@fortawesome/fontawesome-free": "^6.4.2", |                 "@fortawesome/fontawesome-free": "^6.4.2", | ||||||
|                 "@goauthentik/api": "^2023.10.0-1698336292", |                 "@goauthentik/api": "^2023.10.1-1698348102", | ||||||
|                 "@lit-labs/context": "^0.4.1", |                 "@lit-labs/context": "^0.4.1", | ||||||
|                 "@lit-labs/task": "^3.1.0", |                 "@lit-labs/task": "^3.1.0", | ||||||
|                 "@lit/localize": "^0.11.4", |                 "@lit/localize": "^0.11.4", | ||||||
| @ -100,6 +100,9 @@ | |||||||
|                 "typescript": "^5.2.2", |                 "typescript": "^5.2.2", | ||||||
|                 "vite-tsconfig-paths": "^4.2.1" |                 "vite-tsconfig-paths": "^4.2.1" | ||||||
|             }, |             }, | ||||||
|  |             "engines": { | ||||||
|  |                 "node": ">=20" | ||||||
|  |             }, | ||||||
|             "optionalDependencies": { |             "optionalDependencies": { | ||||||
|                 "@esbuild/darwin-arm64": "^0.19.5", |                 "@esbuild/darwin-arm64": "^0.19.5", | ||||||
|                 "@esbuild/linux-amd64": "^0.18.11", |                 "@esbuild/linux-amd64": "^0.18.11", | ||||||
| @ -2883,9 +2886,9 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/@goauthentik/api": { |         "node_modules/@goauthentik/api": { | ||||||
|             "version": "2023.10.0-1698336292", |             "version": "2023.10.1-1698348102", | ||||||
|             "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.10.0-1698336292.tgz", |             "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.10.1-1698348102.tgz", | ||||||
|             "integrity": "sha512-CZd9d6b0pFR/rbD91+enULWN3JhivHVgjDIHE927MEv2wpqtl5koby7VaMNNM9WL8LQdgzCdJt4nstjnhm21tw==" |             "integrity": "sha512-Melx4hoHOLbgAOHREGzx83uN5BKvgql4qIUloxh/abvNeGLlfKL49caiU8++ANUaERr1vb8X2tHFwiwxtqXKeQ==" | ||||||
|         }, |         }, | ||||||
|         "node_modules/@hcaptcha/types": { |         "node_modules/@hcaptcha/types": { | ||||||
|             "version": "1.0.3", |             "version": "1.0.3", | ||||||
|  | |||||||
| @ -38,7 +38,7 @@ | |||||||
|         "@codemirror/theme-one-dark": "^6.1.2", |         "@codemirror/theme-one-dark": "^6.1.2", | ||||||
|         "@formatjs/intl-listformat": "^7.5.0", |         "@formatjs/intl-listformat": "^7.5.0", | ||||||
|         "@fortawesome/fontawesome-free": "^6.4.2", |         "@fortawesome/fontawesome-free": "^6.4.2", | ||||||
|         "@goauthentik/api": "^2023.10.0-1698336292", |         "@goauthentik/api": "^2023.10.1-1698348102", | ||||||
|         "@lit-labs/context": "^0.4.1", |         "@lit-labs/context": "^0.4.1", | ||||||
|         "@lit-labs/task": "^3.1.0", |         "@lit-labs/task": "^3.1.0", | ||||||
|         "@lit/localize": "^0.11.4", |         "@lit/localize": "^0.11.4", | ||||||
| @ -125,5 +125,8 @@ | |||||||
|         "@esbuild/darwin-arm64": "^0.19.5", |         "@esbuild/darwin-arm64": "^0.19.5", | ||||||
|         "@esbuild/linux-amd64": "^0.18.11", |         "@esbuild/linux-amd64": "^0.18.11", | ||||||
|         "@esbuild/linux-arm64": "^0.19.5" |         "@esbuild/linux-arm64": "^0.19.5" | ||||||
|  |     }, | ||||||
|  |     "engines": { | ||||||
|  |         "node": ">=20" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -116,7 +116,7 @@ export class ApplicationForm extends ModelForm<Application, string> { | |||||||
|         return app; |         return app; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     handleConfirmBackchannelProviders({ items }: { items: Provider[] }) { |     handleConfirmBackchannelProviders(items: Provider[]) { | ||||||
|         this.backchannelProviders = items; |         this.backchannelProviders = items; | ||||||
|         this.requestUpdate(); |         this.requestUpdate(); | ||||||
|         return Promise.resolve(); |         return Promise.resolve(); | ||||||
|  | |||||||
| @ -63,7 +63,7 @@ export class AkBackchannelProvidersInput extends AKElement { | |||||||
|         return html` |         return html` | ||||||
|             <ak-form-element-horizontal label=${this.label} name=${name}> |             <ak-form-element-horizontal label=${this.label} name=${name}> | ||||||
|                 <div class="pf-c-input-group"> |                 <div class="pf-c-input-group"> | ||||||
|                     <ak-provider-select-table ?backchannelOnly=${true} .confirm=${confirm}> |                     <ak-provider-select-table ?backchannelOnly=${true} .confirm=${this.confirm}> | ||||||
|                         <button slot="trigger" class="pf-c-button pf-m-control" type="button"> |                         <button slot="trigger" class="pf-c-button pf-m-control" type="button"> | ||||||
|                             ${this.tooltip ? this.tooltip : nothing} |                             ${this.tooltip ? this.tooltip : nothing} | ||||||
|                             <i class="fas fa-plus" aria-hidden="true"></i> |                             <i class="fas fa-plus" aria-hidden="true"></i> | ||||||
|  | |||||||
| @ -114,8 +114,8 @@ export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel { | |||||||
|                         label=${msg("Client type")} |                         label=${msg("Client type")} | ||||||
|                         .value=${provider?.clientType} |                         .value=${provider?.clientType} | ||||||
|                         required |                         required | ||||||
|                         @change=${(ev: CustomEvent<ClientTypeEnum>) => { |                         @change=${(ev: CustomEvent<{ value: ClientTypeEnum }>) => { | ||||||
|                             this.showClientSecret = ev.detail !== ClientTypeEnum.Public; |                             this.showClientSecret = ev.detail.value !== ClientTypeEnum.Public; | ||||||
|                         }} |                         }} | ||||||
|                         .options=${clientTypeOptions} |                         .options=${clientTypeOptions} | ||||||
|                     > |                     > | ||||||
|  | |||||||
| @ -78,8 +78,8 @@ export class TransportForm extends ModelForm<NotificationTransport, string> { | |||||||
|             </ak-form-element-horizontal> |             </ak-form-element-horizontal> | ||||||
|             <ak-form-element-horizontal label=${msg("Mode")} ?required=${true} name="mode"> |             <ak-form-element-horizontal label=${msg("Mode")} ?required=${true} name="mode"> | ||||||
|                 <ak-radio |                 <ak-radio | ||||||
|                     @change=${(ev: CustomEvent<NotificationTransportModeEnum>) => { |                     @change=${(ev: CustomEvent<{ value: NotificationTransportModeEnum }>) => { | ||||||
|                         this.onModeChange(ev.detail); |                         this.onModeChange(ev.detail.value); | ||||||
|                     }} |                     }} | ||||||
|                     .options=${[ |                     .options=${[ | ||||||
|                         { |                         { | ||||||
|  | |||||||
| @ -210,8 +210,8 @@ export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> { | |||||||
|                         label=${msg("Client type")} |                         label=${msg("Client type")} | ||||||
|                         .value=${provider?.clientType} |                         .value=${provider?.clientType} | ||||||
|                         required |                         required | ||||||
|                         @change=${(ev: CustomEvent<ClientTypeEnum>) => { |                         @change=${(ev: CustomEvent<{ value: ClientTypeEnum }>) => { | ||||||
|                             this.showClientSecret = ev.detail !== ClientTypeEnum.Public; |                             this.showClientSecret = ev.detail.value !== ClientTypeEnum.Public; | ||||||
|                         }} |                         }} | ||||||
|                         .options=${clientTypeOptions} |                         .options=${clientTypeOptions} | ||||||
|                     > |                     > | ||||||
| @ -334,13 +334,14 @@ export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> { | |||||||
|                         )} |                         )} | ||||||
|                     > |                     > | ||||||
|                     </ak-radio-input> |                     </ak-radio-input> | ||||||
|                     <ak-switch-input name="includeClaimsInIdToken"> |                     <ak-switch-input | ||||||
|  |                         name="includeClaimsInIdToken" | ||||||
|                         label=${msg("Include claims in id_token")} |                         label=${msg("Include claims in id_token")} | ||||||
|                         ?checked=${first(provider?.includeClaimsInIdToken, true)} |                         ?checked=${first(provider?.includeClaimsInIdToken, true)} | ||||||
|                         help=${msg( |                         help=${msg( | ||||||
|                             "Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint.", |                             "Include User claims from scopes in the id_token, for applications that don't access the userinfo endpoint.", | ||||||
|                         )}></ak-switch-input |                         )} | ||||||
|                     > |                     ></ak-switch-input> | ||||||
|                     <ak-radio-input |                     <ak-radio-input | ||||||
|                         name="issuerMode" |                         name="issuerMode" | ||||||
|                         label=${msg("Issuer mode")} |                         label=${msg("Issuer mode")} | ||||||
|  | |||||||
| @ -42,15 +42,13 @@ export class RoleForm extends ModelForm<Role, string> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     renderForm(): TemplateResult { |     renderForm(): TemplateResult { | ||||||
|         return html`<form class="pf-c-form pf-m-horizontal"> |         return html`<ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name"> | ||||||
|             <ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name"> |  | ||||||
|             <input |             <input | ||||||
|                 type="text" |                 type="text" | ||||||
|                 value="${ifDefined(this.instance?.name)}" |                 value="${ifDefined(this.instance?.name)}" | ||||||
|                 class="pf-c-form-control" |                 class="pf-c-form-control" | ||||||
|                 required |                 required | ||||||
|             /> |             /> | ||||||
|             </ak-form-element-horizontal> |         </ak-form-element-horizontal>`; | ||||||
|         </form>`; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -184,9 +184,9 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> { | |||||||
|                           </p> |                           </p> | ||||||
|                       </ak-form-element-horizontal> ` |                       </ak-form-element-horizontal> ` | ||||||
|                     : html``} |                     : html``} | ||||||
|                 ${this.providerType.slug === ProviderTypeEnum.Openidconnect |                 ${this.providerType.slug === ProviderTypeEnum.Openidconnect || | ||||||
|                     ? html` |                 this.providerType.oidcWellKnownUrl !== "" | ||||||
|                           <ak-form-element-horizontal |                     ? html`<ak-form-element-horizontal | ||||||
|                           label=${msg("OIDC Well-known URL")} |                           label=${msg("OIDC Well-known URL")} | ||||||
|                           name="oidcWellKnownUrl" |                           name="oidcWellKnownUrl" | ||||||
|                       > |                       > | ||||||
| @ -204,8 +204,11 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> { | |||||||
|                                   "OIDC well-known configuration URL. Can be used to automatically configure the URLs above.", |                                   "OIDC well-known configuration URL. Can be used to automatically configure the URLs above.", | ||||||
|                               )} |                               )} | ||||||
|                           </p> |                           </p> | ||||||
|                           </ak-form-element-horizontal> |                       </ak-form-element-horizontal>` | ||||||
|                           <ak-form-element-horizontal |                     : html``} | ||||||
|  |                 ${this.providerType.slug === ProviderTypeEnum.Openidconnect || | ||||||
|  |                 this.providerType.oidcJwksUrl !== "" | ||||||
|  |                     ? html`<ak-form-element-horizontal | ||||||
|                               label=${msg("OIDC JWKS URL")} |                               label=${msg("OIDC JWKS URL")} | ||||||
|                               name="oidcJwksUrl" |                               name="oidcJwksUrl" | ||||||
|                           > |                           > | ||||||
| @ -224,7 +227,6 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> { | |||||||
|                                   )} |                                   )} | ||||||
|                               </p> |                               </p> | ||||||
|                           </ak-form-element-horizontal> |                           </ak-form-element-horizontal> | ||||||
|  |  | ||||||
|                           <ak-form-element-horizontal label=${msg("OIDC JWKS")} name="oidcJwks"> |                           <ak-form-element-horizontal label=${msg("OIDC JWKS")} name="oidcJwks"> | ||||||
|                               <ak-codemirror |                               <ak-codemirror | ||||||
|                                   mode=${CodeMirrorMode.JavaScript} |                                   mode=${CodeMirrorMode.JavaScript} | ||||||
| @ -232,8 +234,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> { | |||||||
|                               > |                               > | ||||||
|                               </ak-codemirror> |                               </ak-codemirror> | ||||||
|                               <p class="pf-c-form__helper-text">${msg("Raw JWKS data.")}</p> |                               <p class="pf-c-form__helper-text">${msg("Raw JWKS data.")}</p> | ||||||
|                           </ak-form-element-horizontal> |                           </ak-form-element-horizontal>` | ||||||
|                       ` |  | ||||||
|                     : html``} |                     : html``} | ||||||
|             </div> |             </div> | ||||||
|         </ak-form-group>`; |         </ak-form-group>`; | ||||||
| @ -386,6 +387,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> { | |||||||
|                             class="pf-c-form-control" |                             class="pf-c-form-control" | ||||||
|                             required |                             required | ||||||
|                         /> |                         /> | ||||||
|  |                         <p class="pf-c-form__helper-text">${msg("Also known as Client ID.")}</p> | ||||||
|                     </ak-form-element-horizontal> |                     </ak-form-element-horizontal> | ||||||
|                     <ak-form-element-horizontal |                     <ak-form-element-horizontal | ||||||
|                         label=${msg("Consumer secret")} |                         label=${msg("Consumer secret")} | ||||||
| @ -394,6 +396,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> { | |||||||
|                         name="consumerSecret" |                         name="consumerSecret" | ||||||
|                     > |                     > | ||||||
|                         <textarea class="pf-c-form-control"></textarea> |                         <textarea class="pf-c-form-control"></textarea> | ||||||
|  |                         <p class="pf-c-form__helper-text">${msg("Also known as Client Secret.")}</p> | ||||||
|                     </ak-form-element-horizontal> |                     </ak-form-element-horizontal> | ||||||
|                     <ak-form-element-horizontal label=${msg("Scopes")} name="additionalScopes"> |                     <ak-form-element-horizontal label=${msg("Scopes")} name="additionalScopes"> | ||||||
|                         <input |                         <input | ||||||
|  | |||||||
| @ -15,6 +15,8 @@ export class UserDeviceTable extends Table<Device> { | |||||||
|     @property({ type: Number }) |     @property({ type: Number }) | ||||||
|     userId?: number; |     userId?: number; | ||||||
|  |  | ||||||
|  |     checkbox = true; | ||||||
|  |  | ||||||
|     async apiEndpoint(): Promise<PaginatedResponse<Device>> { |     async apiEndpoint(): Promise<PaginatedResponse<Device>> { | ||||||
|         return new AuthenticatorsApi(DEFAULT_CONFIG) |         return new AuthenticatorsApi(DEFAULT_CONFIG) | ||||||
|             .authenticatorsAdminAllList({ |             .authenticatorsAdminAllList({ | ||||||
| @ -64,6 +66,21 @@ export class UserDeviceTable extends Table<Device> { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     renderToolbarSelected(): TemplateResult { | ||||||
|  |         const disabled = this.selectedElements.length < 1; | ||||||
|  |         return html`<ak-forms-delete-bulk | ||||||
|  |             objectLabel=${msg("Device(s)")} | ||||||
|  |             .objects=${this.selectedElements} | ||||||
|  |             .delete=${(item: Device) => { | ||||||
|  |                 return this.deleteWrapper(item); | ||||||
|  |             }} | ||||||
|  |         > | ||||||
|  |             <button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger"> | ||||||
|  |                 ${msg("Delete")} | ||||||
|  |             </button> | ||||||
|  |         </ak-forms-delete-bulk>`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     renderToolbar(): TemplateResult { |     renderToolbar(): TemplateResult { | ||||||
|         return html` <ak-spinner-button |         return html` <ak-spinner-button | ||||||
|             .callAction=${() => { |             .callAction=${() => { | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success"; | |||||||
| export const ERROR_CLASS = "pf-m-danger"; | export const ERROR_CLASS = "pf-m-danger"; | ||||||
| export const PROGRESS_CLASS = "pf-m-in-progress"; | export const PROGRESS_CLASS = "pf-m-in-progress"; | ||||||
| export const CURRENT_CLASS = "pf-m-current"; | export const CURRENT_CLASS = "pf-m-current"; | ||||||
| export const VERSION = "2023.10.1"; | export const VERSION = "2023.10.5"; | ||||||
| export const TITLE_DEFAULT = "authentik"; | export const TITLE_DEFAULT = "authentik"; | ||||||
| export const ROUTE_SEPARATOR = ";"; | export const ROUTE_SEPARATOR = ";"; | ||||||
|  |  | ||||||
|  | |||||||
| @ -310,6 +310,12 @@ select[multiple] option:checked { | |||||||
|     --pf-c-wizard__nav-link--before--BackgroundColor: transparent; |     --pf-c-wizard__nav-link--before--BackgroundColor: transparent; | ||||||
| } | } | ||||||
| /* tree view */ | /* tree view */ | ||||||
|  | .pf-c-tree-view__node { | ||||||
|  |     --pf-c-tree-view__node--Color: var(--ak-dark-foreground); | ||||||
|  | } | ||||||
|  | .pf-c-tree-view__node-toggle { | ||||||
|  |     --pf-c-tree-view__node-toggle--Color: var(--ak-dark-foreground); | ||||||
|  | } | ||||||
| .pf-c-tree-view__node:focus { | .pf-c-tree-view__node:focus { | ||||||
|     --pf-c-tree-view__node--focus--BackgroundColor: var(--ak-dark-background-light-ish); |     --pf-c-tree-view__node--focus--BackgroundColor: var(--ak-dark-background-light-ish); | ||||||
| } | } | ||||||
|  | |||||||
| @ -79,6 +79,7 @@ export class PageHeader extends AKElement { | |||||||
|                 } |                 } | ||||||
|                 .pf-c-page__main-section { |                 .pf-c-page__main-section { | ||||||
|                     flex-grow: 1; |                     flex-grow: 1; | ||||||
|  |                     flex-shrink: 1; | ||||||
|                     display: flex; |                     display: flex; | ||||||
|                     flex-direction: column; |                     flex-direction: column; | ||||||
|                     justify-content: center; |                     justify-content: center; | ||||||
|  | |||||||
| @ -80,11 +80,12 @@ export class IdentificationStage extends BaseStage< | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     createHelperForm(): void { |     createHelperForm(): void { | ||||||
|  |         const compatMode = "ShadyDOM" in window; | ||||||
|         this.form = document.createElement("form"); |         this.form = document.createElement("form"); | ||||||
|         document.documentElement.appendChild(this.form); |         document.documentElement.appendChild(this.form); | ||||||
|         // Only add the additional username input if we're in a shadow dom |         // Only add the additional username input if we're in a shadow dom | ||||||
|         // otherwise it just confuses browsers |         // otherwise it just confuses browsers | ||||||
|         if (!("ShadyDOM" in window)) { |         if (!compatMode) { | ||||||
|             // This is a workaround for the fact that we're in a shadow dom |             // This is a workaround for the fact that we're in a shadow dom | ||||||
|             // adapted from https://github.com/home-assistant/frontend/issues/3133 |             // adapted from https://github.com/home-assistant/frontend/issues/3133 | ||||||
|             const username = document.createElement("input"); |             const username = document.createElement("input"); | ||||||
| @ -104,6 +105,8 @@ export class IdentificationStage extends BaseStage< | |||||||
|             }; |             }; | ||||||
|             this.form.appendChild(username); |             this.form.appendChild(username); | ||||||
|         } |         } | ||||||
|  |         // Only add the password field when we don't already show a password field | ||||||
|  |         if (!compatMode && !this.challenge.passwordFields) { | ||||||
|             const password = document.createElement("input"); |             const password = document.createElement("input"); | ||||||
|             password.setAttribute("type", "password"); |             password.setAttribute("type", "password"); | ||||||
|             password.setAttribute("name", "password"); |             password.setAttribute("name", "password"); | ||||||
| @ -128,6 +131,7 @@ export class IdentificationStage extends BaseStage< | |||||||
|                     }); |                     }); | ||||||
|             }; |             }; | ||||||
|             this.form.appendChild(password); |             this.form.appendChild(password); | ||||||
|  |         } | ||||||
|         const totp = document.createElement("input"); |         const totp = document.createElement("input"); | ||||||
|         totp.setAttribute("type", "text"); |         totp.setAttribute("type", "text"); | ||||||
|         totp.setAttribute("name", "code"); |         totp.setAttribute("name", "code"); | ||||||
|  | |||||||
| @ -96,7 +96,7 @@ export class LibraryApplication extends AKElement { | |||||||
|             this.application.metaPublisher !== "" || |             this.application.metaPublisher !== "" || | ||||||
|             this.application.metaDescription !== ""; |             this.application.metaDescription !== ""; | ||||||
|  |  | ||||||
|         const classes = { "pf-m-selectable pf-m-selected": this.selected }; |         const classes = { "pf-m-selectable": this.selected, "pf-m-selected": this.selected }; | ||||||
|         const styles = this.background ? { background: this.background } : {}; |         const styles = this.background ? { background: this.background } : {}; | ||||||
|  |  | ||||||
|         return html` <div |         return html` <div | ||||||
|  | |||||||
| @ -38,7 +38,9 @@ export class LibraryPageApplicationList extends AKElement { | |||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     @property({ attribute: false }) |     @property({ attribute: false }) | ||||||
|     apps: Application[] = []; |     set apps(value: Application[]) { | ||||||
|  |         this.fuse.setCollection(value); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @property() |     @property() | ||||||
|     query = getURLParam<string | undefined>("search", undefined); |     query = getURLParam<string | undefined>("search", undefined); | ||||||
| @ -63,7 +65,7 @@ export class LibraryPageApplicationList extends AKElement { | |||||||
|             shouldSort: true, |             shouldSort: true, | ||||||
|             ignoreFieldNorm: true, |             ignoreFieldNorm: true, | ||||||
|             useExtendedSearch: true, |             useExtendedSearch: true, | ||||||
|             threshold: 0.5, |             threshold: 0.3, | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -77,7 +79,6 @@ export class LibraryPageApplicationList extends AKElement { | |||||||
|  |  | ||||||
|     connectedCallback() { |     connectedCallback() { | ||||||
|         super.connectedCallback(); |         super.connectedCallback(); | ||||||
|         this.fuse.setCollection(this.apps); |  | ||||||
|         if (!this.query) { |         if (!this.query) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -82,9 +82,9 @@ export class UserInterface extends Interface { | |||||||
|                 :host([theme="dark"]) .pf-c-page__header { |                 :host([theme="dark"]) .pf-c-page__header { | ||||||
|                     color: var(--ak-dark-foreground) !important; |                     color: var(--ak-dark-foreground) !important; | ||||||
|                 } |                 } | ||||||
|                 .pf-c-page__header-tools-item .fas, |                 :host([theme="light"]) .pf-c-page__header-tools-item .fas, | ||||||
|                 .pf-c-notification-badge__count, |                 :host([theme="light"]) .pf-c-notification-badge__count, | ||||||
|                 .pf-c-page__header-tools-group .pf-c-button { |                 :host([theme="light"]) .pf-c-page__header-tools-group .pf-c-button { | ||||||
|                     color: var(--ak-global--Color--100) !important; |                     color: var(--ak-global--Color--100) !important; | ||||||
|                 } |                 } | ||||||
|                 .pf-c-page { |                 .pf-c-page { | ||||||
| @ -183,7 +183,7 @@ export class UserInterface extends Interface { | |||||||
|             <ak-enterprise-status interface="user"></ak-enterprise-status> |             <ak-enterprise-status interface="user"></ak-enterprise-status> | ||||||
|             <div class="pf-c-page"> |             <div class="pf-c-page"> | ||||||
|                 <div class="background-wrapper" style="${this.uiConfig.theme.background}"> |                 <div class="background-wrapper" style="${this.uiConfig.theme.background}"> | ||||||
|                     ${this.uiConfig.theme.background === "" |                     ${(this.uiConfig.theme.background || "") === "" | ||||||
|                         ? html`<div class="background-default-slant"></div>` |                         ? html`<div class="background-default-slant"></div>` | ||||||
|                         : html``} |                         : html``} | ||||||
|                 </div> |                 </div> | ||||||
|  | |||||||
| @ -5798,12 +5798,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| <trans-unit id="s945a6b94361ee45b"> | <trans-unit id="s945a6b94361ee45b"> | ||||||
|   <source>For transparent reverse proxies with required authentication</source> |   <source>For transparent reverse proxies with required authentication</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sadf073913458acbd"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="se770e9498b3bacf6"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s40830ec037f34626"> | <trans-unit id="s40830ec037f34626"> | ||||||
|   <source>Configure SAML provider manually</source> |   <source>Configure SAML provider manually</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| @ -6031,6 +6025,24 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="s0924f51b028233a3"> | <trans-unit id="s0924f51b028233a3"> | ||||||
|   <source><No name set></source> |   <source><No name set></source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sdc9a6ad1af30572c"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sfc31264ef7ff86ef"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sc615309d10a9228c"> | ||||||
|  |   <source>RBAC is in preview.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="s32babfed740fd3c1"> | ||||||
|  |   <source>User type used for newly created users.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sb35c08e3a541188f"> | ||||||
|  |   <source>Also known as Client ID.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sd46fd9b647cfea10"> | ||||||
|  |   <source>Also known as Client Secret.</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -6079,12 +6079,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| <trans-unit id="s945a6b94361ee45b"> | <trans-unit id="s945a6b94361ee45b"> | ||||||
|   <source>For transparent reverse proxies with required authentication</source> |   <source>For transparent reverse proxies with required authentication</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sadf073913458acbd"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="se770e9498b3bacf6"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s40830ec037f34626"> | <trans-unit id="s40830ec037f34626"> | ||||||
|   <source>Configure SAML provider manually</source> |   <source>Configure SAML provider manually</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| @ -6312,6 +6306,24 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="s0924f51b028233a3"> | <trans-unit id="s0924f51b028233a3"> | ||||||
|   <source><No name set></source> |   <source><No name set></source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sdc9a6ad1af30572c"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sfc31264ef7ff86ef"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sc615309d10a9228c"> | ||||||
|  |   <source>RBAC is in preview.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="s32babfed740fd3c1"> | ||||||
|  |   <source>User type used for newly created users.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sb35c08e3a541188f"> | ||||||
|  |   <source>Also known as Client ID.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sd46fd9b647cfea10"> | ||||||
|  |   <source>Also known as Client Secret.</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -5713,12 +5713,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| <trans-unit id="s945a6b94361ee45b"> | <trans-unit id="s945a6b94361ee45b"> | ||||||
|   <source>For transparent reverse proxies with required authentication</source> |   <source>For transparent reverse proxies with required authentication</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sadf073913458acbd"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="se770e9498b3bacf6"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s40830ec037f34626"> | <trans-unit id="s40830ec037f34626"> | ||||||
|   <source>Configure SAML provider manually</source> |   <source>Configure SAML provider manually</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| @ -5946,6 +5940,24 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="s0924f51b028233a3"> | <trans-unit id="s0924f51b028233a3"> | ||||||
|   <source><No name set></source> |   <source><No name set></source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sdc9a6ad1af30572c"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sfc31264ef7ff86ef"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sc615309d10a9228c"> | ||||||
|  |   <source>RBAC is in preview.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="s32babfed740fd3c1"> | ||||||
|  |   <source>User type used for newly created users.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sb35c08e3a541188f"> | ||||||
|  |   <source>Also known as Client ID.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sd46fd9b647cfea10"> | ||||||
|  |   <source>Also known as Client Secret.</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
							
								
								
									
										146
									
								
								web/xliff/fr.xlf
									
									
									
									
									
								
							
							
						
						
									
										146
									
								
								web/xliff/fr.xlf
									
									
									
									
									
								
							| @ -613,9 +613,9 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target> | |||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="saa0e2675da69651b"> |       <trans-unit id="saa0e2675da69651b"> | ||||||
|         <source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source> |         <source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source> | ||||||
|         <target>L'URL "  |         <target>L'URL " | ||||||
|         <x id="0" equiv-text="${this.url}"/>" n'a pas été trouvée.</target> |         <x id="0" equiv-text="${this.url}"/>" n'a pas été trouvée.</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s58cd9c2fe836d9c6"> |       <trans-unit id="s58cd9c2fe836d9c6"> | ||||||
| @ -1057,8 +1057,8 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target> | |||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sa8384c9c26731f83"> |       <trans-unit id="sa8384c9c26731f83"> | ||||||
|         <source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source> |         <source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source> | ||||||
|         <target>Pour permettre n'importe quelle URI de redirection, définissez cette valeur sur ".*". Soyez conscient des possibles implications de sécurité que cela peut avoir.</target> |         <target>Pour permettre n'importe quelle URI de redirection, définissez cette valeur sur ".*". Soyez conscient des possibles implications de sécurité que cela peut avoir.</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s55787f4dfcdce52b"> |       <trans-unit id="s55787f4dfcdce52b"> | ||||||
| @ -1630,7 +1630,7 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target> | |||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s33ed903c210a6209"> |       <trans-unit id="s33ed903c210a6209"> | ||||||
|         <source>Token to authenticate with. Currently only bearer authentication is supported.</source> |         <source>Token to authenticate with. Currently only bearer authentication is supported.</source> | ||||||
|         <target>Jeton d'authentification à utiliser. Actuellement, seule l'authentification "bearer authentication" est prise en charge.</target> |         <target>Jeton d'authentification à utiliser. Actuellement, seule l'authentification "bearer authentication" est prise en charge.</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sfc8bb104e2c05af8"> |       <trans-unit id="sfc8bb104e2c05af8"> | ||||||
| @ -1798,8 +1798,8 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target> | |||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sa90b7809586c35ce"> |       <trans-unit id="sa90b7809586c35ce"> | ||||||
|         <source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source> |         <source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source> | ||||||
|         <target>Entrez une URL complète, un chemin relatif ou utilisez 'fa://fa-test' pour utiliser l'icône Font Awesome "fa-test".</target> |         <target>Entrez une URL complète, un chemin relatif ou utilisez 'fa://fa-test' pour utiliser l'icône Font Awesome "fa-test".</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s0410779cb47de312"> |       <trans-unit id="s0410779cb47de312"> | ||||||
| @ -2561,31 +2561,6 @@ Il y a <x id="0" equiv-text="${ago}"/> jour(s)</target> | |||||||
|         <source>If the password's score is less than or equal this value, the policy will fail.</source> |         <source>If the password's score is less than or equal this value, the policy will fail.</source> | ||||||
|         <target>Si le score du mot de passe est inférieur ou égal à cette valeur, la politique échoue.</target> |         <target>Si le score du mot de passe est inférieur ou égal à cette valeur, la politique échoue.</target> | ||||||
|  |  | ||||||
|       </trans-unit> |  | ||||||
|       <trans-unit id="s1bfe7505059d164f"> |  | ||||||
|         <source>0: Too guessable: risky password. (guesses < 10^3)</source> |  | ||||||
|         <target>0: Trop prévisible: mot de passe risqué. (essais < 10^3)</target> |  | ||||||
|          |  | ||||||
|       </trans-unit> |  | ||||||
|       <trans-unit id="s423d1f2477998d0b"> |  | ||||||
|         <source>1: Very guessable: protection from throttled online attacks. (guesses < 10^6)</source> |  | ||||||
|         <target>1: Très prévisible: protection contre les attaques en ligne limitées. (essais < 10^6)</target> |  | ||||||
|          |  | ||||||
|       </trans-unit> |  | ||||||
|       <trans-unit id="s33849cc046eb901d"> |  | ||||||
|         <source>2: Somewhat guessable: protection from unthrottled online attacks. (guesses < 10^8)</source> |  | ||||||
|         <target>2: Quelque peu prévisible: protection contre les attaques en ligne non limitées. (essais < 10^8)</target> |  | ||||||
|          |  | ||||||
|       </trans-unit> |  | ||||||
|       <trans-unit id="s578dcce295718e1b"> |  | ||||||
|         <source>3: Safely unguessable: moderate protection from offline slow-hash scenario. (guesses < 10^10)</source> |  | ||||||
|         <target>3: Sûrement imprévisible: protection modérée contre les attaques de hash-lent hors ligne. (essais < 10^10)</target> |  | ||||||
|          |  | ||||||
|       </trans-unit> |  | ||||||
|       <trans-unit id="s7a46de49f4eba5d7"> |  | ||||||
|         <source>4: Very unguessable: strong protection from offline slow-hash scenario. (guesses >= 10^10)</source> |  | ||||||
|         <target>4: Très imprévisible: forte protection control les attaques de hash-lent hors ligne. (essais >= 10^10)</target> |  | ||||||
|          |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sd6cd7ce2310a73a4"> |       <trans-unit id="sd6cd7ce2310a73a4"> | ||||||
|         <source>Checks the value from the policy request against several rules, mostly used to ensure password strength.</source> |         <source>Checks the value from the policy request against several rules, mostly used to ensure password strength.</source> | ||||||
| @ -2922,7 +2897,7 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s33683c3b1dbaf264"> |       <trans-unit id="s33683c3b1dbaf264"> | ||||||
|         <source>To use SSL instead, use 'ldaps://' and disable this option.</source> |         <source>To use SSL instead, use 'ldaps://' and disable this option.</source> | ||||||
|         <target>Pour utiliser SSL à la base, utilisez "ldaps://" et désactviez cette option.</target> |         <target>Pour utiliser SSL à la base, utilisez "ldaps://" et désactviez cette option.</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s2221fef80f4753a2"> |       <trans-unit id="s2221fef80f4753a2"> | ||||||
| @ -3011,8 +2986,8 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s76768bebabb7d543"> |       <trans-unit id="s76768bebabb7d543"> | ||||||
|         <source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source> |         <source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source> | ||||||
|         <target>Champ qui contient les membres d'un groupe. Si vous utilisez le champ "memberUid", la valeur est censée contenir un nom distinctif relatif, par exemple 'memberUid=un-utilisateur' au lieu de 'memberUid=cn=un-utilisateur,ou=groups,...'</target> |         <target>Champ qui contient les membres d'un groupe. Si vous utilisez le champ "memberUid", la valeur est censée contenir un nom distinctif relatif, par exemple 'memberUid=un-utilisateur' au lieu de 'memberUid=cn=un-utilisateur,ou=groups,...'</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s026555347e589f0e"> |       <trans-unit id="s026555347e589f0e"> | ||||||
| @ -3307,7 +3282,7 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s3198c384c2f68b08"> |       <trans-unit id="s3198c384c2f68b08"> | ||||||
|         <source>Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually.</source> |         <source>Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually.</source> | ||||||
|         <target>Moment où les utilisateurs temporaires doivent être supprimés. Cela ne s'applique que si votre IDP utilise le format NameID "transient" et que l'utilisateur ne se déconnecte pas manuellement.</target> |         <target>Moment où les utilisateurs temporaires doivent être supprimés. Cela ne s'applique que si votre IDP utilise le format NameID "transient" et que l'utilisateur ne se déconnecte pas manuellement.</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sb32e9c1faa0b8673"> |       <trans-unit id="sb32e9c1faa0b8673"> | ||||||
| @ -3475,7 +3450,7 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s9f8aac89fe318acc"> |       <trans-unit id="s9f8aac89fe318acc"> | ||||||
|         <source>Optionally set the 'FriendlyName' value of the Assertion attribute.</source> |         <source>Optionally set the 'FriendlyName' value of the Assertion attribute.</source> | ||||||
|         <target>Indiquer la valeur "FriendlyName" de l'attribut d'assertion (optionnel)</target> |         <target>Indiquer la valeur "FriendlyName" de l'attribut d'assertion (optionnel)</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s851c108679653d2a"> |       <trans-unit id="s851c108679653d2a"> | ||||||
| @ -3804,8 +3779,8 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s7b1fba26d245cb1c"> |       <trans-unit id="s7b1fba26d245cb1c"> | ||||||
|         <source>When using an external logging solution for archiving, this can be set to "minutes=5".</source> |         <source>When using an external logging solution for archiving, this can be set to "minutes=5".</source> | ||||||
|         <target>En cas d'utilisation d'une solution de journalisation externe pour l'archivage, cette valeur peut être fixée à "minutes=5".</target> |         <target>En cas d'utilisation d'une solution de journalisation externe pour l'archivage, cette valeur peut être fixée à "minutes=5".</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s44536d20bb5c8257"> |       <trans-unit id="s44536d20bb5c8257"> | ||||||
| @ -3814,8 +3789,8 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s3bb51cabb02b997e"> |       <trans-unit id="s3bb51cabb02b997e"> | ||||||
|         <source>Format: "weeks=3;days=2;hours=3,seconds=2".</source> |         <source>Format: "weeks=3;days=2;hours=3,seconds=2".</source> | ||||||
|         <target>Format : "weeks=3;days=2;hours=3,seconds=2".</target> |         <target>Format : "weeks=3;days=2;hours=3,seconds=2".</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s04bfd02201db5ab8"> |       <trans-unit id="s04bfd02201db5ab8"> | ||||||
| @ -4011,10 +3986,10 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sa95a538bfbb86111"> |       <trans-unit id="sa95a538bfbb86111"> | ||||||
|         <source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source> |         <source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source> | ||||||
|         <target>Êtes-vous sûr de vouloir mettre à jour |         <target>Êtes-vous sûr de vouloir mettre à jour | ||||||
|         <x id="0" equiv-text="${this.objectLabel}"/>"  |         <x id="0" equiv-text="${this.objectLabel}"/>" | ||||||
|         <x id="1" equiv-text="${this.obj?.name}"/>" ?</target> |         <x id="1" equiv-text="${this.obj?.name}"/>" ?</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sc92d7cfb6ee1fec6"> |       <trans-unit id="sc92d7cfb6ee1fec6"> | ||||||
| @ -5100,8 +5075,8 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sdf1d8edef27236f0"> |       <trans-unit id="sdf1d8edef27236f0"> | ||||||
|         <source>A "roaming" authenticator, like a YubiKey</source> |         <source>A "roaming" authenticator, like a YubiKey</source> | ||||||
|         <target>Un authentificateur "itinérant", comme une YubiKey</target> |         <target>Un authentificateur "itinérant", comme une YubiKey</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sfffba7b23d8fb40c"> |       <trans-unit id="sfffba7b23d8fb40c"> | ||||||
| @ -5426,7 +5401,7 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s5170f9ef331949c0"> |       <trans-unit id="s5170f9ef331949c0"> | ||||||
|         <source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source> |         <source>Show arbitrary input fields to the user, for example during enrollment. Data is saved in the flow context under the 'prompt_data' variable.</source> | ||||||
|         <target>Afficher des champs de saisie arbitraires à l'utilisateur, par exemple pendant l'inscription. Les données sont enregistrées dans le contexte du flux sous la variable "prompt_data".</target> |         <target>Afficher des champs de saisie arbitraires à l'utilisateur, par exemple pendant l'inscription. Les données sont enregistrées dans le contexte du flux sous la variable "prompt_data".</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s36cb242ac90353bc"> |       <trans-unit id="s36cb242ac90353bc"> | ||||||
| @ -5435,10 +5410,10 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s2d5f69929bb7221d"> |       <trans-unit id="s2d5f69929bb7221d"> | ||||||
|         <source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source> |         <source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source> | ||||||
|         <target> |         <target> | ||||||
|         <x id="0" equiv-text="${prompt.name}"/>("  |         <x id="0" equiv-text="${prompt.name}"/>(" | ||||||
|         <x id="1" equiv-text="${prompt.fieldKey}"/>", de type  |         <x id="1" equiv-text="${prompt.fieldKey}"/>", de type | ||||||
|         <x id="2" equiv-text="${prompt.type}"/>)</target> |         <x id="2" equiv-text="${prompt.type}"/>)</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
| @ -5487,8 +5462,8 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s1608b2f94fa0dbd4"> |       <trans-unit id="s1608b2f94fa0dbd4"> | ||||||
|         <source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source> |         <source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source> | ||||||
|         <target>Si défini à une durée supérieure à 0, l'utilisateur aura la possibilité de choisir de "rester connecté", ce qui prolongera sa session jusqu'à la durée spécifiée ici.</target> |         <target>Si défini à une durée supérieure à 0, l'utilisateur aura la possibilité de choisir de "rester connecté", ce qui prolongera sa session jusqu'à la durée spécifiée ici.</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s542a71bb8f41e057"> |       <trans-unit id="s542a71bb8f41e057"> | ||||||
| @ -6272,7 +6247,7 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti | |||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sa7fcf026bd25f231"> |       <trans-unit id="sa7fcf026bd25f231"> | ||||||
|         <source>Can be in the format of 'unix://' when connecting to a local docker daemon, using 'ssh://' to connect via SSH, or 'https://:2376' when connecting to a remote system.</source> |         <source>Can be in the format of 'unix://' when connecting to a local docker daemon, using 'ssh://' to connect via SSH, or 'https://:2376' when connecting to a remote system.</source> | ||||||
|         <target>Peut être au format "unix://" pour une connexion à un service docker local, "ssh://" pour une connexion via SSH, ou "https://:2376" pour une connexion à un système distant.</target> |         <target>Peut être au format "unix://" pour une connexion à un service docker local, "ssh://" pour une connexion via SSH, ou "https://:2376" pour une connexion à un système distant.</target> | ||||||
|  |  | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="saf1d289e3137c2ea"> |       <trans-unit id="saf1d289e3137c2ea"> | ||||||
| @ -7579,7 +7554,7 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sff0ac1ace2d90709"> | <trans-unit id="sff0ac1ace2d90709"> | ||||||
|   <source>Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you).</source> |   <source>Use this provider with nginx's auth_request or traefik's forwardAuth. Each application/domain needs its own provider. Additionally, on each domain, /outpost.goauthentik.io must be routed to the outpost (when using a managed outpost, this is done for you).</source> | ||||||
|   <target>Utilisez ce fournisseur avec l'option "auth_request" de Nginx ou "forwardAuth" de Traefik. Chaque application/domaine a besoin de son propre fournisseur. De plus, sur chaque domaine, "/outpost.goauthentik.io" doit être routé vers le poste avancé (lorsque vous utilisez un poste avancé géré, cela est fait pour vous).</target> |   <target>Utilisez ce fournisseur avec l'option "auth_request" de Nginx ou "forwardAuth" de Traefik. Chaque application/domaine a besoin de son propre fournisseur. De plus, sur chaque domaine, "/outpost.goauthentik.io" doit être routé vers le poste avancé (lorsque vous utilisez un poste avancé géré, cela est fait pour vous).</target> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="scb58b8a60cad8762"> | <trans-unit id="scb58b8a60cad8762"> | ||||||
|   <source>Default relay state</source> |   <source>Default relay state</source> | ||||||
| @ -7597,6 +7572,7 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti | |||||||
|   <source>Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello).</source> |   <source>Stage used to configure a WebAuthn authenticator (i.e. Yubikey, FaceID/Windows Hello).</source> | ||||||
|   <target>Étape de configuration d'un authentificateur WebAuthn (Yubikey, FaceID/Windows Hello).</target> |   <target>Étape de configuration d'un authentificateur WebAuthn (Yubikey, FaceID/Windows Hello).</target> | ||||||
| </trans-unit> | </trans-unit> | ||||||
|  | <<<<<<< HEAD | ||||||
| <trans-unit id="s1cffe58249b04669"> | <trans-unit id="s1cffe58249b04669"> | ||||||
|   <source>Internal application name used in URLs.</source> |   <source>Internal application name used in URLs.</source> | ||||||
|   <target>Nom de l'application interne utilisé dans les URLs.</target> |   <target>Nom de l'application interne utilisé dans les URLs.</target> | ||||||
| @ -7617,14 +7593,6 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti | |||||||
|   <source>For transparent reverse proxies with required authentication</source> |   <source>For transparent reverse proxies with required authentication</source> | ||||||
|   <target>Pour les reverses proxy transparents avec authentification requise</target> |   <target>Pour les reverses proxy transparents avec authentification requise</target> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sadf073913458acbd"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth</source> |  | ||||||
|   <target>Pour nginx auth_request ou traefik forwardAuth</target> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="se770e9498b3bacf6"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> |  | ||||||
|   <target>Pour nginx auth_request ou traefik forwardAuth par domaine racine</target> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s40830ec037f34626"> | <trans-unit id="s40830ec037f34626"> | ||||||
|   <source>Configure SAML provider manually</source> |   <source>Configure SAML provider manually</source> | ||||||
|   <target>Configurer le fournisseur SAML manuellement</target> |   <target>Configurer le fournisseur SAML manuellement</target> | ||||||
| @ -7649,14 +7617,6 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti | |||||||
|   <source>Your application has been saved</source> |   <source>Your application has been saved</source> | ||||||
|   <target>L'application a été sauvegardée</target> |   <target>L'application a été sauvegardée</target> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sf60f1e5b76897c93"> |  | ||||||
|   <source>In the Application:</source> |  | ||||||
|   <target>Dans l'application :</target> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s7ce65cf482b7bff0"> |  | ||||||
|   <source>In the Provider:</source> |  | ||||||
|   <target>Dans le fournisseur :</target> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s67d858051b34c38b"> | <trans-unit id="s67d858051b34c38b"> | ||||||
|   <source>Method's display Name.</source> |   <source>Method's display Name.</source> | ||||||
|   <target>Nom d'affichage de la méthode.</target> |   <target>Nom d'affichage de la méthode.</target> | ||||||
| @ -7931,14 +7891,50 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti | |||||||
|   <source><No name set></source> |   <source><No name set></source> | ||||||
|   <target><No name set></target> |   <target><No name set></target> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="s66313b45b69cfc88"> | <trans-unit id="sdc9a6ad1af30572c"> | ||||||
|   <source>Check the release notes</source> |   <source>For nginx's auth_request or traefik's forwardAuth</source> | ||||||
|  |   <target>Pour nginx auth_request ou traefik forwardAuth</target> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sb4d7bae2440d9781"> | <trans-unit id="sfc31264ef7ff86ef"> | ||||||
|   <source>User Statistics</source> |   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> | ||||||
|  |   <target>Pour nginx auth_request ou traefik forwardAuth par domaine racine</target> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sc615309d10a9228c"> | ||||||
|  |   <source>RBAC is in preview.</source> | ||||||
|  |   <target>RBAC est en aperçu,</target> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="s32babfed740fd3c1"> | <trans-unit id="s32babfed740fd3c1"> | ||||||
|   <source>User type used for newly created users.</source> |   <source>User type used for newly created users.</source> | ||||||
|  |   <target>Type d'utilisateur pour les utilisateurs nouvellement créés.</target> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sb35c08e3a541188f"> | ||||||
|  |   <source>Also known as Client ID.</source> | ||||||
|  |   <target>Également appelé Client ID.</target> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sd46fd9b647cfea10"> | ||||||
|  |   <source>Also known as Client Secret.</source> | ||||||
|  |   <target>Également appelé Client Secret.</target> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sf60f1e5b76897c93"> | ||||||
|  |   <source>In the Application:</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="s7ce65cf482b7bff0"> | ||||||
|  |   <source>In the Provider:</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="s1bfe7505059d164f"> | ||||||
|  |   <source>0: Too guessable: risky password. (guesses < 10^3)</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="s423d1f2477998d0b"> | ||||||
|  |   <source>1: Very guessable: protection from throttled online attacks. (guesses < 10^6)</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="s33849cc046eb901d"> | ||||||
|  |   <source>2: Somewhat guessable: protection from unthrottled online attacks. (guesses < 10^8)</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="s578dcce295718e1b"> | ||||||
|  |   <source>3: Safely unguessable: moderate protection from offline slow-hash scenario. (guesses < 10^10)</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="s7a46de49f4eba5d7"> | ||||||
|  |   <source>4: Very unguessable: strong protection from offline slow-hash scenario. (guesses >= 10^10)</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -5921,12 +5921,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| <trans-unit id="s945a6b94361ee45b"> | <trans-unit id="s945a6b94361ee45b"> | ||||||
|   <source>For transparent reverse proxies with required authentication</source> |   <source>For transparent reverse proxies with required authentication</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sadf073913458acbd"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="se770e9498b3bacf6"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s40830ec037f34626"> | <trans-unit id="s40830ec037f34626"> | ||||||
|   <source>Configure SAML provider manually</source> |   <source>Configure SAML provider manually</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| @ -6154,6 +6148,24 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="s0924f51b028233a3"> | <trans-unit id="s0924f51b028233a3"> | ||||||
|   <source><No name set></source> |   <source><No name set></source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sdc9a6ad1af30572c"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sfc31264ef7ff86ef"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sc615309d10a9228c"> | ||||||
|  |   <source>RBAC is in preview.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="s32babfed740fd3c1"> | ||||||
|  |   <source>User type used for newly created users.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sb35c08e3a541188f"> | ||||||
|  |   <source>Also known as Client ID.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sd46fd9b647cfea10"> | ||||||
|  |   <source>Also known as Client Secret.</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -7557,14 +7557,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
|   <source>For transparent reverse proxies with required authentication</source> |   <source>For transparent reverse proxies with required authentication</source> | ||||||
|   <target>Ƒōŕ ţŕàńśƥàŕēńţ ŕēvēŕśē ƥŕōxĩēś ŵĩţĥ ŕēǫũĩŕēď àũţĥēńţĩćàţĩōń</target> |   <target>Ƒōŕ ţŕàńśƥàŕēńţ ŕēvēŕśē ƥŕōxĩēś ŵĩţĥ ŕēǫũĩŕēď àũţĥēńţĩćàţĩōń</target> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sadf073913458acbd"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth</source> |  | ||||||
|   <target>Ƒōŕ ńĝĩńx'ś àũţĥ_ŕēǫũēśţ ōŕ ţŕàēƒĩx'ś ƒōŕŵàŕďÀũţĥ</target> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="se770e9498b3bacf6"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> |  | ||||||
|   <target>Ƒōŕ ńĝĩńx'ś àũţĥ_ŕēǫũēśţ ōŕ ţŕàēƒĩx'ś ƒōŕŵàŕďÀũţĥ ƥēŕ ŕōōţ ďōmàĩń</target> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s40830ec037f34626"> | <trans-unit id="s40830ec037f34626"> | ||||||
|   <source>Configure SAML provider manually</source> |   <source>Configure SAML provider manually</source> | ||||||
|   <target>Ćōńƒĩĝũŕē ŚÀMĹ ƥŕōvĩďēŕ màńũàĺĺŷ</target> |   <target>Ćōńƒĩĝũŕē ŚÀMĹ ƥŕōvĩďēŕ màńũàĺĺŷ</target> | ||||||
| @ -7844,4 +7836,22 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| <trans-unit id="s0924f51b028233a3"> | <trans-unit id="s0924f51b028233a3"> | ||||||
|   <source><No name set></source> |   <source><No name set></source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
|  | <trans-unit id="sdc9a6ad1af30572c"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sfc31264ef7ff86ef"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sc615309d10a9228c"> | ||||||
|  |   <source>RBAC is in preview.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="s32babfed740fd3c1"> | ||||||
|  |   <source>User type used for newly created users.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sb35c08e3a541188f"> | ||||||
|  |   <source>Also known as Client ID.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sd46fd9b647cfea10"> | ||||||
|  |   <source>Also known as Client Secret.</source> | ||||||
|  | </trans-unit> | ||||||
| </body></file></xliff> | </body></file></xliff> | ||||||
|  | |||||||
| @ -5706,12 +5706,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| <trans-unit id="s945a6b94361ee45b"> | <trans-unit id="s945a6b94361ee45b"> | ||||||
|   <source>For transparent reverse proxies with required authentication</source> |   <source>For transparent reverse proxies with required authentication</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sadf073913458acbd"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="se770e9498b3bacf6"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s40830ec037f34626"> | <trans-unit id="s40830ec037f34626"> | ||||||
|   <source>Configure SAML provider manually</source> |   <source>Configure SAML provider manually</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| @ -5939,6 +5933,24 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="s0924f51b028233a3"> | <trans-unit id="s0924f51b028233a3"> | ||||||
|   <source><No name set></source> |   <source><No name set></source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sdc9a6ad1af30572c"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sfc31264ef7ff86ef"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sc615309d10a9228c"> | ||||||
|  |   <source>RBAC is in preview.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="s32babfed740fd3c1"> | ||||||
|  |   <source>User type used for newly created users.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sb35c08e3a541188f"> | ||||||
|  |   <source>Also known as Client ID.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sd46fd9b647cfea10"> | ||||||
|  |   <source>Also known as Client Secret.</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -613,9 +613,9 @@ | |||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="saa0e2675da69651b"> |       <trans-unit id="saa0e2675da69651b"> | ||||||
|         <source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source> |         <source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source> | ||||||
|         <target>未找到 URL "  |         <target>未找到 URL "  | ||||||
|         <x id="0" equiv-text="${this.url}"/>"。</target> |         <x id="0" equiv-text="${this.url}"/>"。</target> | ||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s58cd9c2fe836d9c6"> |       <trans-unit id="s58cd9c2fe836d9c6"> | ||||||
| @ -1057,8 +1057,8 @@ | |||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sa8384c9c26731f83"> |       <trans-unit id="sa8384c9c26731f83"> | ||||||
|         <source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source> |         <source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source> | ||||||
|         <target>要允许任何重定向 URI,请将此值设置为 ".*"。请注意这可能带来的安全影响。</target> |         <target>要允许任何重定向 URI,请将此值设置为 ".*"。请注意这可能带来的安全影响。</target> | ||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s55787f4dfcdce52b"> |       <trans-unit id="s55787f4dfcdce52b"> | ||||||
| @ -1799,8 +1799,8 @@ | |||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sa90b7809586c35ce"> |       <trans-unit id="sa90b7809586c35ce"> | ||||||
|         <source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source> |         <source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source> | ||||||
|         <target>输入完整 URL、相对路径,或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target> |         <target>输入完整 URL、相对路径,或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target> | ||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s0410779cb47de312"> |       <trans-unit id="s0410779cb47de312"> | ||||||
| @ -3013,8 +3013,8 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s76768bebabb7d543"> |       <trans-unit id="s76768bebabb7d543"> | ||||||
|         <source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source> |         <source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source> | ||||||
|         <target>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target> |         <target>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target> | ||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s026555347e589f0e"> |       <trans-unit id="s026555347e589f0e"> | ||||||
| @ -3806,8 +3806,8 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s7b1fba26d245cb1c"> |       <trans-unit id="s7b1fba26d245cb1c"> | ||||||
|         <source>When using an external logging solution for archiving, this can be set to "minutes=5".</source> |         <source>When using an external logging solution for archiving, this can be set to "minutes=5".</source> | ||||||
|         <target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target> |         <target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target> | ||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s44536d20bb5c8257"> |       <trans-unit id="s44536d20bb5c8257"> | ||||||
| @ -3816,8 +3816,8 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s3bb51cabb02b997e"> |       <trans-unit id="s3bb51cabb02b997e"> | ||||||
|         <source>Format: "weeks=3;days=2;hours=3,seconds=2".</source> |         <source>Format: "weeks=3;days=2;hours=3,seconds=2".</source> | ||||||
|         <target>格式:"weeks=3;days=2;hours=3,seconds=2"。</target> |         <target>格式:"weeks=3;days=2;hours=3,seconds=2"。</target> | ||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s04bfd02201db5ab8"> |       <trans-unit id="s04bfd02201db5ab8"> | ||||||
| @ -4013,10 +4013,10 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sa95a538bfbb86111"> |       <trans-unit id="sa95a538bfbb86111"> | ||||||
|         <source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source> |         <source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source> | ||||||
|         <target>您确定要更新  |         <target>您确定要更新  | ||||||
|         <x id="0" equiv-text="${this.objectLabel}"/>"  |         <x id="0" equiv-text="${this.objectLabel}"/>"  | ||||||
|         <x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target> |         <x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target> | ||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sc92d7cfb6ee1fec6"> |       <trans-unit id="sc92d7cfb6ee1fec6"> | ||||||
| @ -5102,7 +5102,7 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="sdf1d8edef27236f0"> |       <trans-unit id="sdf1d8edef27236f0"> | ||||||
|         <source>A "roaming" authenticator, like a YubiKey</source> |         <source>A "roaming" authenticator, like a YubiKey</source> | ||||||
|         <target>像 YubiKey 这样的“漫游”身份验证器</target> |         <target>像 YubiKey 这样的“漫游”身份验证器</target> | ||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
| @ -5437,10 +5437,10 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s2d5f69929bb7221d"> |       <trans-unit id="s2d5f69929bb7221d"> | ||||||
|         <source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source> |         <source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source> | ||||||
|         <target> |         <target> | ||||||
|         <x id="0" equiv-text="${prompt.name}"/>("  |         <x id="0" equiv-text="${prompt.name}"/>("  | ||||||
|         <x id="1" equiv-text="${prompt.fieldKey}"/>",类型为  |         <x id="1" equiv-text="${prompt.fieldKey}"/>",类型为  | ||||||
|         <x id="2" equiv-text="${prompt.type}"/>)</target> |         <x id="2" equiv-text="${prompt.type}"/>)</target> | ||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
| @ -5489,7 +5489,7 @@ doesn't pass when either or both of the selected options are equal or above the | |||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="s1608b2f94fa0dbd4"> |       <trans-unit id="s1608b2f94fa0dbd4"> | ||||||
|         <source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source> |         <source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source> | ||||||
|         <target>如果设置时长大于 0,用户可以选择“保持登录”选项,这将使用户的会话延长此处设置的时间。</target> |         <target>如果设置时长大于 0,用户可以选择“保持登录”选项,这将使用户的会话延长此处设置的时间。</target> | ||||||
|          |          | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
| @ -7620,14 +7620,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
|   <source>For transparent reverse proxies with required authentication</source> |   <source>For transparent reverse proxies with required authentication</source> | ||||||
|   <target>适用于需要验证身份的透明反向代理</target> |   <target>适用于需要验证身份的透明反向代理</target> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sadf073913458acbd"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth</source> |  | ||||||
|   <target>适用于 nginx 的 auth_request 或 traefik 的 forwardAuth</target> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="se770e9498b3bacf6"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> |  | ||||||
|   <target>适用于按根域名配置的 nginx 的 auth_request 或 traefik 的 forwardAuth</target> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s40830ec037f34626"> | <trans-unit id="s40830ec037f34626"> | ||||||
|   <source>Configure SAML provider manually</source> |   <source>Configure SAML provider manually</source> | ||||||
|   <target>手动配置 SAML 提供程序</target> |   <target>手动配置 SAML 提供程序</target> | ||||||
| @ -7933,6 +7925,28 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| <trans-unit id="s0924f51b028233a3"> | <trans-unit id="s0924f51b028233a3"> | ||||||
|   <source><No name set></source> |   <source><No name set></source> | ||||||
|   <target><未设置名称></target> |   <target><未设置名称></target> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sdc9a6ad1af30572c"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth</source> | ||||||
|  |   <target>适用于 nginx 的 auth_request 或 traefik 的 forwardAuth</target> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sfc31264ef7ff86ef"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> | ||||||
|  |   <target>适用于按根域名配置的 nginx 的 auth_request 或 traefik 的 forwardAuth</target> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sc615309d10a9228c"> | ||||||
|  |   <source>RBAC is in preview.</source> | ||||||
|  |   <target>RBAC 目前处于预览状态。</target> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="s32babfed740fd3c1"> | ||||||
|  |   <source>User type used for newly created users.</source> | ||||||
|  |   <target>新创建用户使用的用户类型。</target> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sb35c08e3a541188f"> | ||||||
|  |   <source>Also known as Client ID.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sd46fd9b647cfea10"> | ||||||
|  |   <source>Also known as Client Secret.</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
| @ -5754,12 +5754,6 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| <trans-unit id="s945a6b94361ee45b"> | <trans-unit id="s945a6b94361ee45b"> | ||||||
|   <source>For transparent reverse proxies with required authentication</source> |   <source>For transparent reverse proxies with required authentication</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="sadf073913458acbd"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="se770e9498b3bacf6"> |  | ||||||
|   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> |  | ||||||
| </trans-unit> |  | ||||||
| <trans-unit id="s40830ec037f34626"> | <trans-unit id="s40830ec037f34626"> | ||||||
|   <source>Configure SAML provider manually</source> |   <source>Configure SAML provider manually</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
| @ -5987,6 +5981,24 @@ Bindings to groups/users are checked against the user of the event.</source> | |||||||
| </trans-unit> | </trans-unit> | ||||||
| <trans-unit id="s0924f51b028233a3"> | <trans-unit id="s0924f51b028233a3"> | ||||||
|   <source><No name set></source> |   <source><No name set></source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sdc9a6ad1af30572c"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sfc31264ef7ff86ef"> | ||||||
|  |   <source>For nginx's auth_request or traefik's forwardAuth per root domain</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sc615309d10a9228c"> | ||||||
|  |   <source>RBAC is in preview.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="s32babfed740fd3c1"> | ||||||
|  |   <source>User type used for newly created users.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sb35c08e3a541188f"> | ||||||
|  |   <source>Also known as Client ID.</source> | ||||||
|  | </trans-unit> | ||||||
|  | <trans-unit id="sd46fd9b647cfea10"> | ||||||
|  |   <source>Also known as Client Secret.</source> | ||||||
| </trans-unit> | </trans-unit> | ||||||
|     </body> |     </body> | ||||||
|   </file> |   </file> | ||||||
|  | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	