Compare commits
	
		
			9 Commits
		
	
	
		
			version-20
			...
			version/0.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2a94ad7782 | |||
| 07eb5ffb4b | |||
| 8cc68928b8 | |||
| 221db12f85 | |||
| 34166d3c20 | |||
| 94972d64e6 | |||
| 253eaa382c | |||
| fc4f9733d1 | |||
| 8d784afcd1 | 
| @ -1,11 +1,9 @@ | |||||||
| [bumpversion] | [bumpversion] | ||||||
| current_version = 2021.5.4 | current_version = 0.13.4-stable | ||||||
| tag = True | tag = True | ||||||
| commit = True | commit = True | ||||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*) | parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*) | ||||||
| serialize =  | serialize = {major}.{minor}.{patch}-{release} | ||||||
| 	{major}.{minor}.{patch}-{release} |  | ||||||
| 	{major}.{minor}.{patch} |  | ||||||
| message = release: {new_version} | message = release: {new_version} | ||||||
| tag_name = version/{new_version} | tag_name = version/{new_version} | ||||||
|  |  | ||||||
| @ -19,18 +17,20 @@ values = | |||||||
|  |  | ||||||
| [bumpversion:file:website/docs/installation/docker-compose.md] | [bumpversion:file:website/docs/installation/docker-compose.md] | ||||||
|  |  | ||||||
|  | [bumpversion:file:website/docs/installation/kubernetes.md] | ||||||
|  |  | ||||||
| [bumpversion:file:docker-compose.yml] | [bumpversion:file:docker-compose.yml] | ||||||
|  |  | ||||||
|  | [bumpversion:file:helm/values.yaml] | ||||||
|  |  | ||||||
|  | [bumpversion:file:helm/README.md] | ||||||
|  |  | ||||||
|  | [bumpversion:file:helm/Chart.yaml] | ||||||
|  |  | ||||||
| [bumpversion:file:.github/workflows/release.yml] | [bumpversion:file:.github/workflows/release.yml] | ||||||
|  |  | ||||||
| [bumpversion:file:authentik/__init__.py] | [bumpversion:file:authentik/__init__.py] | ||||||
|  |  | ||||||
| [bumpversion:file:internal/constants/constants.go] | [bumpversion:file:proxy/pkg/version.go] | ||||||
|  |  | ||||||
| [bumpversion:file:outpost/pkg/version.go] |  | ||||||
|  |  | ||||||
| [bumpversion:file:web/src/constants.ts] | [bumpversion:file:web/src/constants.ts] | ||||||
|  |  | ||||||
| [bumpversion:file:website/docs/outposts/manual-deploy-docker-compose.md] |  | ||||||
|  |  | ||||||
| [bumpversion:file:website/docs/outposts/manual-deploy-kubernetes.md] |  | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								.github/codecov.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/codecov.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +0,0 @@ | |||||||
| coverage: |  | ||||||
|   precision: 2 |  | ||||||
|   round: up |  | ||||||
							
								
								
									
										20
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,15 +1,7 @@ | |||||||
| version: 2 | version: 2 | ||||||
| updates: | updates: | ||||||
| - package-ecosystem: "github-actions" |  | ||||||
|   directory: "/" |  | ||||||
|   schedule: |  | ||||||
|     interval: daily |  | ||||||
|     time: "04:00" |  | ||||||
|   open-pull-requests-limit: 10 |  | ||||||
|   assignees: |  | ||||||
|   - BeryJu |  | ||||||
| - package-ecosystem: gomod | - package-ecosystem: gomod | ||||||
|   directory: "/outpost" |   directory: "/proxy" | ||||||
|   schedule: |   schedule: | ||||||
|     interval: daily |     interval: daily | ||||||
|     time: "04:00" |     time: "04:00" | ||||||
| @ -24,14 +16,6 @@ updates: | |||||||
|   open-pull-requests-limit: 10 |   open-pull-requests-limit: 10 | ||||||
|   assignees: |   assignees: | ||||||
|   - BeryJu |   - BeryJu | ||||||
| - package-ecosystem: npm |  | ||||||
|   directory: "/website" |  | ||||||
|   schedule: |  | ||||||
|     interval: daily |  | ||||||
|     time: "04:00" |  | ||||||
|   open-pull-requests-limit: 10 |  | ||||||
|   assignees: |  | ||||||
|   - BeryJu |  | ||||||
| - package-ecosystem: pip | - package-ecosystem: pip | ||||||
|   directory: "/" |   directory: "/" | ||||||
|   schedule: |   schedule: | ||||||
| @ -49,7 +33,7 @@ updates: | |||||||
|   assignees: |   assignees: | ||||||
|   - BeryJu |   - BeryJu | ||||||
| - package-ecosystem: docker | - package-ecosystem: docker | ||||||
|   directory: "/outpost" |   directory: "/proxy" | ||||||
|   schedule: |   schedule: | ||||||
|     interval: daily |     interval: daily | ||||||
|     time: "04:00" |     time: "04:00" | ||||||
|  | |||||||
							
								
								
									
										158
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										158
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @ -3,134 +3,87 @@ name: authentik-on-release | |||||||
| on: | on: | ||||||
|   release: |   release: | ||||||
|     types: [published, created] |     types: [published, created] | ||||||
|   push: |  | ||||||
|     branches: |  | ||||||
|       - version-* |  | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   # Build |   # Build | ||||||
|   build-server: |   build-server: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v1 | ||||||
|       - name: Set up QEMU |  | ||||||
|         uses: docker/setup-qemu-action@v1.1.0 |  | ||||||
|       - name: Set up Docker Buildx |  | ||||||
|         uses: docker/setup-buildx-action@v1 |  | ||||||
|       - name: Docker Login Registry |       - name: Docker Login Registry | ||||||
|         uses: docker/login-action@v1 |         env: | ||||||
|         with: |           DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | ||||||
|           username: ${{ secrets.DOCKER_USERNAME }} |           DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} | ||||||
|           password: ${{ secrets.DOCKER_PASSWORD }} |         run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD | ||||||
|       - name: Login to GitHub Container Registry |  | ||||||
|         uses: docker/login-action@v1 |  | ||||||
|         with: |  | ||||||
|           registry: ghcr.io |  | ||||||
|           username: ${{ github.repository_owner }} |  | ||||||
|           password: ${{ secrets.GITHUB_TOKEN }} |  | ||||||
|       - name: prepare ts api client |  | ||||||
|         run: | |  | ||||||
|           docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/api --additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0 |  | ||||||
|       - name: Building Docker Image |       - name: Building Docker Image | ||||||
|         uses: docker/build-push-action@v2 |         run: docker build | ||||||
|         with: |           --no-cache | ||||||
|           push: ${{ github.event_name == 'release' }} |           -t beryju/authentik:0.13.4-stable | ||||||
|           tags: | |           -t beryju/authentik:latest | ||||||
|             beryju/authentik:2021.5.4, |           -f Dockerfile . | ||||||
|             beryju/authentik:latest, |       - name: Push Docker Container to Registry (versioned) | ||||||
|             ghcr.io/goauthentik/server:2021.5.4, |         run: docker push beryju/authentik:0.13.4-stable | ||||||
|             ghcr.io/goauthentik/server:latest |       - name: Push Docker Container to Registry (latest) | ||||||
|           platforms: linux/amd64,linux/arm64 |         run: docker push beryju/authentik:latest | ||||||
|           context: . |  | ||||||
|   build-proxy: |   build-proxy: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v1 | ||||||
|       - uses: actions/setup-go@v2 |       - uses: actions/setup-go@v2 | ||||||
|         with: |         with: | ||||||
|           go-version: "^1.15" |           go-version: "^1.15" | ||||||
|       - name: prepare go api client |       - name: prepare go api client | ||||||
|         run: | |         run: | | ||||||
|           cd outpost |           cd proxy | ||||||
|           go get -u github.com/go-swagger/go-swagger/cmd/swagger |           go get -u github.com/go-swagger/go-swagger/cmd/swagger | ||||||
|           swagger generate client -f ../swagger.yaml -A authentik -t pkg/ |           swagger generate client -f ../swagger.yaml -A authentik -t pkg/ | ||||||
|           go build -v ./cmd/proxy/server.go |           go build -v . | ||||||
|       - name: Set up QEMU |  | ||||||
|         uses: docker/setup-qemu-action@v1.1.0 |  | ||||||
|       - name: Set up Docker Buildx |  | ||||||
|         uses: docker/setup-buildx-action@v1 |  | ||||||
|       - name: Docker Login Registry |       - name: Docker Login Registry | ||||||
|         uses: docker/login-action@v1 |         env: | ||||||
|         with: |           DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | ||||||
|           username: ${{ secrets.DOCKER_USERNAME }} |           DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} | ||||||
|           password: ${{ secrets.DOCKER_PASSWORD }} |         run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD | ||||||
|       - name: Login to GitHub Container Registry |  | ||||||
|         uses: docker/login-action@v1 |  | ||||||
|         with: |  | ||||||
|           registry: ghcr.io |  | ||||||
|           username: ${{ github.repository_owner }} |  | ||||||
|           password: ${{ secrets.GITHUB_TOKEN }} |  | ||||||
|       - name: Building Docker Image |       - name: Building Docker Image | ||||||
|         uses: docker/build-push-action@v2 |         run: | | ||||||
|         with: |           cd proxy/ | ||||||
|           push: ${{ github.event_name == 'release' }} |           docker build \ | ||||||
|           tags: | |           --no-cache \ | ||||||
|             beryju/authentik-proxy:2021.5.4, |           -t beryju/authentik-proxy:0.13.4-stable \ | ||||||
|             beryju/authentik-proxy:latest, |           -t beryju/authentik-proxy:latest \ | ||||||
|             ghcr.io/goauthentik/proxy:2021.5.4, |           -f Dockerfile . | ||||||
|             ghcr.io/goauthentik/proxy:latest |       - name: Push Docker Container to Registry (versioned) | ||||||
|           context: outpost/ |         run: docker push beryju/authentik-proxy:0.13.4-stable | ||||||
|           file: outpost/proxy.Dockerfile |       - name: Push Docker Container to Registry (latest) | ||||||
|           platforms: linux/amd64,linux/arm64 |         run: docker push beryju/authentik-proxy:latest | ||||||
|   build-ldap: |   build-static: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v1 | ||||||
|       - uses: actions/setup-go@v2 |  | ||||||
|         with: |  | ||||||
|           go-version: "^1.15" |  | ||||||
|       - name: prepare go api client |  | ||||||
|         run: | |  | ||||||
|           cd outpost |  | ||||||
|           go get -u github.com/go-swagger/go-swagger/cmd/swagger |  | ||||||
|           swagger generate client -f ../swagger.yaml -A authentik -t pkg/ |  | ||||||
|           go build -v ./cmd/ldap/server.go |  | ||||||
|       - name: Set up QEMU |  | ||||||
|         uses: docker/setup-qemu-action@v1.1.0 |  | ||||||
|       - name: Set up Docker Buildx |  | ||||||
|         uses: docker/setup-buildx-action@v1 |  | ||||||
|       - name: Docker Login Registry |       - name: Docker Login Registry | ||||||
|         uses: docker/login-action@v1 |         env: | ||||||
|         with: |           DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | ||||||
|           username: ${{ secrets.DOCKER_USERNAME }} |           DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} | ||||||
|           password: ${{ secrets.DOCKER_PASSWORD }} |         run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD | ||||||
|       - name: Login to GitHub Container Registry |  | ||||||
|         uses: docker/login-action@v1 |  | ||||||
|         with: |  | ||||||
|           registry: ghcr.io |  | ||||||
|           username: ${{ github.repository_owner }} |  | ||||||
|           password: ${{ secrets.GITHUB_TOKEN }} |  | ||||||
|       - name: Building Docker Image |       - name: Building Docker Image | ||||||
|         uses: docker/build-push-action@v2 |         run: | | ||||||
|         with: |           cd web/ | ||||||
|           push: ${{ github.event_name == 'release' }} |           docker build \ | ||||||
|           tags: | |           --no-cache \ | ||||||
|             beryju/authentik-ldap:2021.5.4, |           -t beryju/authentik-static:0.13.4-stable \ | ||||||
|             beryju/authentik-ldap:latest, |           -t beryju/authentik-static:latest \ | ||||||
|             ghcr.io/goauthentik/ldap:2021.5.4, |           -f Dockerfile . | ||||||
|             ghcr.io/goauthentik/ldap:latest |       - name: Push Docker Container to Registry (versioned) | ||||||
|           context: outpost/ |         run: docker push beryju/authentik-static:0.13.4-stable | ||||||
|           file: outpost/ldap.Dockerfile |       - name: Push Docker Container to Registry (latest) | ||||||
|           platforms: linux/amd64,linux/arm64 |         run: docker push beryju/authentik-static:latest | ||||||
|   test-release: |   test-release: | ||||||
|     if: ${{ github.event_name == 'release' }} |  | ||||||
|     needs: |     needs: | ||||||
|       - build-server |       - build-server | ||||||
|  |       - build-static | ||||||
|       - build-proxy |       - build-proxy | ||||||
|       - build-ldap |  | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v1 | ||||||
|       - name: Run test suite in final docker images |       - name: Run test suite in final docker images | ||||||
|         run: | |         run: | | ||||||
|           sudo apt-get install -y pwgen |           sudo apt-get install -y pwgen | ||||||
| @ -139,21 +92,20 @@ jobs: | |||||||
|           docker-compose pull -q |           docker-compose pull -q | ||||||
|           docker-compose up --no-start |           docker-compose up --no-start | ||||||
|           docker-compose start postgresql redis |           docker-compose start postgresql redis | ||||||
|           docker-compose run -u root --entrypoint /bin/bash server -c "apt-get update && apt-get install -y --no-install-recommends git && pip install --no-cache -r requirements-dev.txt && ./manage.py test authentik" |           docker-compose run -u root --entrypoint /bin/bash server -c "pip install --no-cache -r requirements-dev.txt && ./manage.py test authentik" | ||||||
|   sentry-release: |   sentry-release: | ||||||
|     if: ${{ github.event_name == 'release' }} |  | ||||||
|     needs: |     needs: | ||||||
|       - test-release |       - test-release | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v1 | ||||||
|       - name: Create a Sentry.io release |       - name: Create a Sentry.io release | ||||||
|         uses: getsentry/action-release@v1 |         uses: tclindner/sentry-releases-action@v1.2.0 | ||||||
|         env: |         env: | ||||||
|           SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} |           SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | ||||||
|           SENTRY_ORG: beryjuorg |           SENTRY_ORG: beryjuorg | ||||||
|           SENTRY_PROJECT: authentik |           SENTRY_PROJECT: authentik | ||||||
|           SENTRY_URL: https://sentry.beryju.org |           SENTRY_URL: https://sentry.beryju.org | ||||||
|         with: |         with: | ||||||
|           version: authentik@2021.5.4 |           tagName: 0.13.4-stable | ||||||
|           environment: beryjuorg-prod |           environment: beryjuorg-prod | ||||||
|  | |||||||
							
								
								
									
										30
									
								
								.github/workflows/tag.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/tag.yml
									
									
									
									
										vendored
									
									
								
							| @ -10,10 +10,7 @@ jobs: | |||||||
|     name: Create Release from Tag |     name: Create Release from Tag | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@master | ||||||
|       - name: prepare ts api client |  | ||||||
|         run: | |  | ||||||
|           docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/api --additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0 |  | ||||||
|       - name: Pre-release test |       - name: Pre-release test | ||||||
|         run: | |         run: | | ||||||
|           sudo apt-get install -y pwgen |           sudo apt-get install -y pwgen | ||||||
| @ -27,17 +24,26 @@ jobs: | |||||||
|             -f Dockerfile . |             -f Dockerfile . | ||||||
|           docker-compose up --no-start |           docker-compose up --no-start | ||||||
|           docker-compose start postgresql redis |           docker-compose start postgresql redis | ||||||
|           docker-compose run -u root --entrypoint /bin/bash server -c "apt-get update && apt-get install -y --no-install-recommends git && pip install --no-cache -r requirements-dev.txt && ./manage.py test authentik" |           docker-compose run -u root --entrypoint /bin/bash server -c "pip install --no-cache -r requirements-dev.txt && ./manage.py test authentik" | ||||||
|  |       - name: Install Helm | ||||||
|  |         run: | | ||||||
|  |           apt update && apt install -y curl | ||||||
|  |           curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash | ||||||
|  |       - name: Helm package | ||||||
|  |         run: | | ||||||
|  |           helm dependency update helm/ | ||||||
|  |           helm package helm/ | ||||||
|  |           mv authentik-*.tgz authentik-chart.tgz | ||||||
|       - name: Extract version number |       - name: Extract version number | ||||||
|         id: get_version |         id: get_version | ||||||
|         uses: actions/github-script@v4.0.2 |         uses: actions/github-script@0.2.0 | ||||||
|         with: |         with: | ||||||
|           github-token: ${{ secrets.GITHUB_TOKEN }} |           github-token: ${{ secrets.GITHUB_TOKEN }} | ||||||
|           script: | |           script: | | ||||||
|             return context.payload.ref.replace(/\/refs\/tags\/version\//, ''); |             return context.payload.ref.replace(/\/refs\/tags\/version\//, ''); | ||||||
|       - name: Create Release |       - name: Create Release | ||||||
|         id: create_release |         id: create_release | ||||||
|         uses: actions/create-release@v1.1.4 |         uses: actions/create-release@v1.0.0 | ||||||
|         env: |         env: | ||||||
|           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||
|         with: |         with: | ||||||
| @ -45,3 +51,13 @@ jobs: | |||||||
|           release_name: Release ${{ steps.get_version.outputs.result }} |           release_name: Release ${{ steps.get_version.outputs.result }} | ||||||
|           draft: true |           draft: true | ||||||
|           prerelease: false |           prerelease: false | ||||||
|  |       - name: Upload packaged Helm Chart | ||||||
|  |         id: upload-release-asset | ||||||
|  |         uses: actions/upload-release-asset@v1.0.1 | ||||||
|  |         env: | ||||||
|  |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||
|  |         with: | ||||||
|  |           upload_url: ${{ steps.create_release.outputs.upload_url }} | ||||||
|  |           asset_path: ./authentik-chart.tgz | ||||||
|  |           asset_name: authentik-chart.tgz | ||||||
|  |           asset_content_type: application/gzip | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -201,6 +201,3 @@ local.env.yml | |||||||
| selenium_screenshots/ | selenium_screenshots/ | ||||||
| backups/ | backups/ | ||||||
| media/ | media/ | ||||||
| *mmdb |  | ||||||
|  |  | ||||||
| .idea/ |  | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								.prospector.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.prospector.yaml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | strictness: medium | ||||||
|  | test-warnings: true | ||||||
|  | doc-warnings: false | ||||||
|  |  | ||||||
|  | ignore-paths: | ||||||
|  |   - migrations | ||||||
|  |   - docs | ||||||
|  |   - node_modules | ||||||
|  |  | ||||||
|  | uses: | ||||||
|  |   - django | ||||||
|  |   - celery | ||||||
							
								
								
									
										29
									
								
								.pylintrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								.pylintrc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | |||||||
|  | [MASTER] | ||||||
|  |  | ||||||
|  | disable = | ||||||
|  |     arguments-differ, | ||||||
|  |     no-self-use, | ||||||
|  |     fixme, | ||||||
|  |     locally-disabled, | ||||||
|  |     too-many-ancestors, | ||||||
|  |     too-few-public-methods, | ||||||
|  |     import-outside-toplevel, | ||||||
|  |     bad-continuation, | ||||||
|  |     signature-differs, | ||||||
|  |     similarities, | ||||||
|  |     cyclic-import, | ||||||
|  |     protected-access, | ||||||
|  |     unsubscriptable-object # remove when pylint is upgraded to 2.6 | ||||||
|  |  | ||||||
|  | load-plugins=pylint_django,pylint.extensions.bad_builtin | ||||||
|  |  | ||||||
|  | extension-pkg-whitelist=lxml,xmlsec | ||||||
|  |  | ||||||
|  | # Allow constants to be shorter than normal (and lowercase, for settings.py) | ||||||
|  | const-rgx=[a-zA-Z0-9_]{1,40}$ | ||||||
|  |  | ||||||
|  | ignored-modules=django-otp | ||||||
|  | generated-members=xmlsec.constants.*,xmlsec.tree.*,xmlsec.template.* | ||||||
|  | ignore=migrations | ||||||
|  | max-attributes=12 | ||||||
|  | max-branches=20 | ||||||
							
								
								
									
										55
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								Dockerfile
									
									
									
									
									
								
							| @ -1,4 +1,3 @@ | |||||||
| # Stage 1: Lock python dependencies |  | ||||||
| FROM python:3.9-slim-buster as locker | FROM python:3.9-slim-buster as locker | ||||||
|  |  | ||||||
| COPY ./Pipfile /app/ | COPY ./Pipfile /app/ | ||||||
| @ -10,66 +9,40 @@ RUN pip install pipenv && \ | |||||||
|     pipenv lock -r > requirements.txt && \ |     pipenv lock -r > requirements.txt && \ | ||||||
|     pipenv lock -rd > requirements-dev.txt |     pipenv lock -rd > requirements-dev.txt | ||||||
|  |  | ||||||
| # Stage 2: Build webui |  | ||||||
| FROM node as npm-builder |  | ||||||
|  |  | ||||||
| COPY ./web /static/ |  | ||||||
|  |  | ||||||
| ENV NODE_ENV=production |  | ||||||
| RUN cd /static && npm i --production=false && npm run build |  | ||||||
|  |  | ||||||
| # Stage 3: Build go proxy |  | ||||||
| FROM golang:1.16.4 AS builder |  | ||||||
|  |  | ||||||
| WORKDIR /work |  | ||||||
|  |  | ||||||
| COPY --from=npm-builder /static/robots.txt /work/web/robots.txt |  | ||||||
| COPY --from=npm-builder /static/security.txt /work/web/security.txt |  | ||||||
| COPY --from=npm-builder /static/dist/ /work/web/dist/ |  | ||||||
| COPY --from=npm-builder /static/authentik/ /work/web/authentik/ |  | ||||||
|  |  | ||||||
| # RUN ls /work/web/static/authentik/ && exit 1 |  | ||||||
| COPY ./cmd /work/cmd |  | ||||||
| COPY ./web/static.go /work/web/static.go |  | ||||||
| COPY ./internal /work/internal |  | ||||||
| COPY ./go.mod /work/go.mod |  | ||||||
| COPY ./go.sum /work/go.sum |  | ||||||
|  |  | ||||||
| RUN go build -o /work/authentik ./cmd/server/main.go |  | ||||||
|  |  | ||||||
| # Stage 4: Run |  | ||||||
| FROM python:3.9-slim-buster | FROM python:3.9-slim-buster | ||||||
|  |  | ||||||
| WORKDIR / | WORKDIR / | ||||||
| COPY --from=locker /app/requirements.txt / | COPY --from=locker /app/requirements.txt / | ||||||
| COPY --from=locker /app/requirements-dev.txt / | COPY --from=locker /app/requirements-dev.txt / | ||||||
|  |  | ||||||
| ARG GIT_BUILD_HASH |  | ||||||
| ENV GIT_BUILD_HASH=$GIT_BUILD_HASH |  | ||||||
|  |  | ||||||
| RUN apt-get update && \ | RUN apt-get update && \ | ||||||
|     apt-get install -y --no-install-recommends curl ca-certificates gnupg git runit && \ |     apt-get install -y --no-install-recommends curl ca-certificates gnupg && \ | ||||||
|     curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \ |     curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \ | ||||||
|     echo "deb http://apt.postgresql.org/pub/repos/apt buster-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \ |     echo "deb http://apt.postgresql.org/pub/repos/apt buster-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \ | ||||||
|     apt-get update && \ |     apt-get update && \ | ||||||
|     apt-get install -y --no-install-recommends libpq-dev postgresql-client build-essential libxmlsec1-dev pkg-config libmaxminddb0 && \ |     apt-get install -y --no-install-recommends postgresql-client-12 postgresql-client-11 build-essential libxmlsec1-dev pkg-config && \ | ||||||
|     pip install -r /requirements.txt --no-cache-dir && \ |  | ||||||
|     apt-get remove --purge -y build-essential git && \ |  | ||||||
|     apt-get autoremove --purge -y && \ |  | ||||||
|     apt-get clean && \ |     apt-get clean && \ | ||||||
|     rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/ && \ |     pip install -r /requirements.txt --no-cache-dir && \ | ||||||
|  |     apt-get remove --purge -y build-essential && \ | ||||||
|  |     apt-get autoremove --purge -y && \ | ||||||
|  |     # This is quite hacky, but docker has no guaranteed Group ID | ||||||
|  |     # we could instead check for the GID of the socket and add the user dynamically, | ||||||
|  |     # but then we have to drop permmissions later | ||||||
|  |     groupadd -g 998 docker_998 && \ | ||||||
|  |     groupadd -g 999 docker_999 && \ | ||||||
|     adduser --system --no-create-home --uid 1000 --group --home /authentik authentik && \ |     adduser --system --no-create-home --uid 1000 --group --home /authentik authentik && \ | ||||||
|  |     usermod -a -G docker_998 authentik && \ | ||||||
|  |     usermod -a -G docker_999 authentik && \ | ||||||
|     mkdir /backups && \ |     mkdir /backups && \ | ||||||
|     chown authentik:authentik /backups |     chown authentik:authentik /backups | ||||||
|  |  | ||||||
| COPY ./authentik/ /authentik | COPY ./authentik/ /authentik | ||||||
| COPY ./pyproject.toml / | COPY ./pytest.ini / | ||||||
| COPY ./xml /xml | COPY ./xml /xml | ||||||
| COPY ./manage.py / | COPY ./manage.py / | ||||||
| COPY ./lifecycle/ /lifecycle | COPY ./lifecycle/ /lifecycle | ||||||
| COPY --from=builder /work/authentik /authentik-proxy |  | ||||||
|  |  | ||||||
| USER authentik | USER authentik | ||||||
|  | STOPSIGNAL SIGINT | ||||||
| ENV TMPDIR /dev/shm/ | ENV TMPDIR /dev/shm/ | ||||||
| ENV PYTHONUBUFFERED 1 |  | ||||||
| ENTRYPOINT [ "/lifecycle/bootstrap.sh" ] | ENTRYPOINT [ "/lifecycle/bootstrap.sh" ] | ||||||
|  | |||||||
							
								
								
									
										695
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										695
									
								
								LICENSE
									
									
									
									
									
								
							| @ -1,674 +1,21 @@ | |||||||
|                     GNU GENERAL PUBLIC LICENSE | MIT License | ||||||
|                        Version 3, 29 June 2007 |  | ||||||
|  | Copyright (c) 2019 BeryJu.org | ||||||
|  Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> |  | ||||||
|  Everyone is permitted to copy and distribute verbatim copies | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  of this license document, but changing it is not allowed. | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | in the Software without restriction, including without limitation the rights | ||||||
|                             Preamble | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | copies of the Software, and to permit persons to whom the Software is | ||||||
|   The GNU General Public License is a free, copyleft license for | furnished to do so, subject to the following conditions: | ||||||
| software and other kinds of works. |  | ||||||
|  | The above copyright notice and this permission notice shall be included in all | ||||||
|   The licenses for most software and other practical works are designed | copies or substantial portions of the Software. | ||||||
| to take away your freedom to share and change the works.  By contrast, |  | ||||||
| the GNU General Public License is intended to guarantee your freedom to | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
| share and change all versions of a program--to make sure it remains free | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
| software for all its users.  We, the Free Software Foundation, use the | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
| GNU General Public License for most of our software; it applies also to | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
| any other work released this way by its authors.  You can apply it to | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
| your programs, too. | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||
|  | SOFTWARE. | ||||||
|   When we speak of free software, we are referring to freedom, not |  | ||||||
| price.  Our General Public Licenses are designed to make sure that you |  | ||||||
| have the freedom to distribute copies of free software (and charge for |  | ||||||
| them if you wish), that you receive source code or can get it if you |  | ||||||
| want it, that you can change the software or use pieces of it in new |  | ||||||
| free programs, and that you know you can do these things. |  | ||||||
|  |  | ||||||
|   To protect your rights, we need to prevent others from denying you |  | ||||||
| these rights or asking you to surrender the rights.  Therefore, you have |  | ||||||
| certain responsibilities if you distribute copies of the software, or if |  | ||||||
| you modify it: responsibilities to respect the freedom of others. |  | ||||||
|  |  | ||||||
|   For example, if you distribute copies of such a program, whether |  | ||||||
| gratis or for a fee, you must pass on to the recipients the same |  | ||||||
| freedoms that you received.  You must make sure that they, too, receive |  | ||||||
| or can get the source code.  And you must show them these terms so they |  | ||||||
| know their rights. |  | ||||||
|  |  | ||||||
|   Developers that use the GNU GPL protect your rights with two steps: |  | ||||||
| (1) assert copyright on the software, and (2) offer you this License |  | ||||||
| giving you legal permission to copy, distribute and/or modify it. |  | ||||||
|  |  | ||||||
|   For the developers' and authors' protection, the GPL clearly explains |  | ||||||
| that there is no warranty for this free software.  For both users' and |  | ||||||
| authors' sake, the GPL requires that modified versions be marked as |  | ||||||
| changed, so that their problems will not be attributed erroneously to |  | ||||||
| authors of previous versions. |  | ||||||
|  |  | ||||||
|   Some devices are designed to deny users access to install or run |  | ||||||
| modified versions of the software inside them, although the manufacturer |  | ||||||
| can do so.  This is fundamentally incompatible with the aim of |  | ||||||
| protecting users' freedom to change the software.  The systematic |  | ||||||
| pattern of such abuse occurs in the area of products for individuals to |  | ||||||
| use, which is precisely where it is most unacceptable.  Therefore, we |  | ||||||
| have designed this version of the GPL to prohibit the practice for those |  | ||||||
| products.  If such problems arise substantially in other domains, we |  | ||||||
| stand ready to extend this provision to those domains in future versions |  | ||||||
| of the GPL, as needed to protect the freedom of users. |  | ||||||
|  |  | ||||||
|   Finally, every program is threatened constantly by software patents. |  | ||||||
| States should not allow patents to restrict development and use of |  | ||||||
| software on general-purpose computers, but in those that do, we wish to |  | ||||||
| avoid the special danger that patents applied to a free program could |  | ||||||
| make it effectively proprietary.  To prevent this, the GPL assures that |  | ||||||
| patents cannot be used to render the program non-free. |  | ||||||
|  |  | ||||||
|   The precise terms and conditions for copying, distribution and |  | ||||||
| modification follow. |  | ||||||
|  |  | ||||||
|                        TERMS AND CONDITIONS |  | ||||||
|  |  | ||||||
|   0. Definitions. |  | ||||||
|  |  | ||||||
|   "This License" refers to version 3 of the GNU General Public License. |  | ||||||
|  |  | ||||||
|   "Copyright" also means copyright-like laws that apply to other kinds of |  | ||||||
| works, such as semiconductor masks. |  | ||||||
|  |  | ||||||
|   "The Program" refers to any copyrightable work licensed under this |  | ||||||
| License.  Each licensee is addressed as "you".  "Licensees" and |  | ||||||
| "recipients" may be individuals or organizations. |  | ||||||
|  |  | ||||||
|   To "modify" a work means to copy from or adapt all or part of the work |  | ||||||
| in a fashion requiring copyright permission, other than the making of an |  | ||||||
| exact copy.  The resulting work is called a "modified version" of the |  | ||||||
| earlier work or a work "based on" the earlier work. |  | ||||||
|  |  | ||||||
|   A "covered work" means either the unmodified Program or a work based |  | ||||||
| on the Program. |  | ||||||
|  |  | ||||||
|   To "propagate" a work means to do anything with it that, without |  | ||||||
| permission, would make you directly or secondarily liable for |  | ||||||
| infringement under applicable copyright law, except executing it on a |  | ||||||
| computer or modifying a private copy.  Propagation includes copying, |  | ||||||
| distribution (with or without modification), making available to the |  | ||||||
| public, and in some countries other activities as well. |  | ||||||
|  |  | ||||||
|   To "convey" a work means any kind of propagation that enables other |  | ||||||
| parties to make or receive copies.  Mere interaction with a user through |  | ||||||
| a computer network, with no transfer of a copy, is not conveying. |  | ||||||
|  |  | ||||||
|   An interactive user interface displays "Appropriate Legal Notices" |  | ||||||
| to the extent that it includes a convenient and prominently visible |  | ||||||
| feature that (1) displays an appropriate copyright notice, and (2) |  | ||||||
| tells the user that there is no warranty for the work (except to the |  | ||||||
| extent that warranties are provided), that licensees may convey the |  | ||||||
| work under this License, and how to view a copy of this License.  If |  | ||||||
| the interface presents a list of user commands or options, such as a |  | ||||||
| menu, a prominent item in the list meets this criterion. |  | ||||||
|  |  | ||||||
|   1. Source Code. |  | ||||||
|  |  | ||||||
|   The "source code" for a work means the preferred form of the work |  | ||||||
| for making modifications to it.  "Object code" means any non-source |  | ||||||
| form of a work. |  | ||||||
|  |  | ||||||
|   A "Standard Interface" means an interface that either is an official |  | ||||||
| standard defined by a recognized standards body, or, in the case of |  | ||||||
| interfaces specified for a particular programming language, one that |  | ||||||
| is widely used among developers working in that language. |  | ||||||
|  |  | ||||||
|   The "System Libraries" of an executable work include anything, other |  | ||||||
| than the work as a whole, that (a) is included in the normal form of |  | ||||||
| packaging a Major Component, but which is not part of that Major |  | ||||||
| Component, and (b) serves only to enable use of the work with that |  | ||||||
| Major Component, or to implement a Standard Interface for which an |  | ||||||
| implementation is available to the public in source code form.  A |  | ||||||
| "Major Component", in this context, means a major essential component |  | ||||||
| (kernel, window system, and so on) of the specific operating system |  | ||||||
| (if any) on which the executable work runs, or a compiler used to |  | ||||||
| produce the work, or an object code interpreter used to run it. |  | ||||||
|  |  | ||||||
|   The "Corresponding Source" for a work in object code form means all |  | ||||||
| the source code needed to generate, install, and (for an executable |  | ||||||
| work) run the object code and to modify the work, including scripts to |  | ||||||
| control those activities.  However, it does not include the work's |  | ||||||
| System Libraries, or general-purpose tools or generally available free |  | ||||||
| programs which are used unmodified in performing those activities but |  | ||||||
| which are not part of the work.  For example, Corresponding Source |  | ||||||
| includes interface definition files associated with source files for |  | ||||||
| the work, and the source code for shared libraries and dynamically |  | ||||||
| linked subprograms that the work is specifically designed to require, |  | ||||||
| such as by intimate data communication or control flow between those |  | ||||||
| subprograms and other parts of the work. |  | ||||||
|  |  | ||||||
|   The Corresponding Source need not include anything that users |  | ||||||
| can regenerate automatically from other parts of the Corresponding |  | ||||||
| Source. |  | ||||||
|  |  | ||||||
|   The Corresponding Source for a work in source code form is that |  | ||||||
| same work. |  | ||||||
|  |  | ||||||
|   2. Basic Permissions. |  | ||||||
|  |  | ||||||
|   All rights granted under this License are granted for the term of |  | ||||||
| copyright on the Program, and are irrevocable provided the stated |  | ||||||
| conditions are met.  This License explicitly affirms your unlimited |  | ||||||
| permission to run the unmodified Program.  The output from running a |  | ||||||
| covered work is covered by this License only if the output, given its |  | ||||||
| content, constitutes a covered work.  This License acknowledges your |  | ||||||
| rights of fair use or other equivalent, as provided by copyright law. |  | ||||||
|  |  | ||||||
|   You may make, run and propagate covered works that you do not |  | ||||||
| convey, without conditions so long as your license otherwise remains |  | ||||||
| in force.  You may convey covered works to others for the sole purpose |  | ||||||
| of having them make modifications exclusively for you, or provide you |  | ||||||
| with facilities for running those works, provided that you comply with |  | ||||||
| the terms of this License in conveying all material for which you do |  | ||||||
| not control copyright.  Those thus making or running the covered works |  | ||||||
| for you must do so exclusively on your behalf, under your direction |  | ||||||
| and control, on terms that prohibit them from making any copies of |  | ||||||
| your copyrighted material outside their relationship with you. |  | ||||||
|  |  | ||||||
|   Conveying under any other circumstances is permitted solely under |  | ||||||
| the conditions stated below.  Sublicensing is not allowed; section 10 |  | ||||||
| makes it unnecessary. |  | ||||||
|  |  | ||||||
|   3. Protecting Users' Legal Rights From Anti-Circumvention Law. |  | ||||||
|  |  | ||||||
|   No covered work shall be deemed part of an effective technological |  | ||||||
| measure under any applicable law fulfilling obligations under article |  | ||||||
| 11 of the WIPO copyright treaty adopted on 20 December 1996, or |  | ||||||
| similar laws prohibiting or restricting circumvention of such |  | ||||||
| measures. |  | ||||||
|  |  | ||||||
|   When you convey a covered work, you waive any legal power to forbid |  | ||||||
| circumvention of technological measures to the extent such circumvention |  | ||||||
| is effected by exercising rights under this License with respect to |  | ||||||
| the covered work, and you disclaim any intention to limit operation or |  | ||||||
| modification of the work as a means of enforcing, against the work's |  | ||||||
| users, your or third parties' legal rights to forbid circumvention of |  | ||||||
| technological measures. |  | ||||||
|  |  | ||||||
|   4. Conveying Verbatim Copies. |  | ||||||
|  |  | ||||||
|   You may convey verbatim copies of the Program's source code as you |  | ||||||
| receive it, in any medium, provided that you conspicuously and |  | ||||||
| appropriately publish on each copy an appropriate copyright notice; |  | ||||||
| keep intact all notices stating that this License and any |  | ||||||
| non-permissive terms added in accord with section 7 apply to the code; |  | ||||||
| keep intact all notices of the absence of any warranty; and give all |  | ||||||
| recipients a copy of this License along with the Program. |  | ||||||
|  |  | ||||||
|   You may charge any price or no price for each copy that you convey, |  | ||||||
| and you may offer support or warranty protection for a fee. |  | ||||||
|  |  | ||||||
|   5. Conveying Modified Source Versions. |  | ||||||
|  |  | ||||||
|   You may convey a work based on the Program, or the modifications to |  | ||||||
| produce it from the Program, in the form of source code under the |  | ||||||
| terms of section 4, provided that you also meet all of these conditions: |  | ||||||
|  |  | ||||||
|     a) The work must carry prominent notices stating that you modified |  | ||||||
|     it, and giving a relevant date. |  | ||||||
|  |  | ||||||
|     b) The work must carry prominent notices stating that it is |  | ||||||
|     released under this License and any conditions added under section |  | ||||||
|     7.  This requirement modifies the requirement in section 4 to |  | ||||||
|     "keep intact all notices". |  | ||||||
|  |  | ||||||
|     c) You must license the entire work, as a whole, under this |  | ||||||
|     License to anyone who comes into possession of a copy.  This |  | ||||||
|     License will therefore apply, along with any applicable section 7 |  | ||||||
|     additional terms, to the whole of the work, and all its parts, |  | ||||||
|     regardless of how they are packaged.  This License gives no |  | ||||||
|     permission to license the work in any other way, but it does not |  | ||||||
|     invalidate such permission if you have separately received it. |  | ||||||
|  |  | ||||||
|     d) If the work has interactive user interfaces, each must display |  | ||||||
|     Appropriate Legal Notices; however, if the Program has interactive |  | ||||||
|     interfaces that do not display Appropriate Legal Notices, your |  | ||||||
|     work need not make them do so. |  | ||||||
|  |  | ||||||
|   A compilation of a covered work with other separate and independent |  | ||||||
| works, which are not by their nature extensions of the covered work, |  | ||||||
| and which are not combined with it such as to form a larger program, |  | ||||||
| in or on a volume of a storage or distribution medium, is called an |  | ||||||
| "aggregate" if the compilation and its resulting copyright are not |  | ||||||
| used to limit the access or legal rights of the compilation's users |  | ||||||
| beyond what the individual works permit.  Inclusion of a covered work |  | ||||||
| in an aggregate does not cause this License to apply to the other |  | ||||||
| parts of the aggregate. |  | ||||||
|  |  | ||||||
|   6. Conveying Non-Source Forms. |  | ||||||
|  |  | ||||||
|   You may convey a covered work in object code form under the terms |  | ||||||
| of sections 4 and 5, provided that you also convey the |  | ||||||
| machine-readable Corresponding Source under the terms of this License, |  | ||||||
| in one of these ways: |  | ||||||
|  |  | ||||||
|     a) Convey the object code in, or embodied in, a physical product |  | ||||||
|     (including a physical distribution medium), accompanied by the |  | ||||||
|     Corresponding Source fixed on a durable physical medium |  | ||||||
|     customarily used for software interchange. |  | ||||||
|  |  | ||||||
|     b) Convey the object code in, or embodied in, a physical product |  | ||||||
|     (including a physical distribution medium), accompanied by a |  | ||||||
|     written offer, valid for at least three years and valid for as |  | ||||||
|     long as you offer spare parts or customer support for that product |  | ||||||
|     model, to give anyone who possesses the object code either (1) a |  | ||||||
|     copy of the Corresponding Source for all the software in the |  | ||||||
|     product that is covered by this License, on a durable physical |  | ||||||
|     medium customarily used for software interchange, for a price no |  | ||||||
|     more than your reasonable cost of physically performing this |  | ||||||
|     conveying of source, or (2) access to copy the |  | ||||||
|     Corresponding Source from a network server at no charge. |  | ||||||
|  |  | ||||||
|     c) Convey individual copies of the object code with a copy of the |  | ||||||
|     written offer to provide the Corresponding Source.  This |  | ||||||
|     alternative is allowed only occasionally and noncommercially, and |  | ||||||
|     only if you received the object code with such an offer, in accord |  | ||||||
|     with subsection 6b. |  | ||||||
|  |  | ||||||
|     d) Convey the object code by offering access from a designated |  | ||||||
|     place (gratis or for a charge), and offer equivalent access to the |  | ||||||
|     Corresponding Source in the same way through the same place at no |  | ||||||
|     further charge.  You need not require recipients to copy the |  | ||||||
|     Corresponding Source along with the object code.  If the place to |  | ||||||
|     copy the object code is a network server, the Corresponding Source |  | ||||||
|     may be on a different server (operated by you or a third party) |  | ||||||
|     that supports equivalent copying facilities, provided you maintain |  | ||||||
|     clear directions next to the object code saying where to find the |  | ||||||
|     Corresponding Source.  Regardless of what server hosts the |  | ||||||
|     Corresponding Source, you remain obligated to ensure that it is |  | ||||||
|     available for as long as needed to satisfy these requirements. |  | ||||||
|  |  | ||||||
|     e) Convey the object code using peer-to-peer transmission, provided |  | ||||||
|     you inform other peers where the object code and Corresponding |  | ||||||
|     Source of the work are being offered to the general public at no |  | ||||||
|     charge under subsection 6d. |  | ||||||
|  |  | ||||||
|   A separable portion of the object code, whose source code is excluded |  | ||||||
| from the Corresponding Source as a System Library, need not be |  | ||||||
| included in conveying the object code work. |  | ||||||
|  |  | ||||||
|   A "User Product" is either (1) a "consumer product", which means any |  | ||||||
| tangible personal property which is normally used for personal, family, |  | ||||||
| or household purposes, or (2) anything designed or sold for incorporation |  | ||||||
| into a dwelling.  In determining whether a product is a consumer product, |  | ||||||
| doubtful cases shall be resolved in favor of coverage.  For a particular |  | ||||||
| product received by a particular user, "normally used" refers to a |  | ||||||
| typical or common use of that class of product, regardless of the status |  | ||||||
| of the particular user or of the way in which the particular user |  | ||||||
| actually uses, or expects or is expected to use, the product.  A product |  | ||||||
| is a consumer product regardless of whether the product has substantial |  | ||||||
| commercial, industrial or non-consumer uses, unless such uses represent |  | ||||||
| the only significant mode of use of the product. |  | ||||||
|  |  | ||||||
|   "Installation Information" for a User Product means any methods, |  | ||||||
| procedures, authorization keys, or other information required to install |  | ||||||
| and execute modified versions of a covered work in that User Product from |  | ||||||
| a modified version of its Corresponding Source.  The information must |  | ||||||
| suffice to ensure that the continued functioning of the modified object |  | ||||||
| code is in no case prevented or interfered with solely because |  | ||||||
| modification has been made. |  | ||||||
|  |  | ||||||
|   If you convey an object code work under this section in, or with, or |  | ||||||
| specifically for use in, a User Product, and the conveying occurs as |  | ||||||
| part of a transaction in which the right of possession and use of the |  | ||||||
| User Product is transferred to the recipient in perpetuity or for a |  | ||||||
| fixed term (regardless of how the transaction is characterized), the |  | ||||||
| Corresponding Source conveyed under this section must be accompanied |  | ||||||
| by the Installation Information.  But this requirement does not apply |  | ||||||
| if neither you nor any third party retains the ability to install |  | ||||||
| modified object code on the User Product (for example, the work has |  | ||||||
| been installed in ROM). |  | ||||||
|  |  | ||||||
|   The requirement to provide Installation Information does not include a |  | ||||||
| requirement to continue to provide support service, warranty, or updates |  | ||||||
| for a work that has been modified or installed by the recipient, or for |  | ||||||
| the User Product in which it has been modified or installed.  Access to a |  | ||||||
| network may be denied when the modification itself materially and |  | ||||||
| adversely affects the operation of the network or violates the rules and |  | ||||||
| protocols for communication across the network. |  | ||||||
|  |  | ||||||
|   Corresponding Source conveyed, and Installation Information provided, |  | ||||||
| in accord with this section must be in a format that is publicly |  | ||||||
| documented (and with an implementation available to the public in |  | ||||||
| source code form), and must require no special password or key for |  | ||||||
| unpacking, reading or copying. |  | ||||||
|  |  | ||||||
|   7. Additional Terms. |  | ||||||
|  |  | ||||||
|   "Additional permissions" are terms that supplement the terms of this |  | ||||||
| License by making exceptions from one or more of its conditions. |  | ||||||
| Additional permissions that are applicable to the entire Program shall |  | ||||||
| be treated as though they were included in this License, to the extent |  | ||||||
| that they are valid under applicable law.  If additional permissions |  | ||||||
| apply only to part of the Program, that part may be used separately |  | ||||||
| under those permissions, but the entire Program remains governed by |  | ||||||
| this License without regard to the additional permissions. |  | ||||||
|  |  | ||||||
|   When you convey a copy of a covered work, you may at your option |  | ||||||
| remove any additional permissions from that copy, or from any part of |  | ||||||
| it.  (Additional permissions may be written to require their own |  | ||||||
| removal in certain cases when you modify the work.)  You may place |  | ||||||
| additional permissions on material, added by you to a covered work, |  | ||||||
| for which you have or can give appropriate copyright permission. |  | ||||||
|  |  | ||||||
|   Notwithstanding any other provision of this License, for material you |  | ||||||
| add to a covered work, you may (if authorized by the copyright holders of |  | ||||||
| that material) supplement the terms of this License with terms: |  | ||||||
|  |  | ||||||
|     a) Disclaiming warranty or limiting liability differently from the |  | ||||||
|     terms of sections 15 and 16 of this License; or |  | ||||||
|  |  | ||||||
|     b) Requiring preservation of specified reasonable legal notices or |  | ||||||
|     author attributions in that material or in the Appropriate Legal |  | ||||||
|     Notices displayed by works containing it; or |  | ||||||
|  |  | ||||||
|     c) Prohibiting misrepresentation of the origin of that material, or |  | ||||||
|     requiring that modified versions of such material be marked in |  | ||||||
|     reasonable ways as different from the original version; or |  | ||||||
|  |  | ||||||
|     d) Limiting the use for publicity purposes of names of licensors or |  | ||||||
|     authors of the material; or |  | ||||||
|  |  | ||||||
|     e) Declining to grant rights under trademark law for use of some |  | ||||||
|     trade names, trademarks, or service marks; or |  | ||||||
|  |  | ||||||
|     f) Requiring indemnification of licensors and authors of that |  | ||||||
|     material by anyone who conveys the material (or modified versions of |  | ||||||
|     it) with contractual assumptions of liability to the recipient, for |  | ||||||
|     any liability that these contractual assumptions directly impose on |  | ||||||
|     those licensors and authors. |  | ||||||
|  |  | ||||||
|   All other non-permissive additional terms are considered "further |  | ||||||
| restrictions" within the meaning of section 10.  If the Program as you |  | ||||||
| received it, or any part of it, contains a notice stating that it is |  | ||||||
| governed by this License along with a term that is a further |  | ||||||
| restriction, you may remove that term.  If a license document contains |  | ||||||
| a further restriction but permits relicensing or conveying under this |  | ||||||
| License, you may add to a covered work material governed by the terms |  | ||||||
| of that license document, provided that the further restriction does |  | ||||||
| not survive such relicensing or conveying. |  | ||||||
|  |  | ||||||
|   If you add terms to a covered work in accord with this section, you |  | ||||||
| must place, in the relevant source files, a statement of the |  | ||||||
| additional terms that apply to those files, or a notice indicating |  | ||||||
| where to find the applicable terms. |  | ||||||
|  |  | ||||||
|   Additional terms, permissive or non-permissive, may be stated in the |  | ||||||
| form of a separately written license, or stated as exceptions; |  | ||||||
| the above requirements apply either way. |  | ||||||
|  |  | ||||||
|   8. Termination. |  | ||||||
|  |  | ||||||
|   You may not propagate or modify a covered work except as expressly |  | ||||||
| provided under this License.  Any attempt otherwise to propagate or |  | ||||||
| modify it is void, and will automatically terminate your rights under |  | ||||||
| this License (including any patent licenses granted under the third |  | ||||||
| paragraph of section 11). |  | ||||||
|  |  | ||||||
|   However, if you cease all violation of this License, then your |  | ||||||
| license from a particular copyright holder is reinstated (a) |  | ||||||
| provisionally, unless and until the copyright holder explicitly and |  | ||||||
| finally terminates your license, and (b) permanently, if the copyright |  | ||||||
| holder fails to notify you of the violation by some reasonable means |  | ||||||
| prior to 60 days after the cessation. |  | ||||||
|  |  | ||||||
|   Moreover, your license from a particular copyright holder is |  | ||||||
| reinstated permanently if the copyright holder notifies you of the |  | ||||||
| violation by some reasonable means, this is the first time you have |  | ||||||
| received notice of violation of this License (for any work) from that |  | ||||||
| copyright holder, and you cure the violation prior to 30 days after |  | ||||||
| your receipt of the notice. |  | ||||||
|  |  | ||||||
|   Termination of your rights under this section does not terminate the |  | ||||||
| licenses of parties who have received copies or rights from you under |  | ||||||
| this License.  If your rights have been terminated and not permanently |  | ||||||
| reinstated, you do not qualify to receive new licenses for the same |  | ||||||
| material under section 10. |  | ||||||
|  |  | ||||||
|   9. Acceptance Not Required for Having Copies. |  | ||||||
|  |  | ||||||
|   You are not required to accept this License in order to receive or |  | ||||||
| run a copy of the Program.  Ancillary propagation of a covered work |  | ||||||
| occurring solely as a consequence of using peer-to-peer transmission |  | ||||||
| to receive a copy likewise does not require acceptance.  However, |  | ||||||
| nothing other than this License grants you permission to propagate or |  | ||||||
| modify any covered work.  These actions infringe copyright if you do |  | ||||||
| not accept this License.  Therefore, by modifying or propagating a |  | ||||||
| covered work, you indicate your acceptance of this License to do so. |  | ||||||
|  |  | ||||||
|   10. Automatic Licensing of Downstream Recipients. |  | ||||||
|  |  | ||||||
|   Each time you convey a covered work, the recipient automatically |  | ||||||
| receives a license from the original licensors, to run, modify and |  | ||||||
| propagate that work, subject to this License.  You are not responsible |  | ||||||
| for enforcing compliance by third parties with this License. |  | ||||||
|  |  | ||||||
|   An "entity transaction" is a transaction transferring control of an |  | ||||||
| organization, or substantially all assets of one, or subdividing an |  | ||||||
| organization, or merging organizations.  If propagation of a covered |  | ||||||
| work results from an entity transaction, each party to that |  | ||||||
| transaction who receives a copy of the work also receives whatever |  | ||||||
| licenses to the work the party's predecessor in interest had or could |  | ||||||
| give under the previous paragraph, plus a right to possession of the |  | ||||||
| Corresponding Source of the work from the predecessor in interest, if |  | ||||||
| the predecessor has it or can get it with reasonable efforts. |  | ||||||
|  |  | ||||||
|   You may not impose any further restrictions on the exercise of the |  | ||||||
| rights granted or affirmed under this License.  For example, you may |  | ||||||
| not impose a license fee, royalty, or other charge for exercise of |  | ||||||
| rights granted under this License, and you may not initiate litigation |  | ||||||
| (including a cross-claim or counterclaim in a lawsuit) alleging that |  | ||||||
| any patent claim is infringed by making, using, selling, offering for |  | ||||||
| sale, or importing the Program or any portion of it. |  | ||||||
|  |  | ||||||
|   11. Patents. |  | ||||||
|  |  | ||||||
|   A "contributor" is a copyright holder who authorizes use under this |  | ||||||
| License of the Program or a work on which the Program is based.  The |  | ||||||
| work thus licensed is called the contributor's "contributor version". |  | ||||||
|  |  | ||||||
|   A contributor's "essential patent claims" are all patent claims |  | ||||||
| owned or controlled by the contributor, whether already acquired or |  | ||||||
| hereafter acquired, that would be infringed by some manner, permitted |  | ||||||
| by this License, of making, using, or selling its contributor version, |  | ||||||
| but do not include claims that would be infringed only as a |  | ||||||
| consequence of further modification of the contributor version.  For |  | ||||||
| purposes of this definition, "control" includes the right to grant |  | ||||||
| patent sublicenses in a manner consistent with the requirements of |  | ||||||
| this License. |  | ||||||
|  |  | ||||||
|   Each contributor grants you a non-exclusive, worldwide, royalty-free |  | ||||||
| patent license under the contributor's essential patent claims, to |  | ||||||
| make, use, sell, offer for sale, import and otherwise run, modify and |  | ||||||
| propagate the contents of its contributor version. |  | ||||||
|  |  | ||||||
|   In the following three paragraphs, a "patent license" is any express |  | ||||||
| agreement or commitment, however denominated, not to enforce a patent |  | ||||||
| (such as an express permission to practice a patent or covenant not to |  | ||||||
| sue for patent infringement).  To "grant" such a patent license to a |  | ||||||
| party means to make such an agreement or commitment not to enforce a |  | ||||||
| patent against the party. |  | ||||||
|  |  | ||||||
|   If you convey a covered work, knowingly relying on a patent license, |  | ||||||
| and the Corresponding Source of the work is not available for anyone |  | ||||||
| to copy, free of charge and under the terms of this License, through a |  | ||||||
| publicly available network server or other readily accessible means, |  | ||||||
| then you must either (1) cause the Corresponding Source to be so |  | ||||||
| available, or (2) arrange to deprive yourself of the benefit of the |  | ||||||
| patent license for this particular work, or (3) arrange, in a manner |  | ||||||
| consistent with the requirements of this License, to extend the patent |  | ||||||
| license to downstream recipients.  "Knowingly relying" means you have |  | ||||||
| actual knowledge that, but for the patent license, your conveying the |  | ||||||
| covered work in a country, or your recipient's use of the covered work |  | ||||||
| in a country, would infringe one or more identifiable patents in that |  | ||||||
| country that you have reason to believe are valid. |  | ||||||
|  |  | ||||||
|   If, pursuant to or in connection with a single transaction or |  | ||||||
| arrangement, you convey, or propagate by procuring conveyance of, a |  | ||||||
| covered work, and grant a patent license to some of the parties |  | ||||||
| receiving the covered work authorizing them to use, propagate, modify |  | ||||||
| or convey a specific copy of the covered work, then the patent license |  | ||||||
| you grant is automatically extended to all recipients of the covered |  | ||||||
| work and works based on it. |  | ||||||
|  |  | ||||||
|   A patent license is "discriminatory" if it does not include within |  | ||||||
| the scope of its coverage, prohibits the exercise of, or is |  | ||||||
| conditioned on the non-exercise of one or more of the rights that are |  | ||||||
| specifically granted under this License.  You may not convey a covered |  | ||||||
| work if you are a party to an arrangement with a third party that is |  | ||||||
| in the business of distributing software, under which you make payment |  | ||||||
| to the third party based on the extent of your activity of conveying |  | ||||||
| the work, and under which the third party grants, to any of the |  | ||||||
| parties who would receive the covered work from you, a discriminatory |  | ||||||
| patent license (a) in connection with copies of the covered work |  | ||||||
| conveyed by you (or copies made from those copies), or (b) primarily |  | ||||||
| for and in connection with specific products or compilations that |  | ||||||
| contain the covered work, unless you entered into that arrangement, |  | ||||||
| or that patent license was granted, prior to 28 March 2007. |  | ||||||
|  |  | ||||||
|   Nothing in this License shall be construed as excluding or limiting |  | ||||||
| any implied license or other defenses to infringement that may |  | ||||||
| otherwise be available to you under applicable patent law. |  | ||||||
|  |  | ||||||
|   12. No Surrender of Others' Freedom. |  | ||||||
|  |  | ||||||
|   If conditions are imposed on you (whether by court order, agreement or |  | ||||||
| otherwise) that contradict the conditions of this License, they do not |  | ||||||
| excuse you from the conditions of this License.  If you cannot convey a |  | ||||||
| covered work so as to satisfy simultaneously your obligations under this |  | ||||||
| License and any other pertinent obligations, then as a consequence you may |  | ||||||
| not convey it at all.  For example, if you agree to terms that obligate you |  | ||||||
| to collect a royalty for further conveying from those to whom you convey |  | ||||||
| the Program, the only way you could satisfy both those terms and this |  | ||||||
| License would be to refrain entirely from conveying the Program. |  | ||||||
|  |  | ||||||
|   13. Use with the GNU Affero General Public License. |  | ||||||
|  |  | ||||||
|   Notwithstanding any other provision of this License, you have |  | ||||||
| permission to link or combine any covered work with a work licensed |  | ||||||
| under version 3 of the GNU Affero General Public License into a single |  | ||||||
| combined work, and to convey the resulting work.  The terms of this |  | ||||||
| License will continue to apply to the part which is the covered work, |  | ||||||
| but the special requirements of the GNU Affero General Public License, |  | ||||||
| section 13, concerning interaction through a network will apply to the |  | ||||||
| combination as such. |  | ||||||
|  |  | ||||||
|   14. Revised Versions of this License. |  | ||||||
|  |  | ||||||
|   The Free Software Foundation may publish revised and/or new versions of |  | ||||||
| the GNU General Public License from time to time.  Such new versions will |  | ||||||
| be similar in spirit to the present version, but may differ in detail to |  | ||||||
| address new problems or concerns. |  | ||||||
|  |  | ||||||
|   Each version is given a distinguishing version number.  If the |  | ||||||
| Program specifies that a certain numbered version of the GNU General |  | ||||||
| Public License "or any later version" applies to it, you have the |  | ||||||
| option of following the terms and conditions either of that numbered |  | ||||||
| version or of any later version published by the Free Software |  | ||||||
| Foundation.  If the Program does not specify a version number of the |  | ||||||
| GNU General Public License, you may choose any version ever published |  | ||||||
| by the Free Software Foundation. |  | ||||||
|  |  | ||||||
|   If the Program specifies that a proxy can decide which future |  | ||||||
| versions of the GNU General Public License can be used, that proxy's |  | ||||||
| public statement of acceptance of a version permanently authorizes you |  | ||||||
| to choose that version for the Program. |  | ||||||
|  |  | ||||||
|   Later license versions may give you additional or different |  | ||||||
| permissions.  However, no additional obligations are imposed on any |  | ||||||
| author or copyright holder as a result of your choosing to follow a |  | ||||||
| later version. |  | ||||||
|  |  | ||||||
|   15. Disclaimer of Warranty. |  | ||||||
|  |  | ||||||
|   THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |  | ||||||
| APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |  | ||||||
| HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |  | ||||||
| OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |  | ||||||
| THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |  | ||||||
| PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |  | ||||||
| IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |  | ||||||
| ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |  | ||||||
|  |  | ||||||
|   16. Limitation of Liability. |  | ||||||
|  |  | ||||||
|   IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |  | ||||||
| WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |  | ||||||
| THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |  | ||||||
| GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |  | ||||||
| USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |  | ||||||
| DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |  | ||||||
| PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |  | ||||||
| EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |  | ||||||
| SUCH DAMAGES. |  | ||||||
|  |  | ||||||
|   17. Interpretation of Sections 15 and 16. |  | ||||||
|  |  | ||||||
|   If the disclaimer of warranty and limitation of liability provided |  | ||||||
| above cannot be given local legal effect according to their terms, |  | ||||||
| reviewing courts shall apply local law that most closely approximates |  | ||||||
| an absolute waiver of all civil liability in connection with the |  | ||||||
| Program, unless a warranty or assumption of liability accompanies a |  | ||||||
| copy of the Program in return for a fee. |  | ||||||
|  |  | ||||||
|                      END OF TERMS AND CONDITIONS |  | ||||||
|  |  | ||||||
|             How to Apply These Terms to Your New Programs |  | ||||||
|  |  | ||||||
|   If you develop a new program, and you want it to be of the greatest |  | ||||||
| possible use to the public, the best way to achieve this is to make it |  | ||||||
| free software which everyone can redistribute and change under these terms. |  | ||||||
|  |  | ||||||
|   To do so, attach the following notices to the program.  It is safest |  | ||||||
| to attach them to the start of each source file to most effectively |  | ||||||
| state the exclusion of warranty; and each file should have at least |  | ||||||
| the "copyright" line and a pointer to where the full notice is found. |  | ||||||
|  |  | ||||||
|     <one line to give the program's name and a brief idea of what it does.> |  | ||||||
|     Copyright (C) <year>  <name of author> |  | ||||||
|  |  | ||||||
|     This program is free software: you can redistribute it and/or modify |  | ||||||
|     it under the terms of the GNU General Public License as published by |  | ||||||
|     the Free Software Foundation, either version 3 of the License, or |  | ||||||
|     (at your option) any later version. |  | ||||||
|  |  | ||||||
|     This program is distributed in the hope that it will be useful, |  | ||||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
|     GNU General Public License for more details. |  | ||||||
|  |  | ||||||
|     You should have received a copy of the GNU General Public License |  | ||||||
|     along with this program.  If not, see <https://www.gnu.org/licenses/>. |  | ||||||
|  |  | ||||||
| Also add information on how to contact you by electronic and paper mail. |  | ||||||
|  |  | ||||||
|   If the program does terminal interaction, make it output a short |  | ||||||
| notice like this when it starts in an interactive mode: |  | ||||||
|  |  | ||||||
|     <program>  Copyright (C) <year>  <name of author> |  | ||||||
|     This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |  | ||||||
|     This is free software, and you are welcome to redistribute it |  | ||||||
|     under certain conditions; type `show c' for details. |  | ||||||
|  |  | ||||||
| The hypothetical commands `show w' and `show c' should show the appropriate |  | ||||||
| parts of the General Public License.  Of course, your program's commands |  | ||||||
| might be different; for a GUI interface, you would use an "about box". |  | ||||||
|  |  | ||||||
|   You should also get your employer (if you work as a programmer) or school, |  | ||||||
| if any, to sign a "copyright disclaimer" for the program, if necessary. |  | ||||||
| For more information on this, and how to apply and follow the GNU GPL, see |  | ||||||
| <https://www.gnu.org/licenses/>. |  | ||||||
|  |  | ||||||
|   The GNU General Public License does not permit incorporating your program |  | ||||||
| into proprietary programs.  If your program is a subroutine library, you |  | ||||||
| may consider it more useful to permit linking proprietary applications with |  | ||||||
| the library.  If this is what you want to do, use the GNU Lesser General |  | ||||||
| Public License instead of this License.  But first, please read |  | ||||||
| <https://www.gnu.org/licenses/why-not-lgpl.html>. |  | ||||||
|  | |||||||
							
								
								
									
										39
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								Makefile
									
									
									
									
									
								
							| @ -1,40 +1,43 @@ | |||||||
| .SHELLFLAGS += -x -e | all: lint-fix lint coverage gen | ||||||
| PWD = $(shell pwd) |  | ||||||
|  |  | ||||||
| all: lint-fix lint test gen | test-full: | ||||||
|  | 	coverage run manage.py test --failfast -v 3 . | ||||||
|  | 	coverage html | ||||||
|  | 	coverage report | ||||||
|  |  | ||||||
| test-integration: | test-integration: | ||||||
| 	k3d cluster create || exit 0 | 	k3d cluster create || exit 0 | ||||||
| 	k3d kubeconfig write -o ~/.kube/config --overwrite | 	k3d kubeconfig write -o ~/.kube/config --overwrite | ||||||
| 	coverage run manage.py test -v 3 tests/integration | 	coverage run manage.py test --failfast -v 3 tests/integration | ||||||
|  |  | ||||||
| test-e2e: | test-e2e: | ||||||
| 	coverage run manage.py test --failfast -v 3 tests/e2e | 	coverage run manage.py test --failfast -v 3 tests/e2e | ||||||
|  |  | ||||||
| test: | coverage: | ||||||
| 	coverage run manage.py test -v 3 authentik | 	coverage run manage.py test --failfast -v 3 authentik | ||||||
| 	coverage html | 	coverage html | ||||||
| 	coverage report | 	coverage report | ||||||
|  |  | ||||||
| lint-fix: | lint-fix: | ||||||
| 	isort authentik tests lifecycle | 	isort -rc authentik tests lifecycle | ||||||
| 	black authentik tests lifecycle | 	black authentik tests lifecycle | ||||||
|  |  | ||||||
| lint: | lint: | ||||||
| 	pyright authentik tests lifecycle | 	pyright authentik tests lifecycle | ||||||
| 	bandit -r authentik tests lifecycle -x node_modules | 	bandit -r authentik tests lifecycle -x node_modules | ||||||
| 	pylint authentik tests lifecycle | 	pylint authentik tests lifecycle | ||||||
|  | 	prospector | ||||||
|  |  | ||||||
| gen: | gen: coverage | ||||||
| 	./manage.py generate_swagger -o swagger.yaml -f yaml | 	./manage.py generate_swagger -o swagger.yaml -f yaml | ||||||
| 	docker run \ |  | ||||||
| 		--rm -v ${PWD}:/local \ |  | ||||||
| 		openapitools/openapi-generator-cli generate \ |  | ||||||
| 		-i /local/swagger.yaml \ |  | ||||||
| 		-g typescript-fetch \ |  | ||||||
| 		-o /local/web/api \ |  | ||||||
| 		--additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0 |  | ||||||
| 	cd web/api && npx tsc |  | ||||||
|  |  | ||||||
| run: | local-stack: | ||||||
| 	go run -v cmd/server/main.go | 	export AUTHENTIK_TAG=testing | ||||||
|  | 	docker build -t beryju/authentik:testng . | ||||||
|  | 	docker-compose up -d | ||||||
|  | 	docker-compose run --rm server migrate | ||||||
|  |  | ||||||
|  | build-static: | ||||||
|  | 	docker-compose -f scripts/ci.docker-compose.yml up -d | ||||||
|  | 	docker build -t beryju/authentik-static -f static.Dockerfile --network=scripts_default . | ||||||
|  | 	docker-compose -f scripts/ci.docker-compose.yml down -v | ||||||
|  | |||||||
							
								
								
									
										40
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								Pipfile
									
									
									
									
									
								
							| @ -6,57 +6,59 @@ verify_ssl = true | |||||||
| [packages] | [packages] | ||||||
| boto3 = "*" | boto3 = "*" | ||||||
| celery = "*" | celery = "*" | ||||||
| channels = "*" |  | ||||||
| channels-redis = "*" |  | ||||||
| dacite = "*" |  | ||||||
| defusedxml = "*" | defusedxml = "*" | ||||||
| django = "*" | django = "*" | ||||||
| django-dbbackup = { git = 'https://github.com/django-dbbackup/django-dbbackup.git', ref = '9d1909c30a3271c8c9c8450add30d6e0b996e145' } | django-cors-middleware = "*" | ||||||
|  | django-dbbackup = "*" | ||||||
| django-filter = "*" | django-filter = "*" | ||||||
| django-guardian = "*" | django-guardian = "*" | ||||||
| django-model-utils = "*" | django-model-utils = "*" | ||||||
| django-otp = "*" | django-otp = "*" | ||||||
| django-prometheus = "*" | django-prometheus = "*" | ||||||
|  | django-recaptcha = "*" | ||||||
| django-redis = "*" | django-redis = "*" | ||||||
| django-storages = "*" |  | ||||||
| djangorestframework = "*" | djangorestframework = "*" | ||||||
|  | django-storages = "*" | ||||||
| djangorestframework-guardian = "*" | djangorestframework-guardian = "*" | ||||||
| docker = "*" | drf_yasg2 = "*" | ||||||
| drf_yasg = "*" |  | ||||||
| facebook-sdk = "*" | facebook-sdk = "*" | ||||||
| geoip2 = "*" |  | ||||||
| gunicorn = "*" |  | ||||||
| kubernetes = "*" |  | ||||||
| ldap3 = "*" | ldap3 = "*" | ||||||
| lxml = ">=4.6.3" | lxml = "*" | ||||||
| packaging = "*" | packaging = "*" | ||||||
| psycopg2-binary = "*" | psycopg2-binary = "*" | ||||||
| pycryptodome = "*" | pycryptodome = "*" | ||||||
| pyjwt = "*" | pyjwkest = "*" | ||||||
|  | uvicorn = {extras = ["standard"],version = "*"} | ||||||
|  | gunicorn = "*" | ||||||
| pyyaml = "*" | pyyaml = "*" | ||||||
|  | qrcode = "*" | ||||||
| requests-oauthlib = "*" | requests-oauthlib = "*" | ||||||
| sentry-sdk = "*" | sentry-sdk = "*" | ||||||
| service_identity = "*" | service_identity = "*" | ||||||
| structlog = "*" | structlog = "*" | ||||||
| swagger-spec-validator = "*" | swagger-spec-validator = "*" | ||||||
| twisted = "==20.3.0" |  | ||||||
| urllib3 = {extras = ["secure"],version = "*"} | urllib3 = {extras = ["secure"],version = "*"} | ||||||
| uvicorn = {extras = ["standard"],version = "*"} | dacite = "*" | ||||||
| webauthn = "*" | channels = "*" | ||||||
|  | channels-redis = "*" | ||||||
|  | kubernetes = "*" | ||||||
|  | docker = "*" | ||||||
| xmlsec = "*" | xmlsec = "*" | ||||||
|  |  | ||||||
| [requires] | [requires] | ||||||
| python_version = "3.9" | python_version = "3.9" | ||||||
|  |  | ||||||
| [dev-packages] | [dev-packages] | ||||||
|  | autopep8 = "*" | ||||||
| bandit = "*" | bandit = "*" | ||||||
| black = "==21.5b1" | black = "==20.8b1" | ||||||
| bump2version = "*" | bumpversion = "*" | ||||||
| colorama = "*" | colorama = "*" | ||||||
| coverage = "*" | coverage = "*" | ||||||
|  | django-debug-toolbar = "*" | ||||||
| pylint = "*" | pylint = "*" | ||||||
| pylint-django = "*" | pylint-django = "*" | ||||||
|  | selenium = "*" | ||||||
|  | prospector = "*" | ||||||
| pytest = "*" | pytest = "*" | ||||||
| pytest-django = "*" | pytest-django = "*" | ||||||
| selenium = "*" |  | ||||||
| requests-mock = "*" |  | ||||||
|  | |||||||
							
								
								
									
										1797
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1797
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										22
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								README.md
									
									
									
									
									
								
							| @ -1,17 +1,13 @@ | |||||||
| <p align="center"> | <img src="web/icons/icon_top_brand.svg" height="250" alt="authentik logo"> | ||||||
|     <img src="https://goauthentik.io/img/icon_top_brand_colour.svg" height="150" alt="authentik logo"> |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| [](https://discord.gg/jg33eMhnj6) | [](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1) | ||||||
| [](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=6) | [](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1) | ||||||
| [](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=6) | [](https://codecov.io/gh/BeryJu/authentik) | ||||||
| [](https://codecov.io/gh/goauthentik/authentik) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| [Transifex](https://www.transifex.com/beryjuorg/authentik/) |  | ||||||
|  |  | ||||||
| ## What is authentik? | ## What is authentik? | ||||||
|  |  | ||||||
| @ -25,14 +21,12 @@ For bigger setups, there is a Helm Chart in the `helm/` directory. This is docum | |||||||
|  |  | ||||||
| ## Screenshots | ## Screenshots | ||||||
|  |  | ||||||
| Light | Dark |  | ||||||
| --- | --- |  | ||||||
|  |  |  | ||||||
|  |  |  | ||||||
|  |  | ||||||
| ## Development | ## Development | ||||||
|  |  | ||||||
| See [Development Documentation](https://goauthentik.io/developer-docs/) | See [Development Documentation](https://goauthentik.io/docs/development/local-dev-environment) | ||||||
|  |  | ||||||
| ## Security | ## Security | ||||||
|  |  | ||||||
|  | |||||||
| @ -2,10 +2,13 @@ | |||||||
|  |  | ||||||
| ## Supported Versions | ## Supported Versions | ||||||
|  |  | ||||||
|  | As authentik is currently in a pre-stable, only the latest "stable" version is supported. After authentik 1.0, this will change. | ||||||
|  |  | ||||||
| | Version  | Supported          | | | Version  | Supported          | | ||||||
| | ---------- | ------------------ | | | -------- | ------------------ | | ||||||
| | 2021.4.x   | :white_check_mark: | | | 0.11.x   | :white_check_mark: | | ||||||
| | 2021.5.x   | :white_check_mark: | | | 0.12.x   | :white_check_mark: | | ||||||
|  | | 0.13.x   | :white_check_mark: | | ||||||
|  |  | ||||||
| ## Reporting a Vulnerability | ## Reporting a Vulnerability | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,3 +1,2 @@ | |||||||
| """authentik""" | """authentik""" | ||||||
| __version__ = "2021.5.4" | __version__ = "0.13.4-stable" | ||||||
| ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" |  | ||||||
|  | |||||||
| @ -1,31 +0,0 @@ | |||||||
| """Meta API""" |  | ||||||
| from drf_yasg.utils import swagger_auto_schema |  | ||||||
| from rest_framework.fields import CharField |  | ||||||
| from rest_framework.permissions import IsAdminUser |  | ||||||
| from rest_framework.request import Request |  | ||||||
| from rest_framework.response import Response |  | ||||||
| from rest_framework.viewsets import ViewSet |  | ||||||
|  |  | ||||||
| from authentik.core.api.utils import PassiveSerializer |  | ||||||
| from authentik.lib.utils.reflection import get_apps |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AppSerializer(PassiveSerializer): |  | ||||||
|     """Serialize Application info""" |  | ||||||
|  |  | ||||||
|     name = CharField() |  | ||||||
|     label = CharField() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AppsViewSet(ViewSet): |  | ||||||
|     """Read-only view set list all installed apps""" |  | ||||||
|  |  | ||||||
|     permission_classes = [IsAdminUser] |  | ||||||
|  |  | ||||||
|     @swagger_auto_schema(responses={200: AppSerializer(many=True)}) |  | ||||||
|     def list(self, request: Request) -> Response: |  | ||||||
|         """List current messages and pass into Serializer""" |  | ||||||
|         data = [] |  | ||||||
|         for app in sorted(get_apps(), key=lambda app: app.name): |  | ||||||
|             data.append({"name": app.name, "label": app.verbose_name}) |  | ||||||
|         return Response(AppSerializer(data, many=True).data) |  | ||||||
| @ -2,23 +2,25 @@ | |||||||
| import time | import time | ||||||
| from collections import Counter | from collections import Counter | ||||||
| from datetime import timedelta | from datetime import timedelta | ||||||
|  | from typing import Dict, List | ||||||
|  |  | ||||||
| from django.db.models import Count, ExpressionWrapper, F | from django.db.models import Count, ExpressionWrapper, F | ||||||
| from django.db.models.fields import DurationField | from django.db.models.fields import DurationField | ||||||
| from django.db.models.functions import ExtractHour | from django.db.models.functions import ExtractHour | ||||||
|  | from django.http import response | ||||||
| from django.utils.timezone import now | from django.utils.timezone import now | ||||||
| from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method | from drf_yasg2.utils import swagger_auto_schema | ||||||
| from rest_framework.fields import IntegerField, SerializerMethodField | from rest_framework.fields import SerializerMethodField | ||||||
| from rest_framework.permissions import IsAdminUser | from rest_framework.permissions import IsAdminUser | ||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
|  | from rest_framework.serializers import Serializer | ||||||
| from rest_framework.viewsets import ViewSet | from rest_framework.viewsets import ViewSet | ||||||
|  |  | ||||||
| from authentik.core.api.utils import PassiveSerializer | from authentik.audit.models import Event, EventAction | ||||||
| from authentik.events.models import Event, EventAction |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_events_per_1h(**filter_kwargs) -> list[dict[str, int]]: | def get_events_per_1h(**filter_kwargs) -> List[Dict[str, int]]: | ||||||
|     """Get event count by hour in the last day, fill with zeros""" |     """Get event count by hour in the last day, fill with zeros""" | ||||||
|     date_from = now() - timedelta(days=1) |     date_from = now() - timedelta(days=1) | ||||||
|     result = ( |     result = ( | ||||||
| @ -31,51 +33,47 @@ def get_events_per_1h(**filter_kwargs) -> list[dict[str, int]]: | |||||||
|         .annotate(count=Count("pk")) |         .annotate(count=Count("pk")) | ||||||
|         .order_by("age_hours") |         .order_by("age_hours") | ||||||
|     ) |     ) | ||||||
|     data = Counter({int(d["age_hours"]): d["count"] for d in result}) |     data = Counter({d["age_hours"]: d["count"] for d in result}) | ||||||
|     results = [] |     results = [] | ||||||
|     _now = now() |     _now = now() | ||||||
|     for hour in range(0, -24, -1): |     for hour in range(0, -24, -1): | ||||||
|         results.append( |         results.append( | ||||||
|             { |             { | ||||||
|                 "x_cord": time.mktime((_now + timedelta(hours=hour)).timetuple()) |                 "x": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000, | ||||||
|                 * 1000, |                 "y": data[hour * -1], | ||||||
|                 "y_cord": data[hour * -1], |  | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     return results |     return results | ||||||
|  |  | ||||||
|  |  | ||||||
| class CoordinateSerializer(PassiveSerializer): | class AdministrationMetricsSerializer(Serializer): | ||||||
|     """Coordinates for diagrams""" |  | ||||||
|  |  | ||||||
|     x_cord = IntegerField(read_only=True) |  | ||||||
|     y_cord = IntegerField(read_only=True) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class LoginMetricsSerializer(PassiveSerializer): |  | ||||||
|     """Login Metrics per 1h""" |     """Login Metrics per 1h""" | ||||||
|  |  | ||||||
|     logins_per_1h = SerializerMethodField() |     logins_per_1h = SerializerMethodField() | ||||||
|     logins_failed_per_1h = SerializerMethodField() |     logins_failed_per_1h = SerializerMethodField() | ||||||
|  |  | ||||||
|     @swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True)) |  | ||||||
|     def get_logins_per_1h(self, _): |     def get_logins_per_1h(self, _): | ||||||
|         """Get successful logins per hour for the last 24 hours""" |         """Get successful logins per hour for the last 24 hours""" | ||||||
|         return get_events_per_1h(action=EventAction.LOGIN) |         return get_events_per_1h(action=EventAction.LOGIN) | ||||||
|  |  | ||||||
|     @swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True)) |  | ||||||
|     def get_logins_failed_per_1h(self, _): |     def get_logins_failed_per_1h(self, _): | ||||||
|         """Get failed logins per hour for the last 24 hours""" |         """Get failed logins per hour for the last 24 hours""" | ||||||
|         return get_events_per_1h(action=EventAction.LOGIN_FAILED) |         return get_events_per_1h(action=EventAction.LOGIN_FAILED) | ||||||
|  |  | ||||||
|  |     def create(self, request: Request) -> response: | ||||||
|  |         raise NotImplementedError | ||||||
|  |  | ||||||
|  |     def update(self, request: Request) -> Response: | ||||||
|  |         raise NotImplementedError | ||||||
|  |  | ||||||
|  |  | ||||||
| class AdministrationMetricsViewSet(ViewSet): | class AdministrationMetricsViewSet(ViewSet): | ||||||
|     """Login Metrics per 1h""" |     """Login Metrics per 1h""" | ||||||
|  |  | ||||||
|     permission_classes = [IsAdminUser] |     permission_classes = [IsAdminUser] | ||||||
|  |  | ||||||
|     @swagger_auto_schema(responses={200: LoginMetricsSerializer(many=False)}) |     @swagger_auto_schema(responses={200: AdministrationMetricsSerializer(many=True)}) | ||||||
|     def list(self, request: Request) -> Response: |     def list(self, request: Request) -> Response: | ||||||
|         """Login Metrics per 1h""" |         """Login Metrics per 1h""" | ||||||
|         serializer = LoginMetricsSerializer(True) |         serializer = AdministrationMetricsSerializer(True) | ||||||
|         return Response(serializer.data) |         return Response(serializer.data) | ||||||
|  | |||||||
| @ -4,61 +4,45 @@ from importlib import import_module | |||||||
| from django.contrib import messages | from django.contrib import messages | ||||||
| from django.http.response import Http404 | from django.http.response import Http404 | ||||||
| from django.utils.translation import gettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
| from drf_yasg.utils import swagger_auto_schema | from drf_yasg2.utils import swagger_auto_schema | ||||||
| from rest_framework.decorators import action | from rest_framework.decorators import action | ||||||
| from rest_framework.fields import CharField, ChoiceField, DateTimeField, ListField | from rest_framework.fields import CharField, DateTimeField, IntegerField, ListField | ||||||
| from rest_framework.permissions import IsAdminUser | from rest_framework.permissions import IsAdminUser | ||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
|  | from rest_framework.serializers import Serializer | ||||||
| from rest_framework.viewsets import ViewSet | from rest_framework.viewsets import ViewSet | ||||||
|  |  | ||||||
| from authentik.core.api.utils import PassiveSerializer | from authentik.lib.tasks import TaskInfo | ||||||
| from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TaskSerializer(PassiveSerializer): | class TaskSerializer(Serializer): | ||||||
|     """Serialize TaskInfo and TaskResult""" |     """Serialize TaskInfo and TaskResult""" | ||||||
|  |  | ||||||
|     task_name = CharField() |     task_name = CharField() | ||||||
|     task_description = CharField() |     task_description = CharField() | ||||||
|     task_finish_timestamp = DateTimeField(source="finish_timestamp") |     task_finish_timestamp = DateTimeField(source="finish_timestamp") | ||||||
|  |  | ||||||
|     status = ChoiceField( |     status = IntegerField(source="result.status.value") | ||||||
|         source="result.status.name", |  | ||||||
|         choices=[(x.name, x.name) for x in TaskResultStatus], |  | ||||||
|     ) |  | ||||||
|     messages = ListField(source="result.messages") |     messages = ListField(source="result.messages") | ||||||
|  |  | ||||||
|  |     def create(self, request: Request) -> Response: | ||||||
|  |         raise NotImplementedError | ||||||
|  |  | ||||||
|  |     def update(self, request: Request) -> Response: | ||||||
|  |         raise NotImplementedError | ||||||
|  |  | ||||||
|  |  | ||||||
| class TaskViewSet(ViewSet): | class TaskViewSet(ViewSet): | ||||||
|     """Read-only view set that returns all background tasks""" |     """Read-only view set that returns all background tasks""" | ||||||
|  |  | ||||||
|     permission_classes = [IsAdminUser] |     permission_classes = [IsAdminUser] | ||||||
|  |  | ||||||
|     @swagger_auto_schema( |  | ||||||
|         responses={200: TaskSerializer(many=False), 404: "Task not found"} |  | ||||||
|     ) |  | ||||||
|     # pylint: disable=invalid-name |  | ||||||
|     def retrieve(self, request: Request, pk=None) -> Response: |  | ||||||
|         """Get a single system task""" |  | ||||||
|         task = TaskInfo.by_name(pk) |  | ||||||
|         if not task: |  | ||||||
|             raise Http404 |  | ||||||
|         return Response(TaskSerializer(task, many=False).data) |  | ||||||
|  |  | ||||||
|     @swagger_auto_schema(responses={200: TaskSerializer(many=True)}) |     @swagger_auto_schema(responses={200: TaskSerializer(many=True)}) | ||||||
|     def list(self, request: Request) -> Response: |     def list(self, request: Request) -> Response: | ||||||
|         """List system tasks""" |         """List current messages and pass into Serializer""" | ||||||
|         tasks = sorted(TaskInfo.all().values(), key=lambda task: task.task_name) |         return Response(TaskSerializer(TaskInfo.all().values(), many=True).data) | ||||||
|         return Response(TaskSerializer(tasks, many=True).data) |  | ||||||
|  |  | ||||||
|     @swagger_auto_schema( |  | ||||||
|         responses={ |  | ||||||
|             204: "Task retried successfully", |  | ||||||
|             404: "Task not found", |  | ||||||
|             500: "Failed to retry task", |  | ||||||
|         } |  | ||||||
|     ) |  | ||||||
|     @action(detail=True, methods=["post"]) |     @action(detail=True, methods=["post"]) | ||||||
|     # pylint: disable=invalid-name |     # pylint: disable=invalid-name | ||||||
|     def retry(self, request: Request, pk=None) -> Response: |     def retry(self, request: Request, pk=None) -> Response: | ||||||
| @ -77,8 +61,12 @@ class TaskViewSet(ViewSet): | |||||||
|                     % {"name": task.task_name} |                     % {"name": task.task_name} | ||||||
|                 ), |                 ), | ||||||
|             ) |             ) | ||||||
|             return Response(status=204) |             return Response( | ||||||
|  |                 { | ||||||
|  |                     "successful": True, | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|         except ImportError:  # pragma: no cover |         except ImportError:  # pragma: no cover | ||||||
|             # if we get an import error, the module path has probably changed |             # if we get an import error, the module path has probably changed | ||||||
|             task.delete() |             task.delete() | ||||||
|             return Response(status=500) |             return Response({"successful": False}) | ||||||
|  | |||||||
| @ -1,33 +1,26 @@ | |||||||
| """authentik administration overview""" | """authentik administration overview""" | ||||||
| from os import environ |  | ||||||
|  |  | ||||||
| from django.core.cache import cache | from django.core.cache import cache | ||||||
| from drf_yasg.utils import swagger_auto_schema | from drf_yasg2.utils import swagger_auto_schema | ||||||
| from packaging.version import parse | from packaging.version import parse | ||||||
| from rest_framework.fields import SerializerMethodField | from rest_framework.fields import SerializerMethodField | ||||||
| from rest_framework.mixins import ListModelMixin | from rest_framework.mixins import ListModelMixin | ||||||
| from rest_framework.permissions import IsAuthenticated | from rest_framework.permissions import IsAdminUser | ||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
|  | from rest_framework.serializers import Serializer | ||||||
| from rest_framework.viewsets import GenericViewSet | from rest_framework.viewsets import GenericViewSet | ||||||
|  |  | ||||||
| from authentik import ENV_GIT_HASH_KEY, __version__ | from authentik import __version__ | ||||||
| from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version | from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version | ||||||
| from authentik.core.api.utils import PassiveSerializer |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class VersionSerializer(PassiveSerializer): | class VersionSerializer(Serializer): | ||||||
|     """Get running and latest version.""" |     """Get running and latest version.""" | ||||||
|  |  | ||||||
|     version_current = SerializerMethodField() |     version_current = SerializerMethodField() | ||||||
|     version_latest = SerializerMethodField() |     version_latest = SerializerMethodField() | ||||||
|     build_hash = SerializerMethodField() |  | ||||||
|     outdated = SerializerMethodField() |     outdated = SerializerMethodField() | ||||||
|  |  | ||||||
|     def get_build_hash(self, _) -> str: |  | ||||||
|         """Get build hash, if version is not latest or released""" |  | ||||||
|         return environ.get(ENV_GIT_HASH_KEY, "") |  | ||||||
|  |  | ||||||
|     def get_version_current(self, _) -> str: |     def get_version_current(self, _) -> str: | ||||||
|         """Get current version""" |         """Get current version""" | ||||||
|         return __version__ |         return __version__ | ||||||
| @ -46,18 +39,22 @@ class VersionSerializer(PassiveSerializer): | |||||||
|             self.get_version_latest(instance) |             self.get_version_latest(instance) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |     def create(self, request: Request) -> Response: | ||||||
|  |         raise NotImplementedError | ||||||
|  |  | ||||||
|  |     def update(self, request: Request) -> Response: | ||||||
|  |         raise NotImplementedError | ||||||
|  |  | ||||||
|  |  | ||||||
| class VersionViewSet(ListModelMixin, GenericViewSet): | class VersionViewSet(ListModelMixin, GenericViewSet): | ||||||
|     """Get running and latest version.""" |     """Get running and latest version.""" | ||||||
|  |  | ||||||
|     permission_classes = [IsAuthenticated] |     permission_classes = [IsAdminUser] | ||||||
|     pagination_class = None |  | ||||||
|     filter_backends = [] |  | ||||||
|  |  | ||||||
|     def get_queryset(self):  # pragma: no cover |     def get_queryset(self): | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     @swagger_auto_schema(responses={200: VersionSerializer(many=False)}) |     @swagger_auto_schema(responses={200: VersionSerializer(many=True)}) | ||||||
|     def list(self, request: Request) -> Response: |     def list(self, request: Request) -> Response: | ||||||
|         """Get running and latest version.""" |         """Get running and latest version.""" | ||||||
|         return Response(VersionSerializer(True).data) |         return Response(VersionSerializer(True).data) | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ class WorkerViewSet(ListModelMixin, GenericViewSet): | |||||||
|     serializer_class = Serializer |     serializer_class = Serializer | ||||||
|     permission_classes = [IsAdminUser] |     permission_classes = [IsAdminUser] | ||||||
|  |  | ||||||
|     def get_queryset(self):  # pragma: no cover |     def get_queryset(self): | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     def list(self, request: Request) -> Response: |     def list(self, request: Request) -> Response: | ||||||
|  | |||||||
| @ -7,4 +7,5 @@ class AuthentikAdminConfig(AppConfig): | |||||||
|  |  | ||||||
|     name = "authentik.admin" |     name = "authentik.admin" | ||||||
|     label = "authentik_admin" |     label = "authentik_admin" | ||||||
|  |     mountpoint = "administration/" | ||||||
|     verbose_name = "authentik Admin" |     verbose_name = "authentik Admin" | ||||||
|  | |||||||
							
								
								
									
										107
									
								
								authentik/admin/fields.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								authentik/admin/fields.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | |||||||
|  | """Additional fields""" | ||||||
|  | import yaml | ||||||
|  | from django import forms | ||||||
|  | from django.utils.datastructures import MultiValueDict | ||||||
|  | from django.utils.translation import gettext_lazy as _ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ArrayFieldSelectMultiple(forms.SelectMultiple): | ||||||
|  |     """This is a Form Widget for use with a Postgres ArrayField. It implements | ||||||
|  |     a multi-select interface that can be given a set of `choices`. | ||||||
|  |     You can provide a `delimiter` keyword argument to specify the delimeter used. | ||||||
|  |  | ||||||
|  |     https://gist.github.com/stephane/00e73c0002de52b1c601""" | ||||||
|  |  | ||||||
|  |     def __init__(self, *args, **kwargs): | ||||||
|  |         # Accept a `delimiter` argument, and grab it (defaulting to a comma) | ||||||
|  |         self.delimiter = kwargs.pop("delimiter", ",") | ||||||
|  |         super().__init__(*args, **kwargs) | ||||||
|  |  | ||||||
|  |     def value_from_datadict(self, data, files, name): | ||||||
|  |         if isinstance(data, MultiValueDict): | ||||||
|  |             # Normally, we'd want a list here, which is what we get from the | ||||||
|  |             # SelectMultiple superclass, but the SimpleArrayField expects to | ||||||
|  |             # get a delimited string, so we're doing a little extra work. | ||||||
|  |             return self.delimiter.join(data.getlist(name)) | ||||||
|  |  | ||||||
|  |         return data.get(name) | ||||||
|  |  | ||||||
|  |     def get_context(self, name, value, attrs): | ||||||
|  |         return super().get_context(name, value.split(self.delimiter), attrs) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CodeMirrorWidget(forms.Textarea): | ||||||
|  |     """Custom Textarea-based Widget that triggers a CodeMirror editor""" | ||||||
|  |  | ||||||
|  |     # CodeMirror mode to enable | ||||||
|  |     mode: str | ||||||
|  |  | ||||||
|  |     template_name = "fields/codemirror.html" | ||||||
|  |  | ||||||
|  |     def __init__(self, *args, mode="yaml", **kwargs): | ||||||
|  |         super().__init__(*args, **kwargs) | ||||||
|  |         self.mode = mode | ||||||
|  |  | ||||||
|  |     def render(self, *args, **kwargs): | ||||||
|  |         attrs = kwargs.setdefault("attrs", {}) | ||||||
|  |         attrs["mode"] = self.mode | ||||||
|  |         return super().render(*args, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class InvalidYAMLInput(str): | ||||||
|  |     """Invalid YAML String type""" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class YAMLString(str): | ||||||
|  |     """YAML String type""" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class YAMLField(forms.JSONField): | ||||||
|  |     """Django's JSON Field converted to YAML""" | ||||||
|  |  | ||||||
|  |     default_error_messages = { | ||||||
|  |         "invalid": _("'%(value)s' value must be valid YAML."), | ||||||
|  |     } | ||||||
|  |     widget = forms.Textarea | ||||||
|  |  | ||||||
|  |     def to_python(self, value): | ||||||
|  |         if self.disabled: | ||||||
|  |             return value | ||||||
|  |         if value in self.empty_values: | ||||||
|  |             return None | ||||||
|  |         if isinstance(value, (list, dict, int, float, YAMLString)): | ||||||
|  |             return value | ||||||
|  |         try: | ||||||
|  |             converted = yaml.safe_load(value) | ||||||
|  |         except yaml.YAMLError: | ||||||
|  |             raise forms.ValidationError( | ||||||
|  |                 self.error_messages["invalid"], | ||||||
|  |                 code="invalid", | ||||||
|  |                 params={"value": value}, | ||||||
|  |             ) | ||||||
|  |         if isinstance(converted, str): | ||||||
|  |             return YAMLString(converted) | ||||||
|  |         if converted is None: | ||||||
|  |             return {} | ||||||
|  |         return converted | ||||||
|  |  | ||||||
|  |     def bound_data(self, data, initial): | ||||||
|  |         if self.disabled: | ||||||
|  |             return initial | ||||||
|  |         try: | ||||||
|  |             return yaml.safe_load(data) | ||||||
|  |         except yaml.YAMLError: | ||||||
|  |             return InvalidYAMLInput(data) | ||||||
|  |  | ||||||
|  |     def prepare_value(self, value): | ||||||
|  |         if isinstance(value, InvalidYAMLInput): | ||||||
|  |             return value | ||||||
|  |         return yaml.dump(value, explicit_start=True, default_flow_style=False) | ||||||
|  |  | ||||||
|  |     def has_changed(self, initial, data): | ||||||
|  |         if super().has_changed(initial, data): | ||||||
|  |             return True | ||||||
|  |         # For purposes of seeing whether something has changed, True isn't the | ||||||
|  |         # same as 1 and the order of keys doesn't matter. | ||||||
|  |         data = self.to_python(data) | ||||||
|  |         return yaml.dump(initial, sort_keys=True) != yaml.dump(data, sort_keys=True) | ||||||
							
								
								
									
										18
									
								
								authentik/admin/forms/overview.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								authentik/admin/forms/overview.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | """Forms for modals on overview page""" | ||||||
|  | from django import forms | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PolicyCacheClearForm(forms.Form): | ||||||
|  |     """Form to clear Policy cache""" | ||||||
|  |  | ||||||
|  |     title = "Clear Policy cache" | ||||||
|  |     body = """Are you sure you want to clear the policy cache? | ||||||
|  |     This will cause all policies to be re-evaluated on their next usage.""" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FlowCacheClearForm(forms.Form): | ||||||
|  |     """Form to clear Flow cache""" | ||||||
|  |  | ||||||
|  |     title = "Clear Flow cache" | ||||||
|  |     body = """Are you sure you want to clear the flow cache? | ||||||
|  |     This will cause all flows to be re-evaluated on their next usage.""" | ||||||
							
								
								
									
										12
									
								
								authentik/admin/forms/policies.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								authentik/admin/forms/policies.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | """authentik administration forms""" | ||||||
|  | from django import forms | ||||||
|  |  | ||||||
|  | from authentik.admin.fields import CodeMirrorWidget, YAMLField | ||||||
|  | from authentik.core.models import User | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PolicyTestForm(forms.Form): | ||||||
|  |     """Form to test policies against user""" | ||||||
|  |  | ||||||
|  |     user = forms.ModelChoiceField(queryset=User.objects.all()) | ||||||
|  |     context = YAMLField(widget=CodeMirrorWidget(), required=False, initial=dict) | ||||||
							
								
								
									
										17
									
								
								authentik/admin/forms/source.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								authentik/admin/forms/source.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | """authentik core source form fields""" | ||||||
|  |  | ||||||
|  | SOURCE_FORM_FIELDS = [ | ||||||
|  |     "name", | ||||||
|  |     "slug", | ||||||
|  |     "enabled", | ||||||
|  |     "authentication_flow", | ||||||
|  |     "enrollment_flow", | ||||||
|  | ] | ||||||
|  | SOURCE_SERIALIZER_FIELDS = [ | ||||||
|  |     "pk", | ||||||
|  |     "name", | ||||||
|  |     "slug", | ||||||
|  |     "enabled", | ||||||
|  |     "authentication_flow", | ||||||
|  |     "enrollment_flow", | ||||||
|  | ] | ||||||
							
								
								
									
										22
									
								
								authentik/admin/forms/users.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								authentik/admin/forms/users.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | """authentik administrative user forms""" | ||||||
|  |  | ||||||
|  | from django import forms | ||||||
|  |  | ||||||
|  | from authentik.admin.fields import CodeMirrorWidget, YAMLField | ||||||
|  | from authentik.core.models import User | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserForm(forms.ModelForm): | ||||||
|  |     """Update User Details""" | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |  | ||||||
|  |         model = User | ||||||
|  |         fields = ["username", "name", "email", "is_active", "attributes"] | ||||||
|  |         widgets = { | ||||||
|  |             "name": forms.TextInput, | ||||||
|  |             "attributes": CodeMirrorWidget, | ||||||
|  |         } | ||||||
|  |         field_classes = { | ||||||
|  |             "attributes": YAMLField, | ||||||
|  |         } | ||||||
							
								
								
									
										9
									
								
								authentik/admin/mixins.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								authentik/admin/mixins.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | """authentik admin mixins""" | ||||||
|  | from django.contrib.auth.mixins import UserPassesTestMixin | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AdminRequiredMixin(UserPassesTestMixin): | ||||||
|  |     """Make sure user is administrator""" | ||||||
|  |  | ||||||
|  |     def test_func(self): | ||||||
|  |         return self.request.user.is_superuser | ||||||
| @ -4,7 +4,7 @@ from celery.schedules import crontab | |||||||
| CELERY_BEAT_SCHEDULE = { | CELERY_BEAT_SCHEDULE = { | ||||||
|     "admin_latest_version": { |     "admin_latest_version": { | ||||||
|         "task": "authentik.admin.tasks.update_latest_version", |         "task": "authentik.admin.tasks.update_latest_version", | ||||||
|         "schedule": crontab(minute="*/60"),  # Run every hour |         "schedule": crontab(minute=0),  # Run every hour | ||||||
|         "options": {"queue": "authentik_scheduled"}, |         "options": {"queue": "authentik_scheduled"}, | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,55 +1,30 @@ | |||||||
| """authentik admin tasks""" | """authentik admin tasks""" | ||||||
| import re |  | ||||||
|  |  | ||||||
| from django.core.cache import cache | from django.core.cache import cache | ||||||
| from django.core.validators import URLValidator |  | ||||||
| from packaging.version import parse |  | ||||||
| from requests import RequestException, get | from requests import RequestException, get | ||||||
| from structlog.stdlib import get_logger | from structlog import get_logger | ||||||
|  |  | ||||||
| from authentik import __version__ | from authentik.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus | ||||||
| from authentik.events.models import Event, EventAction |  | ||||||
| from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus |  | ||||||
| from authentik.root.celery import CELERY_APP | from authentik.root.celery import CELERY_APP | ||||||
|  |  | ||||||
| LOGGER = get_logger() | LOGGER = get_logger() | ||||||
| VERSION_CACHE_KEY = "authentik_latest_version" | VERSION_CACHE_KEY = "authentik_latest_version" | ||||||
| VERSION_CACHE_TIMEOUT = 8 * 60 * 60  # 8 hours | VERSION_CACHE_TIMEOUT = 2 * 60 * 60  # 2 hours | ||||||
| # Chop of the first ^ because we want to search the entire string |  | ||||||
| URL_FINDER = URLValidator.regex.pattern[1:] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @CELERY_APP.task(bind=True, base=MonitoredTask) | @CELERY_APP.task(bind=True, base=MonitoredTask) | ||||||
| def update_latest_version(self: MonitoredTask): | def update_latest_version(self: MonitoredTask): | ||||||
|     """Update latest version info""" |     """Update latest version info""" | ||||||
|     try: |     try: | ||||||
|         response = get( |         response = get("https://api.github.com/repos/beryju/authentik/releases/latest") | ||||||
|             "https://api.github.com/repos/goauthentik/authentik/releases/latest" |  | ||||||
|         ) |  | ||||||
|         response.raise_for_status() |         response.raise_for_status() | ||||||
|         data = response.json() |         data = response.json() | ||||||
|         tag_name = data.get("tag_name") |         tag_name = data.get("tag_name") | ||||||
|         upstream_version = tag_name.split("/")[1] |         cache.set(VERSION_CACHE_KEY, tag_name.split("/")[1], VERSION_CACHE_TIMEOUT) | ||||||
|         cache.set(VERSION_CACHE_KEY, upstream_version, VERSION_CACHE_TIMEOUT) |  | ||||||
|         self.set_status( |         self.set_status( | ||||||
|             TaskResult( |             TaskResult( | ||||||
|                 TaskResultStatus.SUCCESSFUL, ["Successfully updated latest Version"] |                 TaskResultStatus.SUCCESSFUL, ["Successfully updated latest Version"] | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|         # Check if upstream version is newer than what we're running, |  | ||||||
|         # and if no event exists yet, create one. |  | ||||||
|         local_version = parse(__version__) |  | ||||||
|         if local_version < parse(upstream_version): |  | ||||||
|             # Event has already been created, don't create duplicate |  | ||||||
|             if Event.objects.filter( |  | ||||||
|                 action=EventAction.UPDATE_AVAILABLE, |  | ||||||
|                 context__new_version=upstream_version, |  | ||||||
|             ).exists(): |  | ||||||
|                 return |  | ||||||
|             event_dict = {"new_version": upstream_version} |  | ||||||
|             if match := re.search(URL_FINDER, data.get("body", "")): |  | ||||||
|                 event_dict["message"] = f"Changelog: {match.group()}" |  | ||||||
|             Event.new(EventAction.UPDATE_AVAILABLE, **event_dict).save() |  | ||||||
|     except (RequestException, IndexError) as exc: |     except (RequestException, IndexError) as exc: | ||||||
|         cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT) |         cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT) | ||||||
|         self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc)) |         self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc)) | ||||||
|  | |||||||
							
								
								
									
										131
									
								
								authentik/admin/templates/administration/application/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								authentik/admin/templates/administration/application/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-applications"></i> | ||||||
|  |             {% trans 'Applications' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "External Applications which use authentik as Identity-Provider, utilizing protocols like OAuth2 and SAML." %}</p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-modal-button href="{% url 'authentik_admin:application-create' %}"> | ||||||
|  |                         <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                             {% trans 'Create' %} | ||||||
|  |                         </ak-spinner-button> | ||||||
|  |                         <div slot="modal"></div> | ||||||
|  |                     </ak-modal-button> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader"></th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Slug' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Provider' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Provider Type' %}</th> | ||||||
|  |                     <th role="columnheader"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for application in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <td role="cell" {% if application.meta_icon %} style="vertical-align: bottom;" {% endif %}> | ||||||
|  |                         {% if application.meta_icon %} | ||||||
|  |                         <img class="app-icon pf-c-avatar" src="{{ application.meta_icon.url }}" alt="{% trans 'Application Icon' %}"> | ||||||
|  |                         {% else %} | ||||||
|  |                         <i class="pf-icon pf-icon-arrow"></i> | ||||||
|  |                         {% endif %} | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <a href="/applications/{{ application.slug }}/"> | ||||||
|  |                             <div> | ||||||
|  |                                 {{ application.name }} | ||||||
|  |                             </div> | ||||||
|  |                             {% if application.meta_publisher %} | ||||||
|  |                             <small>{{ application.meta_publisher }}</small> | ||||||
|  |                             {% endif %} | ||||||
|  |                         </a> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <code>{{ application.slug }}</span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ application.get_provider }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ application.get_provider|verbose_name }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:application-update' pk=application.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                 {% trans 'Edit' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:application-delete' pk=application.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                 {% trans 'Delete' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="pf-icon pf-icon-applications pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Applications.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any application." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no applications exist. Click the button below to create one.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-modal-button href="{% url 'authentik_admin:application-create' %}"> | ||||||
|  |                     <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                         {% trans 'Create' %} | ||||||
|  |                     </ak-spinner-button> | ||||||
|  |                     <div slot="modal"></div> | ||||||
|  |                 </ak-modal-button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										5
									
								
								authentik/admin/templates/administration/base.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								authentik/admin/templates/administration/base.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | {% load static %} | ||||||
|  | {% load i18n %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | {% endblock %} | ||||||
| @ -0,0 +1,116 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-key"></i> | ||||||
|  |             {% trans 'Certificate-Key Pairs' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Import certificates of external providers or create certificates to sign requests with." %}</p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-modal-button href="{% url 'authentik_admin:certificatekeypair-create' %}"> | ||||||
|  |                         <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                             {% trans 'Create' %} | ||||||
|  |                         </ak-spinner-button> | ||||||
|  |                         <div slot="modal"></div> | ||||||
|  |                     </ak-modal-button> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Private Key available' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Fingerprint' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for kp in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader"> | ||||||
|  |                         <div> | ||||||
|  |                             <div>{{ kp.name }}</div> | ||||||
|  |                         </div> | ||||||
|  |                     </th> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {% if kp.key_data is not None %} | ||||||
|  |                             {% trans 'Yes' %} | ||||||
|  |                             {% else %} | ||||||
|  |                             {% trans 'No' %} | ||||||
|  |                             {% endif %} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <code>{{ kp.fingerprint }}</code> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:certificatekeypair-update' pk=kp.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                 {% trans 'Edit' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:certificatekeypair-delete' pk=kp.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                 {% trans 'Delete' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="pf-icon pf-icon-key pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Certificates.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any certificates." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no certificates exist. Click the button below to create one.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-modal-button href="{% url 'authentik_admin:certificatekeypair-create' %}"> | ||||||
|  |                     <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                         {% trans 'Create' %} | ||||||
|  |                     </ak-spinner-button> | ||||||
|  |                     <div slot="modal"></div> | ||||||
|  |                 </ak-modal-button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										13
									
								
								authentik/admin/templates/administration/flow/import.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								authentik/admin/templates/administration/flow/import.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | {% extends base_template|default:"generic/form.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  |  | ||||||
|  | {% block above_form %} | ||||||
|  | <h1> | ||||||
|  | {% trans 'Import Flow' %} | ||||||
|  | </h1> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block action %} | ||||||
|  | {% trans 'Import Flow' %} | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										135
									
								
								authentik/admin/templates/administration/flow/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								authentik/admin/templates/administration/flow/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,135 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-process-automation"></i> | ||||||
|  |             {% trans 'Flows' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Flows describe a chain of Stages to authenticate, enroll or recover a user. Stages are chosen based on policies applied to them." %}</p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-modal-button href="{% url 'authentik_admin:flow-create' %}"> | ||||||
|  |                         <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                             {% trans 'Create' %} | ||||||
|  |                         </ak-spinner-button> | ||||||
|  |                         <div slot="modal"></div> | ||||||
|  |                     </ak-modal-button> | ||||||
|  |                     <ak-modal-button href="{% url 'authentik_admin:flow-import' %}"> | ||||||
|  |                         <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                             {% trans 'Import' %} | ||||||
|  |                         </ak-spinner-button> | ||||||
|  |                         <div slot="modal"></div> | ||||||
|  |                     </ak-modal-button> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Identifier' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Designation' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Stages' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Policies' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for flow in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader"> | ||||||
|  |                         <a href="/flows/{{ flow.slug }}/"> | ||||||
|  |                             <div><code>{{ flow.slug }}</code></div> | ||||||
|  |                             <small>{{ flow.name }}</small> | ||||||
|  |                         </a> | ||||||
|  |                     </th> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ flow.designation }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ flow.stages.all|length }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ flow.policies.all|length }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:flow-update' pk=flow.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                 {% trans 'Edit' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:flow-delete' pk=flow.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                 {% trans 'Delete' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <a class="pf-c-button pf-m-secondary ak-root-link" href="{% url 'authentik_admin:flow-execute' pk=flow.pk %}?next={{ request.get_full_path }}">{% trans 'Execute' %}</a> | ||||||
|  |                         <a class="pf-c-button pf-m-secondary ak-root-link" href="{% url 'authentik_admin:flow-export' pk=flow.pk %}?next={{ request.get_full_path }}">{% trans 'Export' %}</a> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="pf-icon pf-icon-process-automation pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Flows.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any flows." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no flows exist. Click the button below to create one.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-modal-button href="{% url 'authentik_admin:flow-create' %}"> | ||||||
|  |                     <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                         {% trans 'Create' %} | ||||||
|  |                     </ak-spinner-button> | ||||||
|  |                     <div slot="modal"></div> | ||||||
|  |                 </ak-modal-button> | ||||||
|  |                 <ak-modal-button href="{% url 'authentik_admin:flow-import' %}"> | ||||||
|  |                     <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                         {% trans 'Import' %} | ||||||
|  |                     </ak-spinner-button> | ||||||
|  |                     <div slot="modal"></div> | ||||||
|  |                 </ak-modal-button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										114
									
								
								authentik/admin/templates/administration/group/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								authentik/admin/templates/administration/group/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,114 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-users"></i> | ||||||
|  |             {% trans 'Groups' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Group users together and give them permissions based on the membership." %} | ||||||
|  |         </p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-modal-button href="{% url 'authentik_admin:group-create' %}"> | ||||||
|  |                         <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                             {% trans 'Create' %} | ||||||
|  |                         </ak-spinner-button> | ||||||
|  |                         <div slot="modal"></div> | ||||||
|  |                     </ak-modal-button> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Parent' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Members' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for group in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ group.name }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ group.parent }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ group.users.all|length }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:group-update' pk=group.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                 {% trans 'Edit' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:group-delete' pk=group.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                 {% trans 'Delete' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="pf-icon pf-icon-users pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Groups.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any groups." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no group exist. Click the button below to create one.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-modal-button href="{% url 'authentik_admin:group-create' %}"> | ||||||
|  |                     <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                         {% trans 'Create' %} | ||||||
|  |                     </ak-spinner-button> | ||||||
|  |                     <div slot="modal"></div> | ||||||
|  |                 </ak-modal-button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										149
									
								
								authentik/admin/templates/administration/outpost/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								authentik/admin/templates/administration/outpost/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,149 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load humanize %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  | {% load admin_reflection %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-zone"></i> | ||||||
|  |             {% trans 'Outposts' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Outposts are deployments of authentik components to support different environments and protocols, like reverse proxies." %}</p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-modal-button href="{% url 'authentik_admin:outpost-create' %}"> | ||||||
|  |                         <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                             {% trans 'Create' %} | ||||||
|  |                         </ak-spinner-button> | ||||||
|  |                         <div slot="modal"></div> | ||||||
|  |                     </ak-modal-button> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Providers' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Health' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Version' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for outpost in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader"> | ||||||
|  |                         <span>{{ outpost.name }}</span> | ||||||
|  |                     </th> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ outpost.providers.all.select_subclasses|join:", " }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     {% with states=outpost.state %} | ||||||
|  |                     {% if states|length > 0 %} | ||||||
|  |                         <td role="cell"> | ||||||
|  |                             {% for state in states %} | ||||||
|  |                             <div> | ||||||
|  |                                 {% if state.last_seen %} | ||||||
|  |                                 <i class="fas fa-check pf-m-success"></i> {{ state.last_seen|naturaltime }} | ||||||
|  |                                 {% else %} | ||||||
|  |                                 <i class="fas fa-times pf-m-danger"></i> {% trans 'Unhealthy' %} | ||||||
|  |                                 {% endif %} | ||||||
|  |                             </div> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </td> | ||||||
|  |                         <td role="cell"> | ||||||
|  |                             {% for state in states %} | ||||||
|  |                                 <div> | ||||||
|  |                                     {% if not state.version %} | ||||||
|  |                                     <i class="fas fa-question-circle"></i> | ||||||
|  |                                     {% elif state.version_outdated %} | ||||||
|  |                                     <i class="fas fa-times pf-m-danger"></i> {% blocktrans with is=state.version should=state.version_should %}{{ is }}, should be {{ should }}{% endblocktrans %} | ||||||
|  |                                     {% else %} | ||||||
|  |                                     <i class="fas fa-check pf-m-success"></i> {{ state.version }} | ||||||
|  |                                     {% endif %} | ||||||
|  |                                 </div> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </td> | ||||||
|  |                     {% else %} | ||||||
|  |                         <td role="cell"> | ||||||
|  |                             <i class="fas fa-question-circle"></i> | ||||||
|  |                         </td> | ||||||
|  |                         <td role="cell"> | ||||||
|  |                             <i class="fas fa-question-circle"></i> | ||||||
|  |                         </td> | ||||||
|  |                     {% endif %} | ||||||
|  |                     {% endwith %} | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:outpost-update' pk=outpost.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                 {% trans 'Edit' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:outpost-delete' pk=outpost.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                 {% trans 'Delete' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         {% get_htmls outpost as htmls %} | ||||||
|  |                         {% for html in htmls %} | ||||||
|  |                         {{ html|safe }} | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="fas fa-map-marker pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Outposts.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any outposts." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no outposts exist. Click the button below to create one.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-modal-button href="{% url 'authentik_admin:outpost-create' %}"> | ||||||
|  |                     <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                         {% trans 'Create' %} | ||||||
|  |                     </ak-spinner-button> | ||||||
|  |                     <div slot="modal"></div> | ||||||
|  |                 </ak-modal-button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
| @ -0,0 +1,154 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load humanize %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  | {% load admin_reflection %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon-integration"></i> | ||||||
|  |             {% trans 'Outpost Service-Connections' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Outpost Service-Connections define how authentik connects to external platforms to manage and deploy Outposts." %}</p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-dropdown class="pf-c-dropdown"> | ||||||
|  |                         <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> | ||||||
|  |                             <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> | ||||||
|  |                             <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> | ||||||
|  |                         </button> | ||||||
|  |                         <ul class="pf-c-dropdown__menu" hidden> | ||||||
|  |                             {% for type, name in types.items %} | ||||||
|  |                             <li> | ||||||
|  |                                 <ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-create' %}?type={{ type }}"> | ||||||
|  |                                     <button slot="trigger" class="pf-c-dropdown__menu-item"> | ||||||
|  |                                         {{ name|verbose_name }}<br> | ||||||
|  |                                         <small> | ||||||
|  |                                             {{ name|doc }} | ||||||
|  |                                         </small> | ||||||
|  |                                     </button> | ||||||
|  |                                     <div slot="modal"></div> | ||||||
|  |                                 </ak-modal-button> | ||||||
|  |                             </li> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </ul> | ||||||
|  |                     </ak-dropdown> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Type' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Local?' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Status' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for sc in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader"> | ||||||
|  |                         <span>{{ sc.name }}</span> | ||||||
|  |                     </th> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ sc|verbose_name }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ sc.local|yesno:"Yes,No" }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {% if sc.state.healthy %} | ||||||
|  |                             <i class="fas fa-check pf-m-success"></i> {{ sc.state.version }} | ||||||
|  |                             {% else %} | ||||||
|  |                             <i class="fas fa-times pf-m-danger"></i> {% trans 'Unhealthy' %} | ||||||
|  |                             {% endif %} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-update' pk=sc.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                 {% trans 'Edit' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-delete' pk=sc.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                 {% trans 'Delete' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="fas fa-map-marker pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Outpost Service Connections.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any outposts." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no service connections exist. Click the button below to create one.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-dropdown class="pf-c-dropdown"> | ||||||
|  |                     <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> | ||||||
|  |                         <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> | ||||||
|  |                         <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> | ||||||
|  |                     </button> | ||||||
|  |                     <ul class="pf-c-dropdown__menu" hidden> | ||||||
|  |                         {% for type, name in types.items %} | ||||||
|  |                         <li> | ||||||
|  |                             <ak-modal-button href="{% url 'authentik_admin:outpost-service-connection-create' %}?type={{ type }}"> | ||||||
|  |                                 <button slot="trigger" class="pf-c-dropdown__menu-item"> | ||||||
|  |                                     {{ name|verbose_name }}<br> | ||||||
|  |                                     <small> | ||||||
|  |                                         {{ name|doc }} | ||||||
|  |                                     </small> | ||||||
|  |                                 </button> | ||||||
|  |                                 <div slot="modal"></div> | ||||||
|  |                             </ak-modal-button> | ||||||
|  |                         </li> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </ul> | ||||||
|  |                 </ak-dropdown> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										148
									
								
								authentik/admin/templates/administration/policy/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								authentik/admin/templates/administration/policy/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,148 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-infrastructure"></i> | ||||||
|  |             {% trans 'Policies' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages." %}</p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-dropdown class="pf-c-dropdown"> | ||||||
|  |                         <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> | ||||||
|  |                             <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> | ||||||
|  |                             <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> | ||||||
|  |                         </button> | ||||||
|  |                         <ul class="pf-c-dropdown__menu" hidden> | ||||||
|  |                             {% for type, name in types.items %} | ||||||
|  |                             <li> | ||||||
|  |                                 <ak-modal-button href="{% url 'authentik_admin:policy-create' %}?type={{ type }}"> | ||||||
|  |                                     <button slot="trigger" class="pf-c-dropdown__menu-item"> | ||||||
|  |                                         {{ name|verbose_name }}<br> | ||||||
|  |                                         <small> | ||||||
|  |                                             {{ name|doc }} | ||||||
|  |                                         </small> | ||||||
|  |                                     </button> | ||||||
|  |                                     <div slot="modal"></div> | ||||||
|  |                                 </ak-modal-button> | ||||||
|  |                             </li> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </ul> | ||||||
|  |                     </ak-dropdown> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Type' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for policy in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader"> | ||||||
|  |                         <div> | ||||||
|  |                             <div>{{ policy.name }}</div> | ||||||
|  |                             {% if not policy.bindings.exists and not policy.promptstage_set.exists %} | ||||||
|  |                             <i class="pf-icon pf-icon-warning-triangle"></i> | ||||||
|  |                             <small>{% trans 'Warning: Policy is not assigned.' %}</small> | ||||||
|  |                             {% else %} | ||||||
|  |                             <i class="pf-icon pf-icon-ok"></i> | ||||||
|  |                             <small>{% blocktrans with object_count=policy.bindings.all|length %}Assigned to {{ object_count }} objects.{% endblocktrans %}</small> | ||||||
|  |                             {% endif %} | ||||||
|  |                         </div> | ||||||
|  |                     </th> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ policy|verbose_name }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:policy-update' pk=policy.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                 {% trans 'Edit' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:policy-test' pk=policy.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                 {% trans 'Test' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:policy-delete' pk=policy.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                 {% trans 'Delete' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="pf-icon pf-icon-infrastructure pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Policies.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any policies." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no policies exist. Click the button below to create one.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-dropdown class="pf-c-dropdown"> | ||||||
|  |                     <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> | ||||||
|  |                         <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> | ||||||
|  |                         <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> | ||||||
|  |                     </button> | ||||||
|  |                     <ul class="pf-c-dropdown__menu" hidden> | ||||||
|  |                         {% for type, name in types.items %} | ||||||
|  |                         <li> | ||||||
|  |                             <ak-modal-button href="{% url 'authentik_admin:policy-create' %}?type={{ type }}"> | ||||||
|  |                                 <button slot="trigger" class="pf-c-dropdown__menu-item"> | ||||||
|  |                                     {{ name|verbose_name }}<br> | ||||||
|  |                                     <small> | ||||||
|  |                                         {{ name|doc }} | ||||||
|  |                                     </small> | ||||||
|  |                                 </button> | ||||||
|  |                                 <div slot="modal"></div> | ||||||
|  |                             </ak-modal-button> | ||||||
|  |                         </li> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </ul> | ||||||
|  |                 </ak-dropdown> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										11
									
								
								authentik/admin/templates/administration/policy/test.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								authentik/admin/templates/administration/policy/test.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | {% extends 'generic/form.html' %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  |  | ||||||
|  | {% block above_form %} | ||||||
|  | <h1>{% blocktrans with policy=policy %}Test policy {{ policy }}{% endblocktrans %}</h1> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block action %} | ||||||
|  | {% trans 'Test' %} | ||||||
|  | {% endblock %} | ||||||
| @ -0,0 +1,119 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-infrastructure"></i> | ||||||
|  |             {% trans 'Policy Bindings' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Bind existing Policies to Models accepting policies." %}</p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-modal-button href="{% url 'authentik_admin:policy-binding-create' %}"> | ||||||
|  |                         <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                             {% trans 'Create' %} | ||||||
|  |                         </ak-spinner-button> | ||||||
|  |                         <div slot="modal"></div> | ||||||
|  |                     </ak-modal-button> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Policy' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Enabled' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Order' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Timeout' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for pbm in object_list %} | ||||||
|  |                     <tr role="role"> | ||||||
|  |                         <td> | ||||||
|  |                             {{ pbm }} | ||||||
|  |                             <small> | ||||||
|  |                                 {{ pbm|fieldtype }} | ||||||
|  |                             </small> | ||||||
|  |                         </td> | ||||||
|  |                         <td></td> | ||||||
|  |                         <td></td> | ||||||
|  |                         <td></td> | ||||||
|  |                         <td></td> | ||||||
|  |                     </tr> | ||||||
|  |                     {% for binding in pbm.bindings %} | ||||||
|  |                     <tr class="row pf-c-table__expandable-row pf-m-expanded"> | ||||||
|  |                         <th role="cell"> | ||||||
|  |                             <div>{{ binding.policy }}</div> | ||||||
|  |                             <small> | ||||||
|  |                                 {{ binding.policy|fieldtype }} | ||||||
|  |                             </small> | ||||||
|  |                         </th> | ||||||
|  |                         <th role="cell"> | ||||||
|  |                             <div>{{ binding.enabled }}</div> | ||||||
|  |                         </th> | ||||||
|  |                         <th role="cell"> | ||||||
|  |                             <div>{{ binding.order }}</div> | ||||||
|  |                         </th> | ||||||
|  |                         <th role="cell"> | ||||||
|  |                             <div>{{ binding.timeout }}</div> | ||||||
|  |                         </th> | ||||||
|  |                         <td> | ||||||
|  |                             <ak-modal-button href="{% url 'authentik_admin:policy-binding-update' pk=binding.pk %}"> | ||||||
|  |                                 <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                     {% trans 'Edit' %} | ||||||
|  |                                 </ak-spinner-button> | ||||||
|  |                                 <div slot="modal"></div> | ||||||
|  |                             </ak-modal-button> | ||||||
|  |                             <ak-modal-button href="{% url 'authentik_admin:policy-binding-delete' pk=binding.pk %}"> | ||||||
|  |                                 <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                     {% trans 'Delete' %} | ||||||
|  |                                 </ak-spinner-button> | ||||||
|  |                                 <div slot="modal"></div> | ||||||
|  |                             </ak-modal-button> | ||||||
|  |                         </td> | ||||||
|  |                     </tr> | ||||||
|  |                     {% endfor %} | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Policy Bindings.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                     {% trans 'Currently no policy bindings exist. Click the button below to create one.' %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-modal-button href="{% url 'authentik_admin:policy-binding-create' %}"> | ||||||
|  |                     <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                         {% trans 'Create' %} | ||||||
|  |                     </ak-spinner-button> | ||||||
|  |                     <div slot="modal"></div> | ||||||
|  |                 </ak-modal-button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
| @ -0,0 +1,139 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-blueprint"></i> | ||||||
|  |             {% trans 'Property Mappings' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Control how authentik exposes and interprets information." %} | ||||||
|  |         </p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-dropdown class="pf-c-dropdown"> | ||||||
|  |                         <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> | ||||||
|  |                             <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> | ||||||
|  |                             <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> | ||||||
|  |                         </button> | ||||||
|  |                         <ul class="pf-c-dropdown__menu" hidden> | ||||||
|  |                             {% for type, name in types.items %} | ||||||
|  |                             <li> | ||||||
|  |                                 <ak-modal-button href="{% url 'authentik_admin:property-mapping-create' %}?type={{ type }}"> | ||||||
|  |                                     <button slot="trigger" class="pf-c-dropdown__menu-item"> | ||||||
|  |                                         {{ name|verbose_name }}<br> | ||||||
|  |                                         <small> | ||||||
|  |                                             {{ name|doc }} | ||||||
|  |                                         </small> | ||||||
|  |                                     </button> | ||||||
|  |                                     <div slot="modal"></div> | ||||||
|  |                                 </ak-modal-button> | ||||||
|  |                             </li> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </ul> | ||||||
|  |                     </ak-dropdown> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Type' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for property_mapping in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ property_mapping.name }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ property_mapping|verbose_name }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:property-mapping-update' pk=property_mapping.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                 {% trans 'Edit' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:property-mapping-delete' pk=property_mapping.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                 {% trans 'Delete' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="pf-icon pf-icon-blueprint pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Property Mappings.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any property mappings." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no property mappings exist. Click the button below to create one.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-dropdown class="pf-c-dropdown"> | ||||||
|  |                     <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> | ||||||
|  |                         <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> | ||||||
|  |                         <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> | ||||||
|  |                     </button> | ||||||
|  |                     <ul class="pf-c-dropdown__menu" hidden> | ||||||
|  |                         {% for type, name in types.items %} | ||||||
|  |                         <li> | ||||||
|  |                             <ak-modal-button href="{% url 'authentik_admin:property-mapping-create' %}?type={{ type }}"> | ||||||
|  |                                 <button slot="trigger" class="pf-c-dropdown__menu-item"> | ||||||
|  |                                     {{ name|verbose_name }}<br> | ||||||
|  |                                     <small> | ||||||
|  |                                         {{ name|doc }} | ||||||
|  |                                     </small> | ||||||
|  |                                 </button> | ||||||
|  |                                 <div slot="modal"></div> | ||||||
|  |                             </ak-modal-button> | ||||||
|  |                         </li> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </ul> | ||||||
|  |                 </ak-dropdown> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										159
									
								
								authentik/admin/templates/administration/provider/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								authentik/admin/templates/administration/provider/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,159 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  | {% load admin_reflection %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-integration"></i> | ||||||
|  |             {% trans 'Providers' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Provide support for protocols like SAML and OAuth to assigned applications." %} | ||||||
|  |     </p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-dropdown class="pf-c-dropdown"> | ||||||
|  |                         <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> | ||||||
|  |                             <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> | ||||||
|  |                             <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> | ||||||
|  |                         </button> | ||||||
|  |                         <ul class="pf-c-dropdown__menu" hidden> | ||||||
|  |                             {% for type, name in types.items %} | ||||||
|  |                             <li> | ||||||
|  |                                 <ak-modal-button href="{% url 'authentik_admin:provider-create' %}?type={{ type }}"> | ||||||
|  |                                     <button slot="trigger" class="pf-c-dropdown__menu-item"> | ||||||
|  |                                         {{ name|verbose_name }}<br> | ||||||
|  |                                         <small> | ||||||
|  |                                             {{ name|doc }} | ||||||
|  |                                         </small> | ||||||
|  |                                     </button> | ||||||
|  |                                     <div slot="modal"></div> | ||||||
|  |                                 </ak-modal-button> | ||||||
|  |                             </li> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </ul> | ||||||
|  |                     </ak-dropdown> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Type' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for provider in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader"> | ||||||
|  |                         <div> | ||||||
|  |                             <div>{{ provider.name }}</div> | ||||||
|  |                             {% if not provider.application %} | ||||||
|  |                             <i class="pf-icon pf-icon-warning-triangle"></i> | ||||||
|  |                             <small>{% trans 'Warning: Provider not assigned to any application.' %}</small> | ||||||
|  |                             {% else %} | ||||||
|  |                             <i class="pf-icon pf-icon-ok"></i> | ||||||
|  |                             <small> | ||||||
|  |                                 {% blocktrans with app=provider.application %} | ||||||
|  |                                     Assigned to application {{ app }}. | ||||||
|  |                                 {% endblocktrans %} | ||||||
|  |                             </small> | ||||||
|  |                             {% endif %} | ||||||
|  |                         </div> | ||||||
|  |                     </th> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ provider|verbose_name }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:provider-update' pk=provider.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                 {% trans 'Edit' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:provider-delete' pk=provider.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                 {% trans 'Delete' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         {% get_links provider as links %} | ||||||
|  |                         {% for name, href in links.items %} | ||||||
|  |                             <a class="pf-c-button pf-m-tertiary ak-root-link" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a> | ||||||
|  |                         {% endfor %} | ||||||
|  |                         {% get_htmls provider as htmls %} | ||||||
|  |                         {% for html in htmls %} | ||||||
|  |                             {{ html|safe }} | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="pf-icon-integration pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Providers.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any providers." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no providers exist. Click the button below to create one.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-dropdown class="pf-c-dropdown"> | ||||||
|  |                     <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> | ||||||
|  |                         <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> | ||||||
|  |                         <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> | ||||||
|  |                     </button> | ||||||
|  |                     <ul class="pf-c-dropdown__menu" hidden> | ||||||
|  |                         {% for type, name in types.items %} | ||||||
|  |                         <li> | ||||||
|  |                             <ak-modal-button href="{% url 'authentik_admin:provider-create' %}?type={{ type }}"> | ||||||
|  |                                 <button slot="trigger" class="pf-c-dropdown__menu-item"> | ||||||
|  |                                     {{ name|verbose_name }}<br> | ||||||
|  |                                     <small> | ||||||
|  |                                         {{ name|doc }} | ||||||
|  |                                     </small> | ||||||
|  |                                 </button> | ||||||
|  |                                 <div slot="modal"></div> | ||||||
|  |                             </ak-modal-button> | ||||||
|  |                         </li> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </ul> | ||||||
|  |                 </ak-dropdown> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										153
									
								
								authentik/admin/templates/administration/source/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								authentik/admin/templates/administration/source/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,153 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  | {% load admin_reflection %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-middleware"></i> | ||||||
|  |             {% trans 'Source' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "External Sources which can be used to get Identities into authentik, for example Social Providers like Twiter and GitHub or Enterprise Providers like ADFS and LDAP." %} | ||||||
|  |         </p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-dropdown class="pf-c-dropdown"> | ||||||
|  |                         <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> | ||||||
|  |                             <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> | ||||||
|  |                             <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> | ||||||
|  |                         </button> | ||||||
|  |                         <ul class="pf-c-dropdown__menu" hidden> | ||||||
|  |                             {% for type, name in types.items %} | ||||||
|  |                             <li> | ||||||
|  |                                 <ak-modal-button href="{% url 'authentik_admin:source-create' %}?type={{ type }}"> | ||||||
|  |                                     <button slot="trigger" class="pf-c-dropdown__menu-item"> | ||||||
|  |                                         {{ name|verbose_name }}<br> | ||||||
|  |                                         <small> | ||||||
|  |                                             {{ name|doc }} | ||||||
|  |                                         </small> | ||||||
|  |                                     </button> | ||||||
|  |                                     <div slot="modal"></div> | ||||||
|  |                                 </ak-modal-button> | ||||||
|  |                             </li> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </ul> | ||||||
|  |                     </ak-dropdown> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Type' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Additional Info' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for source in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader"> | ||||||
|  |                         <a href="/sources/{{ source.slug }}/"> | ||||||
|  |                             <div>{{ source.name }}</div> | ||||||
|  |                             {% if not source.enabled %} | ||||||
|  |                             <small>{% trans 'Disabled' %}</small> | ||||||
|  |                             {% endif %} | ||||||
|  |                         </a> | ||||||
|  |                     </th> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ source|fieldtype }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ source.ui_additional_info|default:""|safe }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:source-update' pk=source.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                 {% trans 'Edit' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:source-delete' pk=source.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                 {% trans 'Delete' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         {% get_links source as links %} | ||||||
|  |                         {% for name, href in links %} | ||||||
|  |                             <a class="pf-c-button pf-m-tertiary ak-root-link" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="pf-icon pf-icon-middleware pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Sources.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any sources." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no sources exist. Click the button below to create one.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-dropdown class="pf-c-dropdown"> | ||||||
|  |                     <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> | ||||||
|  |                         <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> | ||||||
|  |                         <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> | ||||||
|  |                     </button> | ||||||
|  |                     <ul class="pf-c-dropdown__menu" hidden> | ||||||
|  |                         {% for type, name in types.items %} | ||||||
|  |                         <li> | ||||||
|  |                             <ak-modal-button href="{% url 'authentik_admin:source-create' %}?type={{ type }}"> | ||||||
|  |                                 <button slot="trigger" class="pf-c-dropdown__menu-item"> | ||||||
|  |                                     {{ name|verbose_name }}<br> | ||||||
|  |                                     <small> | ||||||
|  |                                         {{ name|doc }} | ||||||
|  |                                     </small> | ||||||
|  |                                 </button> | ||||||
|  |                                 <div slot="modal"></div> | ||||||
|  |                             </ak-modal-button> | ||||||
|  |                         </li> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </ul> | ||||||
|  |                 </ak-dropdown> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										148
									
								
								authentik/admin/templates/administration/stage/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								authentik/admin/templates/administration/stage/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,148 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  | {% load admin_reflection %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-plugged"></i> | ||||||
|  |             {% trans 'Stages' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Stages are single steps of a Flow that a user is guided through." %}</p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-dropdown class="pf-c-dropdown"> | ||||||
|  |                         <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> | ||||||
|  |                             <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> | ||||||
|  |                             <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> | ||||||
|  |                         </button> | ||||||
|  |                         <ul class="pf-c-dropdown__menu" hidden> | ||||||
|  |                             {% for type, name in types.items %} | ||||||
|  |                             <li> | ||||||
|  |                                 <ak-modal-button href="{% url 'authentik_admin:stage-create' %}?type={{ type }}"> | ||||||
|  |                                     <button slot="trigger" class="pf-c-dropdown__menu-item"> | ||||||
|  |                                         {{ name|verbose_name }}<br> | ||||||
|  |                                         <small> | ||||||
|  |                                             {{ name|doc }} | ||||||
|  |                                         </small> | ||||||
|  |                                     </button> | ||||||
|  |                                     <div slot="modal"></div> | ||||||
|  |                                 </ak-modal-button> | ||||||
|  |                             </li> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </ul> | ||||||
|  |                     </ak-dropdown> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Flows' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for stage in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader"> | ||||||
|  |                         <div> | ||||||
|  |                             <div>{{ stage.name }}</div> | ||||||
|  |                             <small>{{ stage|verbose_name }}</small> | ||||||
|  |                         </div> | ||||||
|  |                     </th> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <ul> | ||||||
|  |                             {% for flow in stage.flow_set.all %} | ||||||
|  |                             <li>{{ flow.slug }}<</li> | ||||||
|  |                             {% empty %} | ||||||
|  |                             <li>-</li> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </ul> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:stage-update' pk=stage.stage_uuid %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                 {% trans 'Edit' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:stage-delete' pk=stage.stage_uuid %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                 {% trans 'Delete' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         {% get_links stage as links %} | ||||||
|  |                         {% for name, href in links.items %} | ||||||
|  |                         <a class="pf-c-button pf-m-tertiary ak-root-link" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="pf-icon pf-icon-plugged pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Stages.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any stages." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no stages exist. Click the button below to create one.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-dropdown class="pf-c-dropdown"> | ||||||
|  |                     <button class="pf-m-primary pf-c-dropdown__toggle" type="button"> | ||||||
|  |                         <span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span> | ||||||
|  |                         <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> | ||||||
|  |                     </button> | ||||||
|  |                     <ul class="pf-c-dropdown__menu" hidden> | ||||||
|  |                         {% for type, name in types.items %} | ||||||
|  |                         <li> | ||||||
|  |                             <ak-modal-button href="{% url 'authentik_admin:stage-create' %}?type={{ type }}"> | ||||||
|  |                                 <button slot="trigger" class="pf-c-dropdown__menu-item"> | ||||||
|  |                                     {{ name|verbose_name }}<br> | ||||||
|  |                                     <small> | ||||||
|  |                                         {{ name|doc }} | ||||||
|  |                                     </small> | ||||||
|  |                                 </button> | ||||||
|  |                                 <div slot="modal"></div> | ||||||
|  |                             </ak-modal-button> | ||||||
|  |                         </li> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </ul> | ||||||
|  |                 </ak-dropdown> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										125
									
								
								authentik/admin/templates/administration/stage_binding/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								authentik/admin/templates/administration/stage_binding/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,125 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-infrastructure"></i> | ||||||
|  |             {% trans 'Stage Bindings' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Bind existing Stages to Flows." %}</p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-modal-button href="{% url 'authentik_admin:stage-binding-create' %}"> | ||||||
|  |                         <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                             {% trans 'Create' %} | ||||||
|  |                         </ak-spinner-button> | ||||||
|  |                         <div slot="modal"></div> | ||||||
|  |                     </ak-modal-button> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Order' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Stage Type' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% regroup object_list by target as grouped_bindings %} | ||||||
|  |                 {% for flow in grouped_bindings %} | ||||||
|  |                     <tr role="role"> | ||||||
|  |                         <td> | ||||||
|  |                             {% blocktrans with slug=flow.grouper.slug %} | ||||||
|  |                             Flow {{ slug }} | ||||||
|  |                             {% endblocktrans %} | ||||||
|  |                         </td> | ||||||
|  |                         <td></td> | ||||||
|  |                         <td></td> | ||||||
|  |                         <td></td> | ||||||
|  |                     </tr> | ||||||
|  |                     {% for binding in flow.list %} | ||||||
|  |                     <tr class="pf-c-table__expandable-row pf-m-expanded" role="row"> | ||||||
|  |                         <td role="cell"> | ||||||
|  |                             <span> | ||||||
|  |                                 {{ binding.order }} | ||||||
|  |                             </span> | ||||||
|  |                         </td> | ||||||
|  |                         <th role="columnheader"> | ||||||
|  |                             <div> | ||||||
|  |                                 <div>{{ binding.target.slug }}</div> | ||||||
|  |                                 <small> | ||||||
|  |                                     {{ binding.target.name }} | ||||||
|  |                                 </small> | ||||||
|  |                             </div> | ||||||
|  |                         </th> | ||||||
|  |                         <td role="cell"> | ||||||
|  |                             <div> | ||||||
|  |                                 <div> | ||||||
|  |                                     {{ binding.stage.name }} | ||||||
|  |                                 </div> | ||||||
|  |                                 <small> | ||||||
|  |                                     {{ binding.stage }} | ||||||
|  |                                 </small> | ||||||
|  |                             </div> | ||||||
|  |                         </td> | ||||||
|  |                         <td> | ||||||
|  |                             <ak-modal-button href="{% url 'authentik_admin:stage-binding-update' pk=binding.pk %}"> | ||||||
|  |                                 <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                     {% trans 'Update' %} | ||||||
|  |                                 </ak-spinner-button> | ||||||
|  |                                 <div slot="modal"></div> | ||||||
|  |                             </ak-modal-button> | ||||||
|  |                             <ak-modal-button href="{% url 'authentik_admin:stage-binding-delete' pk=binding.pk %}"> | ||||||
|  |                                 <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                     {% trans 'Delete' %} | ||||||
|  |                                 </ak-spinner-button> | ||||||
|  |                                 <div slot="modal"></div> | ||||||
|  |                             </ak-modal-button> | ||||||
|  |                         </td> | ||||||
|  |                     </tr> | ||||||
|  |                     {% endfor %} | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Flow-Stage Bindings.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                     {% trans 'Currently no flow-stage bindings exist. Click the button below to create one.' %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-modal-button href="{% url 'authentik_admin:stage-binding-create' %}"> | ||||||
|  |                     <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                         {% trans 'Create' %} | ||||||
|  |                     </ak-spinner-button> | ||||||
|  |                     <div slot="modal"></div> | ||||||
|  |                 </ak-modal-button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
| @ -0,0 +1,103 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-migration"></i> | ||||||
|  |             {% trans 'Invitations' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Create Invitation Links to enroll Users, and optionally force specific attributes of their account." %} | ||||||
|  |         </p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-modal-button href="{% url 'authentik_admin:stage-invitation-create' %}"> | ||||||
|  |                         <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                             {% trans 'Create' %} | ||||||
|  |                         </ak-spinner-button> | ||||||
|  |                         <div slot="modal"></div> | ||||||
|  |                     </ak-modal-button> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Expiry' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Link' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for invitation in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ invitation.expiry }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ invitation.Link }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:stage-invitation-delete' pk=invitation.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                 {% trans 'Delete' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="pf-icon pf-icon-migration pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Invitations.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any invitations." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no invitations exist. Click the button below to create one.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-modal-button href="{% url 'authentik_admin:stage-invitation-create' %}"> | ||||||
|  |                     <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                         {% trans 'Create' %} | ||||||
|  |                     </ak-spinner-button> | ||||||
|  |                     <div slot="modal"></div> | ||||||
|  |                 </ak-modal-button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										130
									
								
								authentik/admin/templates/administration/stage_prompt/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								authentik/admin/templates/administration/stage_prompt/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,130 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  | {% load admin_reflection %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-plugged"></i> | ||||||
|  |             {% trans 'Prompts' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Single Prompts that can be used for Prompt Stages." %}</p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-modal-button href="{% url 'authentik_admin:stage-prompt-create' %}"> | ||||||
|  |                         <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                             {% trans 'Create' %} | ||||||
|  |                         </ak-spinner-button> | ||||||
|  |                         <div slot="modal"></div> | ||||||
|  |                     </ak-modal-button> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Field' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Label' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Type' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Order' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Flows' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for prompt in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader"> | ||||||
|  |                         <div> | ||||||
|  |                             <div>{{ prompt.field_key }}</div> | ||||||
|  |                         </div> | ||||||
|  |                     </th> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <div> | ||||||
|  |                             {{ prompt.label }} | ||||||
|  |                         </div> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <div> | ||||||
|  |                             {{ prompt.type }} | ||||||
|  |                         </div> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <div> | ||||||
|  |                             {{ prompt.order }} | ||||||
|  |                         </div> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <ul> | ||||||
|  |                             {% for flow in prompt.flow_set.all %} | ||||||
|  |                             <li>{{ flow.slug }}</li> | ||||||
|  |                             {% empty %} | ||||||
|  |                             <li>-</li> | ||||||
|  |                             {% endfor %} | ||||||
|  |                         </ul> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:stage-prompt-update' pk=prompt.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                 {% trans 'Update' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:stage-prompt-delete' pk=prompt.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                 {% trans 'Delete' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         {% get_links prompt as links %} | ||||||
|  |                         {% for name, href in links.items %} | ||||||
|  |                         <a class="pf-c-button pf-m-tertiary ak-root-link" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="pf-icon pf-icon-plugged pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Stage Prompts.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any stage prompts." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no stage prompts exist. Click the button below to create one.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                 <a href="{% url 'authentik_admin:stage-prompt-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										84
									
								
								authentik/admin/templates/administration/task/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								authentik/admin/templates/administration/task/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,84 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load humanize %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-automation"></i> | ||||||
|  |             {% trans 'System Tasks' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Long-running operations which authentik executes in the background." %}</p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                     {% trans 'Refresh' %} | ||||||
|  |                 </button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Identifier' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Description' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Last Run' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Status' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Messages' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for task in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader"> | ||||||
|  |                         <pre>{{ task.task_name }}</pre> | ||||||
|  |                     </th> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ task.task_description }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ task.finish_timestamp|naturaltime }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {% if task.result.status == task_successful %} | ||||||
|  |                             <i class="fas fa-check pf-m-success"></i> {% trans 'Successful' %} | ||||||
|  |                             {% elif task.result.status == task_warning %} | ||||||
|  |                             <i class="fas fa-exclamation-triangle pf-m-warning"></i> {% trans 'Warning' %} | ||||||
|  |                             {% elif task.result.status == task_error %} | ||||||
|  |                             <i class="fas fa-times pf-m-danger"></i> {% trans 'Error' %} | ||||||
|  |                             {% else %} | ||||||
|  |                             <i class="fas fa-question-circle"></i> {% trans 'Unknown' %} | ||||||
|  |                             {% endif %} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         {% for message in task.result.messages %} | ||||||
|  |                         <div> | ||||||
|  |                             {{ message }} | ||||||
|  |                         </div> | ||||||
|  |                         {% endfor %} | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-action-button url="{% url 'authentik_api:admin_system_tasks-retry' pk=task.task_name %}"> | ||||||
|  |                             {% trans 'Retry Task' %} | ||||||
|  |                         </ak-action-button> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										102
									
								
								authentik/admin/templates/administration/token/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								authentik/admin/templates/administration/token/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-security"></i> | ||||||
|  |             {% trans 'Tokens' %} | ||||||
|  |         </h1> | ||||||
|  |         <p>{% trans "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access." %}</p> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Identifier' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'User' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Expires?' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Expiry Date' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for token in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader"> | ||||||
|  |                         <div>{{ token.identifier }}</div> | ||||||
|  |                     </th> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ token.user }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ token.expiring|yesno:"Yes,No" }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {% if not token.expiring %} | ||||||
|  |                             - | ||||||
|  |                             {% else %} | ||||||
|  |                             {{ token.expires }} | ||||||
|  |                             {% endif %} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:token-delete' pk=token.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                                 {% trans 'Delete' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         <ak-token-copy-button identifier="{{ token.identifier }}"> | ||||||
|  |                             {% trans 'Copy token' %} | ||||||
|  |                         </ak-token-copy-button> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="fas fa-key pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Tokens.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any token." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no tokens exist.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										42
									
								
								authentik/admin/templates/administration/user/disable.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								authentik/admin/templates/administration/user/disable.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         {% block above_form %} | ||||||
|  |         <h1> | ||||||
|  |             {% blocktrans with object_type=object|verbose_name %} | ||||||
|  |             Disable {{ object_type }} | ||||||
|  |             {% endblocktrans %} | ||||||
|  |         </h1> | ||||||
|  |         {% endblock %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section"> | ||||||
|  |     <div class="pf-l-stack"> | ||||||
|  |         <div class="pf-l-stack__item"> | ||||||
|  |             <div class="pf-c-card"> | ||||||
|  |                 <div class="pf-c-card__body"> | ||||||
|  |                     <form action="" method="post" class="pf-c-form"> | ||||||
|  |                         {% csrf_token %} | ||||||
|  |                         <p> | ||||||
|  |                             {% blocktrans with object_type=object|verbose_name name=object %} | ||||||
|  |                             Are you sure you want to disable {{ object_type }} "{{ object }}"? | ||||||
|  |                             {% endblocktrans %} | ||||||
|  |                         </p> | ||||||
|  |                         <div class="pf-c-form__group pf-m-action"> | ||||||
|  |                             <div class="pf-c-form__actions"> | ||||||
|  |                                 <input class="pf-c-button pf-m-danger" type="submit" value="{% trans 'Disable' %}" /> | ||||||
|  |                                 <a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Back" %}</a> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     </form> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										125
									
								
								authentik/admin/templates/administration/user/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								authentik/admin/templates/administration/user/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,125 @@ | |||||||
|  | {% extends "administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         <h1> | ||||||
|  |             <i class="pf-icon pf-icon-user"></i> | ||||||
|  |             {% trans 'Users' %} | ||||||
|  |         </h1> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|  |     <div class="pf-c-card"> | ||||||
|  |         {% if object_list %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |                 <div class="pf-c-toolbar__bulk-select"> | ||||||
|  |                     <ak-modal-button href="{% url 'authentik_admin:user-create' %}"> | ||||||
|  |                         <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                             {% trans 'Create' %} | ||||||
|  |                         </ak-spinner-button> | ||||||
|  |                         <div slot="modal"></div> | ||||||
|  |                     </ak-modal-button> | ||||||
|  |                     <button role="ak-refresh" class="pf-c-button pf-m-primary"> | ||||||
|  |                         {% trans 'Refresh' %} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |                 {% include 'partials/pagination.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Name' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Active' %}</th> | ||||||
|  |                     <th role="columnheader" scope="col">{% trans 'Last Login' %}</th> | ||||||
|  |                     <th role="cell"></th> | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody role="rowgroup"> | ||||||
|  |                 {% for user in object_list %} | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th role="columnheader"> | ||||||
|  |                         <div> | ||||||
|  |                             <div>{{ user.username }}</div> | ||||||
|  |                             <small>{{ user.name }}</small> | ||||||
|  |                         </div> | ||||||
|  |                     </th> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ user.is_active }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td role="cell"> | ||||||
|  |                         <span> | ||||||
|  |                             {{ user.last_login }} | ||||||
|  |                         </span> | ||||||
|  |                     </td> | ||||||
|  |                     <td> | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:user-update' pk=user.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                                 {% trans 'Edit' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         {% if user.is_active %} | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:user-disable' pk=user.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-warning"> | ||||||
|  |                                 {% trans 'Disable' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         {% else %} | ||||||
|  |                         <ak-modal-button href="{% url 'authentik_admin:user-delete' pk=user.pk %}"> | ||||||
|  |                             <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                                 {% trans 'Enable' %} | ||||||
|  |                             </ak-spinner-button> | ||||||
|  |                             <div slot="modal"></div> | ||||||
|  |                         </ak-modal-button> | ||||||
|  |                         {% endif %} | ||||||
|  |                         <a class="pf-c-button pf-m-tertiary ak-root-link" href="{% url 'authentik_admin:user-password-reset' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Reset Password' %}</a> | ||||||
|  |                         <a class="pf-c-button pf-m-tertiary ak-root-link" href="{% url 'authentik_core:impersonate-init' user_id=user.pk %}">{% trans 'Impersonate' %}</a> | ||||||
|  |                     </td> | ||||||
|  |                 </tr> | ||||||
|  |                 {% endfor %} | ||||||
|  |             </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div class="pf-c-pagination pf-m-bottom"> | ||||||
|  |             {% include 'partials/pagination.html' %} | ||||||
|  |         </div> | ||||||
|  |         {% else %} | ||||||
|  |         <div class="pf-c-toolbar"> | ||||||
|  |             <div class="pf-c-toolbar__content"> | ||||||
|  |                 {% include 'partials/toolbar_search.html' %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="pf-c-empty-state"> | ||||||
|  |             <div class="pf-c-empty-state__content"> | ||||||
|  |                 <i class="pf-icon pf-icon-user pf-c-empty-state__icon" aria-hidden="true"></i> | ||||||
|  |                 <h1 class="pf-c-title pf-m-lg"> | ||||||
|  |                     {% trans 'No Users.' %} | ||||||
|  |                 </h1> | ||||||
|  |                 <div class="pf-c-empty-state__body"> | ||||||
|  |                 {% if request.GET.search != "" %} | ||||||
|  |                     {% trans "Your search query doesn't match any users." %} | ||||||
|  |                 {% else %} | ||||||
|  |                     {% trans 'Currently no users exist. How did you even get here.' %} | ||||||
|  |                 {% endif %} | ||||||
|  |                 </div> | ||||||
|  |                 <ak-modal-button href="{% url 'authentik_admin:user-create' %}"> | ||||||
|  |                     <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                         {% trans 'Create' %} | ||||||
|  |                     </ak-spinner-button> | ||||||
|  |                     <div slot="modal"></div> | ||||||
|  |                 </ak-modal-button> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										1
									
								
								authentik/admin/templates/fields/codemirror.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								authentik/admin/templates/fields/codemirror.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | <ak-codemirror mode="{{ widget.attrs.mode }}"><textarea class="pf-c-form-control" name="{{ widget.name }}">{% if widget.value %}{{ widget.value }}{% endif %}</textarea></ak-codemirror> | ||||||
							
								
								
									
										18
									
								
								authentik/admin/templates/generic/create.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								authentik/admin/templates/generic/create.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | {% extends base_template|default:"generic/form.html" %} | ||||||
|  |  | ||||||
|  | {% load authentik_utils %} | ||||||
|  | {% load i18n %} | ||||||
|  |  | ||||||
|  | {% block above_form %} | ||||||
|  | <h1> | ||||||
|  |     {% blocktrans with type=form|form_verbose_name %} | ||||||
|  |     Create {{ type }} | ||||||
|  |     {% endblocktrans %} | ||||||
|  | </h1> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block action %} | ||||||
|  | {% blocktrans with type=form|form_verbose_name %} | ||||||
|  | Create {{ type }} | ||||||
|  | {% endblocktrans %} | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										38
									
								
								authentik/admin/templates/generic/form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								authentik/admin/templates/generic/form.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | |||||||
|  | {% extends container_template|default:"administration/base.html" %} | ||||||
|  |  | ||||||
|  | {% load i18n %} | ||||||
|  | {% load authentik_utils %} | ||||||
|  | {% load static %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <section class="pf-c-page__main-section pf-m-light"> | ||||||
|  |     <div class="pf-c-content"> | ||||||
|  |         {% block above_form %} | ||||||
|  |         {% endblock %} | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <section class="pf-c-page__main-section"> | ||||||
|  |     <div class="pf-l-stack"> | ||||||
|  |         <div class="pf-l-stack__item"> | ||||||
|  |             <div class="pf-c-card"> | ||||||
|  |                 <div class="pf-c-card__body"> | ||||||
|  |                     <form id="main-form" action="" method="post" class="pf-c-form pf-m-horizontal" enctype="multipart/form-data"> | ||||||
|  |                         {% include 'partials/form_horizontal.html' with form=form %} | ||||||
|  |                         {% block beneath_form %} | ||||||
|  |                         {% endblock %} | ||||||
|  |                     </form> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </section> | ||||||
|  | <footer class="pf-c-modal-box__footer"> | ||||||
|  |     <input class="pf-c-button pf-m-primary" type="submit" form="main-form" value="{% block action %}{% endblock %}" /> | ||||||
|  |     <a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Cancel" %}</a> | ||||||
|  | </footer> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block scripts %} | ||||||
|  | {{ block.super }} | ||||||
|  | {{ form.media.js }} | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										20
									
								
								authentik/admin/templates/generic/form_non_model.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								authentik/admin/templates/generic/form_non_model.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | {% extends base_template|default:"generic/form.html" %} | ||||||
|  |  | ||||||
|  | {% load authentik_utils %} | ||||||
|  | {% load i18n %} | ||||||
|  |  | ||||||
|  | {% block above_form %} | ||||||
|  | <h1> | ||||||
|  |     {% trans form.title %} | ||||||
|  | </h1> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block beneath_form %} | ||||||
|  | <p> | ||||||
|  |     {% trans form.body %} | ||||||
|  | </p> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block action %} | ||||||
|  | {% trans 'Confirm' %} | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										18
									
								
								authentik/admin/templates/generic/update.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								authentik/admin/templates/generic/update.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | {% extends base_template|default:"generic/form.html" %} | ||||||
|  |  | ||||||
|  | {% load authentik_utils %} | ||||||
|  | {% load i18n %} | ||||||
|  |  | ||||||
|  | {% block above_form %} | ||||||
|  | <h1> | ||||||
|  |     {% blocktrans with type=form|form_verbose_name|title inst=form.instance %} | ||||||
|  |     Update {{ inst }} | ||||||
|  |     {% endblocktrans %} | ||||||
|  | </h1> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block action %} | ||||||
|  | {% blocktrans with type=form|form_verbose_name %} | ||||||
|  | Update {{ type }} | ||||||
|  | {% endblocktrans %} | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										62
									
								
								authentik/admin/templatetags/admin_reflection.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								authentik/admin/templatetags/admin_reflection.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | |||||||
|  | """authentik admin templatetags""" | ||||||
|  | from django import template | ||||||
|  | from django.db.models import Model | ||||||
|  | from django.utils.html import mark_safe | ||||||
|  | from structlog import get_logger | ||||||
|  |  | ||||||
|  | register = template.Library() | ||||||
|  | LOGGER = get_logger() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @register.simple_tag() | ||||||
|  | def get_links(model_instance): | ||||||
|  |     """Find all link_ methods on an object instance, run them and return as dict""" | ||||||
|  |     prefix = "link_" | ||||||
|  |     links = {} | ||||||
|  |  | ||||||
|  |     if not isinstance(model_instance, Model): | ||||||
|  |         LOGGER.warning("Model is not instance of Model", model_instance=model_instance) | ||||||
|  |         return links | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         for name in dir(model_instance): | ||||||
|  |             if not name.startswith(prefix): | ||||||
|  |                 continue | ||||||
|  |             value = getattr(model_instance, name) | ||||||
|  |             if not callable(value): | ||||||
|  |                 continue | ||||||
|  |             human_name = name.replace(prefix, "").replace("_", " ").capitalize() | ||||||
|  |             link = value() | ||||||
|  |             if link: | ||||||
|  |                 links[human_name] = link | ||||||
|  |     except NotImplementedError: | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     return links | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @register.simple_tag(takes_context=True) | ||||||
|  | def get_htmls(context, model_instance): | ||||||
|  |     """Find all html_ methods on an object instance, run them and return as dict""" | ||||||
|  |     prefix = "html_" | ||||||
|  |     htmls = [] | ||||||
|  |  | ||||||
|  |     if not isinstance(model_instance, Model): | ||||||
|  |         LOGGER.warning("Model is not instance of Model", model_instance=model_instance) | ||||||
|  |         return htmls | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         for name in dir(model_instance): | ||||||
|  |             if not name.startswith(prefix): | ||||||
|  |                 continue | ||||||
|  |             value = getattr(model_instance, name) | ||||||
|  |             if not callable(value): | ||||||
|  |                 continue | ||||||
|  |             if name.startswith(prefix): | ||||||
|  |                 html = value(context.get("request")) | ||||||
|  |                 if html: | ||||||
|  |                     htmls.append(mark_safe(html)) | ||||||
|  |     except NotImplementedError: | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     return htmls | ||||||
| @ -1,13 +1,12 @@ | |||||||
| """test admin api""" | """test admin api""" | ||||||
| from json import loads | from json import loads | ||||||
|  |  | ||||||
|  | from django.shortcuts import reverse | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
| from django.urls import reverse |  | ||||||
|  |  | ||||||
| from authentik import __version__ | from authentik import __version__ | ||||||
| from authentik.core.models import Group, User | from authentik.core.models import Group, User | ||||||
| from authentik.core.tasks import clean_expired_models | from authentik.core.tasks import clean_expired_models | ||||||
| from authentik.events.monitored_tasks import TaskResultStatus |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestAdminAPI(TestCase): | class TestAdminAPI(TestCase): | ||||||
| @ -28,29 +27,9 @@ class TestAdminAPI(TestCase): | |||||||
|         self.assertEqual(response.status_code, 200) |         self.assertEqual(response.status_code, 200) | ||||||
|         body = loads(response.content) |         body = loads(response.content) | ||||||
|         self.assertTrue( |         self.assertTrue( | ||||||
|             any(task["task_name"] == "clean_expired_models" for task in body) |             any([task["task_name"] == "clean_expired_models" for task in body]) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def test_tasks_single(self): |  | ||||||
|         """Test Task API (read single)""" |  | ||||||
|         clean_expired_models.delay() |  | ||||||
|         response = self.client.get( |  | ||||||
|             reverse( |  | ||||||
|                 "authentik_api:admin_system_tasks-detail", |  | ||||||
|                 kwargs={"pk": "clean_expired_models"}, |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(response.status_code, 200) |  | ||||||
|         body = loads(response.content) |  | ||||||
|         self.assertEqual(body["status"], TaskResultStatus.SUCCESSFUL.name) |  | ||||||
|         self.assertEqual(body["task_name"], "clean_expired_models") |  | ||||||
|         response = self.client.get( |  | ||||||
|             reverse( |  | ||||||
|                 "authentik_api:admin_system_tasks-detail", kwargs={"pk": "qwerqwer"} |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(response.status_code, 404) |  | ||||||
|  |  | ||||||
|     def test_tasks_retry(self): |     def test_tasks_retry(self): | ||||||
|         """Test Task API (retry)""" |         """Test Task API (retry)""" | ||||||
|         clean_expired_models.delay() |         clean_expired_models.delay() | ||||||
| @ -60,7 +39,9 @@ class TestAdminAPI(TestCase): | |||||||
|                 kwargs={"pk": "clean_expired_models"}, |                 kwargs={"pk": "clean_expired_models"}, | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|         self.assertEqual(response.status_code, 204) |         self.assertEqual(response.status_code, 200) | ||||||
|  |         body = loads(response.content) | ||||||
|  |         self.assertTrue(body["successful"]) | ||||||
|  |  | ||||||
|     def test_tasks_retry_404(self): |     def test_tasks_retry_404(self): | ||||||
|         """Test Task API (retry, 404)""" |         """Test Task API (retry, 404)""" | ||||||
| @ -90,8 +71,3 @@ class TestAdminAPI(TestCase): | |||||||
|         """Test metrics API""" |         """Test metrics API""" | ||||||
|         response = self.client.get(reverse("authentik_api:admin_metrics-list")) |         response = self.client.get(reverse("authentik_api:admin_metrics-list")) | ||||||
|         self.assertEqual(response.status_code, 200) |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|     def test_apps(self): |  | ||||||
|         """Test apps API""" |  | ||||||
|         response = self.client.get(reverse("authentik_api:apps-list")) |  | ||||||
|         self.assertEqual(response.status_code, 200) |  | ||||||
|  | |||||||
							
								
								
									
										66
									
								
								authentik/admin/tests/test_generated.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								authentik/admin/tests/test_generated.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | |||||||
|  | """admin tests""" | ||||||
|  | from importlib import import_module | ||||||
|  | from typing import Callable | ||||||
|  |  | ||||||
|  | from django.forms import ModelForm | ||||||
|  | from django.shortcuts import reverse | ||||||
|  | from django.test import Client, TestCase | ||||||
|  | from django.urls.exceptions import NoReverseMatch | ||||||
|  |  | ||||||
|  | from authentik.admin.urls import urlpatterns | ||||||
|  | from authentik.core.models import Group, User | ||||||
|  | from authentik.lib.utils.reflection import get_apps | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestAdmin(TestCase): | ||||||
|  |     """Generic admin tests""" | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         self.user = User.objects.create_user(username="test") | ||||||
|  |         self.user.ak_groups.add(Group.objects.filter(is_superuser=True).first()) | ||||||
|  |         self.user.save() | ||||||
|  |         self.client = Client() | ||||||
|  |         self.client.force_login(self.user) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def generic_view_tester(view_name: str) -> Callable: | ||||||
|  |     """This is used instead of subTest for better visibility""" | ||||||
|  |  | ||||||
|  |     def tester(self: TestAdmin): | ||||||
|  |         try: | ||||||
|  |             full_url = reverse(f"authentik_admin:{view_name}") | ||||||
|  |             response = self.client.get(full_url) | ||||||
|  |             self.assertTrue(response.status_code < 500) | ||||||
|  |         except NoReverseMatch: | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |     return tester | ||||||
|  |  | ||||||
|  |  | ||||||
|  | for url in urlpatterns: | ||||||
|  |     method_name = url.name.replace("-", "_") | ||||||
|  |     setattr(TestAdmin, f"test_view_{method_name}", generic_view_tester(url.name)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def generic_form_tester(form: ModelForm) -> Callable: | ||||||
|  |     """Test a form""" | ||||||
|  |  | ||||||
|  |     def tester(self: TestAdmin): | ||||||
|  |         form_inst = form() | ||||||
|  |         self.assertFalse(form_inst.is_valid()) | ||||||
|  |  | ||||||
|  |     return tester | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Load the forms module from every app, so we have all forms loaded | ||||||
|  | for app in get_apps(): | ||||||
|  |     module = app.__module__.replace(".apps", ".forms") | ||||||
|  |     try: | ||||||
|  |         import_module(module) | ||||||
|  |     except ImportError: | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  | for form_class in ModelForm.__subclasses__(): | ||||||
|  |     setattr( | ||||||
|  |         TestAdmin, f"test_form_{form_class.__name__}", generic_form_tester(form_class) | ||||||
|  |     ) | ||||||
							
								
								
									
										43
									
								
								authentik/admin/tests/test_policy_binding.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								authentik/admin/tests/test_policy_binding.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | """admin tests""" | ||||||
|  | from uuid import uuid4 | ||||||
|  |  | ||||||
|  | from django import forms | ||||||
|  | from django.test import TestCase | ||||||
|  | from django.test.client import RequestFactory | ||||||
|  |  | ||||||
|  | from authentik.admin.views.policies_bindings import PolicyBindingCreateView | ||||||
|  | from authentik.core.models import Application | ||||||
|  | from authentik.policies.forms import PolicyBindingForm | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestPolicyBindingView(TestCase): | ||||||
|  |     """Generic admin tests""" | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         self.factory = RequestFactory() | ||||||
|  |  | ||||||
|  |     def test_without_get_param(self): | ||||||
|  |         """Test PolicyBindingCreateView without get params""" | ||||||
|  |         request = self.factory.get("/") | ||||||
|  |         view = PolicyBindingCreateView(request=request) | ||||||
|  |         self.assertEqual(view.get_initial(), {}) | ||||||
|  |  | ||||||
|  |     def test_with_params_invalid(self): | ||||||
|  |         """Test PolicyBindingCreateView with invalid get params""" | ||||||
|  |         request = self.factory.get("/", {"target": uuid4()}) | ||||||
|  |         view = PolicyBindingCreateView(request=request) | ||||||
|  |         self.assertEqual(view.get_initial(), {}) | ||||||
|  |  | ||||||
|  |     def test_with_params(self): | ||||||
|  |         """Test PolicyBindingCreateView with get params""" | ||||||
|  |         target = Application.objects.create(name="test") | ||||||
|  |         request = self.factory.get("/", {"target": target.pk.hex}) | ||||||
|  |         view = PolicyBindingCreateView(request=request) | ||||||
|  |         self.assertEqual(view.get_initial(), {"target": target, "order": 0}) | ||||||
|  |  | ||||||
|  |         self.assertTrue( | ||||||
|  |             isinstance( | ||||||
|  |                 PolicyBindingForm(initial={"target": "foo"}).fields["target"].widget, | ||||||
|  |                 forms.HiddenInput, | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
							
								
								
									
										43
									
								
								authentik/admin/tests/test_stage_bindings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								authentik/admin/tests/test_stage_bindings.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | """admin tests""" | ||||||
|  | from uuid import uuid4 | ||||||
|  |  | ||||||
|  | from django import forms | ||||||
|  | from django.test import TestCase | ||||||
|  | from django.test.client import RequestFactory | ||||||
|  |  | ||||||
|  | from authentik.admin.views.stages_bindings import StageBindingCreateView | ||||||
|  | from authentik.flows.forms import FlowStageBindingForm | ||||||
|  | from authentik.flows.models import Flow | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestStageBindingView(TestCase): | ||||||
|  |     """Generic admin tests""" | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         self.factory = RequestFactory() | ||||||
|  |  | ||||||
|  |     def test_without_get_param(self): | ||||||
|  |         """Test StageBindingCreateView without get params""" | ||||||
|  |         request = self.factory.get("/") | ||||||
|  |         view = StageBindingCreateView(request=request) | ||||||
|  |         self.assertEqual(view.get_initial(), {}) | ||||||
|  |  | ||||||
|  |     def test_with_params_invalid(self): | ||||||
|  |         """Test StageBindingCreateView with invalid get params""" | ||||||
|  |         request = self.factory.get("/", {"target": uuid4()}) | ||||||
|  |         view = StageBindingCreateView(request=request) | ||||||
|  |         self.assertEqual(view.get_initial(), {}) | ||||||
|  |  | ||||||
|  |     def test_with_params(self): | ||||||
|  |         """Test StageBindingCreateView with get params""" | ||||||
|  |         target = Flow.objects.create(name="test", slug="test") | ||||||
|  |         request = self.factory.get("/", {"target": target.pk.hex}) | ||||||
|  |         view = StageBindingCreateView(request=request) | ||||||
|  |         self.assertEqual(view.get_initial(), {"target": target, "order": 0}) | ||||||
|  |  | ||||||
|  |         self.assertTrue( | ||||||
|  |             isinstance( | ||||||
|  |                 FlowStageBindingForm(initial={"target": "foo"}).fields["target"].widget, | ||||||
|  |                 forms.HiddenInput, | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
| @ -1,81 +0,0 @@ | |||||||
| """test admin tasks""" |  | ||||||
| import json |  | ||||||
| from dataclasses import dataclass |  | ||||||
| from unittest.mock import Mock, patch |  | ||||||
|  |  | ||||||
| from django.core.cache import cache |  | ||||||
| from django.test import TestCase |  | ||||||
| from requests.exceptions import RequestException |  | ||||||
|  |  | ||||||
| from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version |  | ||||||
| from authentik.events.models import Event, EventAction |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @dataclass |  | ||||||
| class MockResponse: |  | ||||||
|     """Mock class to emulate the methods of requests's Response we need""" |  | ||||||
|  |  | ||||||
|     status_code: int |  | ||||||
|     response: str |  | ||||||
|  |  | ||||||
|     def json(self) -> dict: |  | ||||||
|         """Get json parsed response""" |  | ||||||
|         return json.loads(self.response) |  | ||||||
|  |  | ||||||
|     def raise_for_status(self): |  | ||||||
|         """raise RequestException if status code is 400 or more""" |  | ||||||
|         if self.status_code >= 400: |  | ||||||
|             raise RequestException |  | ||||||
|  |  | ||||||
|  |  | ||||||
| REQUEST_MOCK_VALID = Mock( |  | ||||||
|     return_value=MockResponse( |  | ||||||
|         200, |  | ||||||
|         """{ |  | ||||||
|             "tag_name": "version/99999999.9999999", |  | ||||||
|             "body": "https://goauthentik.io/test" |  | ||||||
|         }""", |  | ||||||
|     ) |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| REQUEST_MOCK_INVALID = Mock(return_value=MockResponse(400, "{}")) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestAdminTasks(TestCase): |  | ||||||
|     """test admin tasks""" |  | ||||||
|  |  | ||||||
|     @patch("authentik.admin.tasks.get", REQUEST_MOCK_VALID) |  | ||||||
|     def test_version_valid_response(self): |  | ||||||
|         """Test Update checker with valid response""" |  | ||||||
|         update_latest_version.delay().get() |  | ||||||
|         self.assertEqual(cache.get(VERSION_CACHE_KEY), "99999999.9999999") |  | ||||||
|         self.assertTrue( |  | ||||||
|             Event.objects.filter( |  | ||||||
|                 action=EventAction.UPDATE_AVAILABLE, |  | ||||||
|                 context__new_version="99999999.9999999", |  | ||||||
|                 context__message="Changelog: https://goauthentik.io/test", |  | ||||||
|             ).exists() |  | ||||||
|         ) |  | ||||||
|         # test that a consecutive check doesn't create a duplicate event |  | ||||||
|         update_latest_version.delay().get() |  | ||||||
|         self.assertEqual( |  | ||||||
|             len( |  | ||||||
|                 Event.objects.filter( |  | ||||||
|                     action=EventAction.UPDATE_AVAILABLE, |  | ||||||
|                     context__new_version="99999999.9999999", |  | ||||||
|                     context__message="Changelog: https://goauthentik.io/test", |  | ||||||
|                 ) |  | ||||||
|             ), |  | ||||||
|             1, |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     @patch("authentik.admin.tasks.get", REQUEST_MOCK_INVALID) |  | ||||||
|     def test_version_error(self): |  | ||||||
|         """Test Update checker with invalid response""" |  | ||||||
|         update_latest_version.delay().get() |  | ||||||
|         self.assertEqual(cache.get(VERSION_CACHE_KEY), "0.0.0") |  | ||||||
|         self.assertFalse( |  | ||||||
|             Event.objects.filter( |  | ||||||
|                 action=EventAction.UPDATE_AVAILABLE, context__new_version="0.0.0" |  | ||||||
|             ).exists() |  | ||||||
|         ) |  | ||||||
							
								
								
									
										352
									
								
								authentik/admin/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										352
									
								
								authentik/admin/urls.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,352 @@ | |||||||
|  | """authentik URL Configuration""" | ||||||
|  | from django.urls import path | ||||||
|  |  | ||||||
|  | from authentik.admin.views import ( | ||||||
|  |     applications, | ||||||
|  |     certificate_key_pair, | ||||||
|  |     flows, | ||||||
|  |     groups, | ||||||
|  |     outposts, | ||||||
|  |     outposts_service_connections, | ||||||
|  |     overview, | ||||||
|  |     policies, | ||||||
|  |     policies_bindings, | ||||||
|  |     property_mappings, | ||||||
|  |     providers, | ||||||
|  |     sources, | ||||||
|  |     stages, | ||||||
|  |     stages_bindings, | ||||||
|  |     stages_invitations, | ||||||
|  |     stages_prompts, | ||||||
|  |     tasks, | ||||||
|  |     tokens, | ||||||
|  |     users, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | urlpatterns = [ | ||||||
|  |     path( | ||||||
|  |         "overview/cache/flow/", | ||||||
|  |         overview.FlowCacheClearView.as_view(), | ||||||
|  |         name="overview-clear-flow-cache", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "overview/cache/policy/", | ||||||
|  |         overview.PolicyCacheClearView.as_view(), | ||||||
|  |         name="overview-clear-policy-cache", | ||||||
|  |     ), | ||||||
|  |     # Applications | ||||||
|  |     path( | ||||||
|  |         "applications/", applications.ApplicationListView.as_view(), name="applications" | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "applications/create/", | ||||||
|  |         applications.ApplicationCreateView.as_view(), | ||||||
|  |         name="application-create", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "applications/<uuid:pk>/update/", | ||||||
|  |         applications.ApplicationUpdateView.as_view(), | ||||||
|  |         name="application-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "applications/<uuid:pk>/delete/", | ||||||
|  |         applications.ApplicationDeleteView.as_view(), | ||||||
|  |         name="application-delete", | ||||||
|  |     ), | ||||||
|  |     # Tokens | ||||||
|  |     path("tokens/", tokens.TokenListView.as_view(), name="tokens"), | ||||||
|  |     path( | ||||||
|  |         "tokens/<uuid:pk>/delete/", | ||||||
|  |         tokens.TokenDeleteView.as_view(), | ||||||
|  |         name="token-delete", | ||||||
|  |     ), | ||||||
|  |     # Sources | ||||||
|  |     path("sources/", sources.SourceListView.as_view(), name="sources"), | ||||||
|  |     path("sources/create/", sources.SourceCreateView.as_view(), name="source-create"), | ||||||
|  |     path( | ||||||
|  |         "sources/<uuid:pk>/update/", | ||||||
|  |         sources.SourceUpdateView.as_view(), | ||||||
|  |         name="source-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "sources/<uuid:pk>/delete/", | ||||||
|  |         sources.SourceDeleteView.as_view(), | ||||||
|  |         name="source-delete", | ||||||
|  |     ), | ||||||
|  |     # Policies | ||||||
|  |     path("policies/", policies.PolicyListView.as_view(), name="policies"), | ||||||
|  |     path("policies/create/", policies.PolicyCreateView.as_view(), name="policy-create"), | ||||||
|  |     path( | ||||||
|  |         "policies/<uuid:pk>/update/", | ||||||
|  |         policies.PolicyUpdateView.as_view(), | ||||||
|  |         name="policy-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "policies/<uuid:pk>/delete/", | ||||||
|  |         policies.PolicyDeleteView.as_view(), | ||||||
|  |         name="policy-delete", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "policies/<uuid:pk>/test/", | ||||||
|  |         policies.PolicyTestView.as_view(), | ||||||
|  |         name="policy-test", | ||||||
|  |     ), | ||||||
|  |     # Policy bindings | ||||||
|  |     path( | ||||||
|  |         "policies/bindings/", | ||||||
|  |         policies_bindings.PolicyBindingListView.as_view(), | ||||||
|  |         name="policies-bindings", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "policies/bindings/create/", | ||||||
|  |         policies_bindings.PolicyBindingCreateView.as_view(), | ||||||
|  |         name="policy-binding-create", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "policies/bindings/<uuid:pk>/update/", | ||||||
|  |         policies_bindings.PolicyBindingUpdateView.as_view(), | ||||||
|  |         name="policy-binding-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "policies/bindings/<uuid:pk>/delete/", | ||||||
|  |         policies_bindings.PolicyBindingDeleteView.as_view(), | ||||||
|  |         name="policy-binding-delete", | ||||||
|  |     ), | ||||||
|  |     # Providers | ||||||
|  |     path("providers/", providers.ProviderListView.as_view(), name="providers"), | ||||||
|  |     path( | ||||||
|  |         "providers/create/", | ||||||
|  |         providers.ProviderCreateView.as_view(), | ||||||
|  |         name="provider-create", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "providers/<int:pk>/update/", | ||||||
|  |         providers.ProviderUpdateView.as_view(), | ||||||
|  |         name="provider-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "providers/<int:pk>/delete/", | ||||||
|  |         providers.ProviderDeleteView.as_view(), | ||||||
|  |         name="provider-delete", | ||||||
|  |     ), | ||||||
|  |     # Stages | ||||||
|  |     path("stages/", stages.StageListView.as_view(), name="stages"), | ||||||
|  |     path("stages/create/", stages.StageCreateView.as_view(), name="stage-create"), | ||||||
|  |     path( | ||||||
|  |         "stages/<uuid:pk>/update/", | ||||||
|  |         stages.StageUpdateView.as_view(), | ||||||
|  |         name="stage-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "stages/<uuid:pk>/delete/", | ||||||
|  |         stages.StageDeleteView.as_view(), | ||||||
|  |         name="stage-delete", | ||||||
|  |     ), | ||||||
|  |     # Stage bindings | ||||||
|  |     path( | ||||||
|  |         "stages/bindings/", | ||||||
|  |         stages_bindings.StageBindingListView.as_view(), | ||||||
|  |         name="stage-bindings", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "stages/bindings/create/", | ||||||
|  |         stages_bindings.StageBindingCreateView.as_view(), | ||||||
|  |         name="stage-binding-create", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "stages/bindings/<uuid:pk>/update/", | ||||||
|  |         stages_bindings.StageBindingUpdateView.as_view(), | ||||||
|  |         name="stage-binding-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "stages/bindings/<uuid:pk>/delete/", | ||||||
|  |         stages_bindings.StageBindingDeleteView.as_view(), | ||||||
|  |         name="stage-binding-delete", | ||||||
|  |     ), | ||||||
|  |     # Stage Prompts | ||||||
|  |     path( | ||||||
|  |         "stages/prompts/", | ||||||
|  |         stages_prompts.PromptListView.as_view(), | ||||||
|  |         name="stage-prompts", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "stages/prompts/create/", | ||||||
|  |         stages_prompts.PromptCreateView.as_view(), | ||||||
|  |         name="stage-prompt-create", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "stages/prompts/<uuid:pk>/update/", | ||||||
|  |         stages_prompts.PromptUpdateView.as_view(), | ||||||
|  |         name="stage-prompt-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "stages/prompts/<uuid:pk>/delete/", | ||||||
|  |         stages_prompts.PromptDeleteView.as_view(), | ||||||
|  |         name="stage-prompt-delete", | ||||||
|  |     ), | ||||||
|  |     # Stage Invitations | ||||||
|  |     path( | ||||||
|  |         "stages/invitations/", | ||||||
|  |         stages_invitations.InvitationListView.as_view(), | ||||||
|  |         name="stage-invitations", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "stages/invitations/create/", | ||||||
|  |         stages_invitations.InvitationCreateView.as_view(), | ||||||
|  |         name="stage-invitation-create", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "stages/invitations/<uuid:pk>/delete/", | ||||||
|  |         stages_invitations.InvitationDeleteView.as_view(), | ||||||
|  |         name="stage-invitation-delete", | ||||||
|  |     ), | ||||||
|  |     # Flows | ||||||
|  |     path("flows/", flows.FlowListView.as_view(), name="flows"), | ||||||
|  |     path( | ||||||
|  |         "flows/create/", | ||||||
|  |         flows.FlowCreateView.as_view(), | ||||||
|  |         name="flow-create", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "flows/import/", | ||||||
|  |         flows.FlowImportView.as_view(), | ||||||
|  |         name="flow-import", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "flows/<uuid:pk>/update/", | ||||||
|  |         flows.FlowUpdateView.as_view(), | ||||||
|  |         name="flow-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "flows/<uuid:pk>/execute/", | ||||||
|  |         flows.FlowDebugExecuteView.as_view(), | ||||||
|  |         name="flow-execute", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "flows/<uuid:pk>/export/", | ||||||
|  |         flows.FlowExportView.as_view(), | ||||||
|  |         name="flow-export", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "flows/<uuid:pk>/delete/", | ||||||
|  |         flows.FlowDeleteView.as_view(), | ||||||
|  |         name="flow-delete", | ||||||
|  |     ), | ||||||
|  |     # Property Mappings | ||||||
|  |     path( | ||||||
|  |         "property-mappings/", | ||||||
|  |         property_mappings.PropertyMappingListView.as_view(), | ||||||
|  |         name="property-mappings", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "property-mappings/create/", | ||||||
|  |         property_mappings.PropertyMappingCreateView.as_view(), | ||||||
|  |         name="property-mapping-create", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "property-mappings/<uuid:pk>/update/", | ||||||
|  |         property_mappings.PropertyMappingUpdateView.as_view(), | ||||||
|  |         name="property-mapping-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "property-mappings/<uuid:pk>/delete/", | ||||||
|  |         property_mappings.PropertyMappingDeleteView.as_view(), | ||||||
|  |         name="property-mapping-delete", | ||||||
|  |     ), | ||||||
|  |     # Users | ||||||
|  |     path("users/", users.UserListView.as_view(), name="users"), | ||||||
|  |     path("users/create/", users.UserCreateView.as_view(), name="user-create"), | ||||||
|  |     path("users/<int:pk>/update/", users.UserUpdateView.as_view(), name="user-update"), | ||||||
|  |     path("users/<int:pk>/delete/", users.UserDeleteView.as_view(), name="user-delete"), | ||||||
|  |     path( | ||||||
|  |         "users/<int:pk>/disable/", users.UserDisableView.as_view(), name="user-disable" | ||||||
|  |     ), | ||||||
|  |     path("users/<int:pk>/enable/", users.UserEnableView.as_view(), name="user-enable"), | ||||||
|  |     path( | ||||||
|  |         "users/<int:pk>/reset/", | ||||||
|  |         users.UserPasswordResetView.as_view(), | ||||||
|  |         name="user-password-reset", | ||||||
|  |     ), | ||||||
|  |     # Groups | ||||||
|  |     path("groups/", groups.GroupListView.as_view(), name="groups"), | ||||||
|  |     path("groups/create/", groups.GroupCreateView.as_view(), name="group-create"), | ||||||
|  |     path( | ||||||
|  |         "groups/<uuid:pk>/update/", | ||||||
|  |         groups.GroupUpdateView.as_view(), | ||||||
|  |         name="group-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "groups/<uuid:pk>/delete/", | ||||||
|  |         groups.GroupDeleteView.as_view(), | ||||||
|  |         name="group-delete", | ||||||
|  |     ), | ||||||
|  |     # Certificate-Key Pairs | ||||||
|  |     path( | ||||||
|  |         "crypto/certificates/", | ||||||
|  |         certificate_key_pair.CertificateKeyPairListView.as_view(), | ||||||
|  |         name="certificate_key_pair", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "crypto/certificates/create/", | ||||||
|  |         certificate_key_pair.CertificateKeyPairCreateView.as_view(), | ||||||
|  |         name="certificatekeypair-create", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "crypto/certificates/<uuid:pk>/update/", | ||||||
|  |         certificate_key_pair.CertificateKeyPairUpdateView.as_view(), | ||||||
|  |         name="certificatekeypair-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "crypto/certificates/<uuid:pk>/delete/", | ||||||
|  |         certificate_key_pair.CertificateKeyPairDeleteView.as_view(), | ||||||
|  |         name="certificatekeypair-delete", | ||||||
|  |     ), | ||||||
|  |     # Outposts | ||||||
|  |     path( | ||||||
|  |         "outposts/", | ||||||
|  |         outposts.OutpostListView.as_view(), | ||||||
|  |         name="outposts", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "outposts/create/", | ||||||
|  |         outposts.OutpostCreateView.as_view(), | ||||||
|  |         name="outpost-create", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "outposts/<uuid:pk>/update/", | ||||||
|  |         outposts.OutpostUpdateView.as_view(), | ||||||
|  |         name="outpost-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "outposts/<uuid:pk>/delete/", | ||||||
|  |         outposts.OutpostDeleteView.as_view(), | ||||||
|  |         name="outpost-delete", | ||||||
|  |     ), | ||||||
|  |     # Outpost Service Connections | ||||||
|  |     path( | ||||||
|  |         "outposts/service_connections/", | ||||||
|  |         outposts_service_connections.OutpostServiceConnectionListView.as_view(), | ||||||
|  |         name="outpost-service-connections", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "outposts/service_connections/create/", | ||||||
|  |         outposts_service_connections.OutpostServiceConnectionCreateView.as_view(), | ||||||
|  |         name="outpost-service-connection-create", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "outposts/service_connections/<uuid:pk>/update/", | ||||||
|  |         outposts_service_connections.OutpostServiceConnectionUpdateView.as_view(), | ||||||
|  |         name="outpost-service-connection-update", | ||||||
|  |     ), | ||||||
|  |     path( | ||||||
|  |         "outposts/service_connections/<uuid:pk>/delete/", | ||||||
|  |         outposts_service_connections.OutpostServiceConnectionDeleteView.as_view(), | ||||||
|  |         name="outpost-service-connection-delete", | ||||||
|  |     ), | ||||||
|  |     # Tasks | ||||||
|  |     path( | ||||||
|  |         "tasks/", | ||||||
|  |         tasks.TaskListView.as_view(), | ||||||
|  |         name="tasks", | ||||||
|  |     ), | ||||||
|  | ] | ||||||
							
								
								
									
										93
									
								
								authentik/admin/views/applications.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								authentik/admin/views/applications.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | |||||||
|  | """authentik Application administration""" | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from django.views.generic import ListView, UpdateView | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.core.forms.applications import ApplicationForm | ||||||
|  | from authentik.core.models import Application | ||||||
|  | from authentik.lib.views import CreateAssignPermView | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ApplicationListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     ListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all applications""" | ||||||
|  |  | ||||||
|  |     model = Application | ||||||
|  |     permission_required = "authentik_core.view_application" | ||||||
|  |     ordering = "name" | ||||||
|  |     template_name = "administration/application/list.html" | ||||||
|  |  | ||||||
|  |     search_fields = [ | ||||||
|  |         "name", | ||||||
|  |         "slug", | ||||||
|  |         "meta_launch_url", | ||||||
|  |         "meta_icon_url", | ||||||
|  |         "meta_description", | ||||||
|  |         "meta_publisher", | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ApplicationCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     CreateAssignPermView, | ||||||
|  | ): | ||||||
|  |     """Create new Application""" | ||||||
|  |  | ||||||
|  |     model = Application | ||||||
|  |     form_class = ApplicationForm | ||||||
|  |     permission_required = "authentik_core.add_application" | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:applications") | ||||||
|  |     success_message = _("Successfully created Application") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ApplicationUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     UpdateView, | ||||||
|  | ): | ||||||
|  |     """Update application""" | ||||||
|  |  | ||||||
|  |     model = Application | ||||||
|  |     form_class = ApplicationForm | ||||||
|  |     permission_required = "authentik_core.change_application" | ||||||
|  |  | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:applications") | ||||||
|  |     success_message = _("Successfully updated Application") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ApplicationDeleteView( | ||||||
|  |     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView | ||||||
|  | ): | ||||||
|  |     """Delete application""" | ||||||
|  |  | ||||||
|  |     model = Application | ||||||
|  |     permission_required = "authentik_core.delete_application" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:applications") | ||||||
|  |     success_message = _("Successfully deleted Application") | ||||||
							
								
								
									
										86
									
								
								authentik/admin/views/certificate_key_pair.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								authentik/admin/views/certificate_key_pair.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,86 @@ | |||||||
|  | """authentik CertificateKeyPair administration""" | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from django.views.generic import ListView, UpdateView | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.crypto.forms import CertificateKeyPairForm | ||||||
|  | from authentik.crypto.models import CertificateKeyPair | ||||||
|  | from authentik.lib.views import CreateAssignPermView | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CertificateKeyPairListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     ListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all keypairs""" | ||||||
|  |  | ||||||
|  |     model = CertificateKeyPair | ||||||
|  |     permission_required = "authentik_crypto.view_certificatekeypair" | ||||||
|  |     ordering = "name" | ||||||
|  |     template_name = "administration/certificatekeypair/list.html" | ||||||
|  |  | ||||||
|  |     search_fields = ["name"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CertificateKeyPairCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     CreateAssignPermView, | ||||||
|  | ): | ||||||
|  |     """Create new CertificateKeyPair""" | ||||||
|  |  | ||||||
|  |     model = CertificateKeyPair | ||||||
|  |     form_class = CertificateKeyPairForm | ||||||
|  |     permission_required = "authentik_crypto.add_certificatekeypair" | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:certificate_key_pair") | ||||||
|  |     success_message = _("Successfully created CertificateKeyPair") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CertificateKeyPairUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     UpdateView, | ||||||
|  | ): | ||||||
|  |     """Update certificatekeypair""" | ||||||
|  |  | ||||||
|  |     model = CertificateKeyPair | ||||||
|  |     form_class = CertificateKeyPairForm | ||||||
|  |     permission_required = "authentik_crypto.change_certificatekeypair" | ||||||
|  |  | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:certificate_key_pair") | ||||||
|  |     success_message = _("Successfully updated Certificate-Key Pair") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CertificateKeyPairDeleteView( | ||||||
|  |     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView | ||||||
|  | ): | ||||||
|  |     """Delete certificatekeypair""" | ||||||
|  |  | ||||||
|  |     model = CertificateKeyPair | ||||||
|  |     permission_required = "authentik_crypto.delete_certificatekeypair" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:certificate_key_pair") | ||||||
|  |     success_message = _("Successfully deleted Certificate-Key Pair") | ||||||
							
								
								
									
										151
									
								
								authentik/admin/views/flows.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								authentik/admin/views/flows.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,151 @@ | |||||||
|  | """authentik Flow administration""" | ||||||
|  | from django.contrib import messages | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.http import HttpRequest, HttpResponse, JsonResponse | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from django.views.generic import DetailView, FormView, ListView, UpdateView | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.flows.forms import FlowForm, FlowImportForm | ||||||
|  | from authentik.flows.models import Flow | ||||||
|  | from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER | ||||||
|  | from authentik.flows.transfer.common import DataclassEncoder | ||||||
|  | from authentik.flows.transfer.exporter import FlowExporter | ||||||
|  | from authentik.flows.transfer.importer import FlowImporter | ||||||
|  | from authentik.flows.views import SESSION_KEY_PLAN, FlowPlanner | ||||||
|  | from authentik.lib.utils.urls import redirect_with_qs | ||||||
|  | from authentik.lib.views import CreateAssignPermView | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FlowListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     ListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all flows""" | ||||||
|  |  | ||||||
|  |     model = Flow | ||||||
|  |     permission_required = "authentik_flows.view_flow" | ||||||
|  |     ordering = "name" | ||||||
|  |     template_name = "administration/flow/list.html" | ||||||
|  |     search_fields = ["name", "slug", "designation", "title"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FlowCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     CreateAssignPermView, | ||||||
|  | ): | ||||||
|  |     """Create new Flow""" | ||||||
|  |  | ||||||
|  |     model = Flow | ||||||
|  |     form_class = FlowForm | ||||||
|  |     permission_required = "authentik_flows.add_flow" | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:flows") | ||||||
|  |     success_message = _("Successfully created Flow") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FlowUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     UpdateView, | ||||||
|  | ): | ||||||
|  |     """Update flow""" | ||||||
|  |  | ||||||
|  |     model = Flow | ||||||
|  |     form_class = FlowForm | ||||||
|  |     permission_required = "authentik_flows.change_flow" | ||||||
|  |  | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:flows") | ||||||
|  |     success_message = _("Successfully updated Flow") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FlowDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): | ||||||
|  |     """Delete flow""" | ||||||
|  |  | ||||||
|  |     model = Flow | ||||||
|  |     permission_required = "authentik_flows.delete_flow" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:flows") | ||||||
|  |     success_message = _("Successfully deleted Flow") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FlowDebugExecuteView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): | ||||||
|  |     """Debug exectue flow, setting the current user as pending user""" | ||||||
|  |  | ||||||
|  |     model = Flow | ||||||
|  |     permission_required = "authentik_flows.view_flow" | ||||||
|  |  | ||||||
|  |     # pylint: disable=unused-argument | ||||||
|  |     def get(self, request: HttpRequest, pk: str) -> HttpResponse: | ||||||
|  |         """Debug exectue flow, setting the current user as pending user""" | ||||||
|  |         flow: Flow = self.get_object() | ||||||
|  |         planner = FlowPlanner(flow) | ||||||
|  |         planner.use_cache = False | ||||||
|  |         plan = planner.plan(self.request, {PLAN_CONTEXT_PENDING_USER: request.user}) | ||||||
|  |         self.request.session[SESSION_KEY_PLAN] = plan | ||||||
|  |         return redirect_with_qs( | ||||||
|  |             "authentik_flows:flow-executor-shell", | ||||||
|  |             self.request.GET, | ||||||
|  |             flow_slug=flow.slug, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FlowImportView(LoginRequiredMixin, FormView): | ||||||
|  |     """Import flow from JSON Export; only allowed for superusers | ||||||
|  |     as these flows can contain python code""" | ||||||
|  |  | ||||||
|  |     form_class = FlowImportForm | ||||||
|  |     template_name = "administration/flow/import.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:flows") | ||||||
|  |  | ||||||
|  |     def dispatch(self, request, *args, **kwargs): | ||||||
|  |         if not request.user.is_superuser: | ||||||
|  |             return self.handle_no_permission() | ||||||
|  |         return super().dispatch(request, *args, **kwargs) | ||||||
|  |  | ||||||
|  |     def form_valid(self, form: FlowImportForm) -> HttpResponse: | ||||||
|  |         importer = FlowImporter(form.cleaned_data["flow"].read().decode()) | ||||||
|  |         successful = importer.apply() | ||||||
|  |         if not successful: | ||||||
|  |             messages.error(self.request, _("Failed to import flow.")) | ||||||
|  |         else: | ||||||
|  |             messages.success(self.request, _("Successfully imported flow.")) | ||||||
|  |         return super().form_valid(form) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FlowExportView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): | ||||||
|  |     """Export Flow""" | ||||||
|  |  | ||||||
|  |     model = Flow | ||||||
|  |     permission_required = "authentik_flows.export_flow" | ||||||
|  |  | ||||||
|  |     # pylint: disable=unused-argument | ||||||
|  |     def get(self, request: HttpRequest, pk: str) -> HttpResponse: | ||||||
|  |         """Debug exectue flow, setting the current user as pending user""" | ||||||
|  |         flow: Flow = self.get_object() | ||||||
|  |         exporter = FlowExporter(flow) | ||||||
|  |         response = JsonResponse(exporter.export(), encoder=DataclassEncoder, safe=False) | ||||||
|  |         response["Content-Disposition"] = f'attachment; filename="{flow.slug}.akflow"' | ||||||
|  |         return response | ||||||
							
								
								
									
										83
									
								
								authentik/admin/views/groups.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								authentik/admin/views/groups.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | |||||||
|  | """authentik Group administration""" | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from django.views.generic import ListView, UpdateView | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.core.forms.groups import GroupForm | ||||||
|  | from authentik.core.models import Group | ||||||
|  | from authentik.lib.views import CreateAssignPermView | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GroupListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     ListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all groups""" | ||||||
|  |  | ||||||
|  |     model = Group | ||||||
|  |     permission_required = "authentik_core.view_group" | ||||||
|  |     ordering = "name" | ||||||
|  |     template_name = "administration/group/list.html" | ||||||
|  |     search_fields = ["name", "attributes"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GroupCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     CreateAssignPermView, | ||||||
|  | ): | ||||||
|  |     """Create new Group""" | ||||||
|  |  | ||||||
|  |     model = Group | ||||||
|  |     form_class = GroupForm | ||||||
|  |     permission_required = "authentik_core.add_group" | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:groups") | ||||||
|  |     success_message = _("Successfully created Group") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GroupUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     UpdateView, | ||||||
|  | ): | ||||||
|  |     """Update group""" | ||||||
|  |  | ||||||
|  |     model = Group | ||||||
|  |     form_class = GroupForm | ||||||
|  |     permission_required = "authentik_core.change_group" | ||||||
|  |  | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:groups") | ||||||
|  |     success_message = _("Successfully updated Group") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GroupDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): | ||||||
|  |     """Delete group""" | ||||||
|  |  | ||||||
|  |     model = Group | ||||||
|  |     permission_required = "authentik_flows.delete_group" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:groups") | ||||||
|  |     success_message = _("Successfully deleted Group") | ||||||
							
								
								
									
										93
									
								
								authentik/admin/views/outposts.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								authentik/admin/views/outposts.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | |||||||
|  | """authentik Outpost administration""" | ||||||
|  | from dataclasses import asdict | ||||||
|  | from typing import Any, Dict | ||||||
|  |  | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from django.views.generic import ListView, UpdateView | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.lib.views import CreateAssignPermView | ||||||
|  | from authentik.outposts.forms import OutpostForm | ||||||
|  | from authentik.outposts.models import Outpost, OutpostConfig | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OutpostListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     ListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all outposts""" | ||||||
|  |  | ||||||
|  |     model = Outpost | ||||||
|  |     permission_required = "authentik_outposts.view_outpost" | ||||||
|  |     ordering = "name" | ||||||
|  |     template_name = "administration/outpost/list.html" | ||||||
|  |     search_fields = ["name", "_config"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OutpostCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     CreateAssignPermView, | ||||||
|  | ): | ||||||
|  |     """Create new Outpost""" | ||||||
|  |  | ||||||
|  |     model = Outpost | ||||||
|  |     form_class = OutpostForm | ||||||
|  |     permission_required = "authentik_outposts.add_outpost" | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:outposts") | ||||||
|  |     success_message = _("Successfully created Outpost") | ||||||
|  |  | ||||||
|  |     def get_initial(self) -> Dict[str, Any]: | ||||||
|  |         return { | ||||||
|  |             "_config": asdict( | ||||||
|  |                 OutpostConfig(authentik_host=self.request.build_absolute_uri("/")) | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OutpostUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     UpdateView, | ||||||
|  | ): | ||||||
|  |     """Update outpost""" | ||||||
|  |  | ||||||
|  |     model = Outpost | ||||||
|  |     form_class = OutpostForm | ||||||
|  |     permission_required = "authentik_outposts.change_outpost" | ||||||
|  |  | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:outposts") | ||||||
|  |     success_message = _("Successfully updated Outpost") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OutpostDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): | ||||||
|  |     """Delete outpost""" | ||||||
|  |  | ||||||
|  |     model = Outpost | ||||||
|  |     permission_required = "authentik_outposts.delete_outpost" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:outposts") | ||||||
|  |     success_message = _("Successfully deleted Outpost") | ||||||
							
								
								
									
										83
									
								
								authentik/admin/views/outposts_service_connections.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								authentik/admin/views/outposts_service_connections.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | |||||||
|  | """authentik OutpostServiceConnection administration""" | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     InheritanceCreateView, | ||||||
|  |     InheritanceListView, | ||||||
|  |     InheritanceUpdateView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.outposts.models import OutpostServiceConnection | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OutpostServiceConnectionListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     InheritanceListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all outpost-service-connections""" | ||||||
|  |  | ||||||
|  |     model = OutpostServiceConnection | ||||||
|  |     permission_required = "authentik_outposts.add_outpostserviceconnection" | ||||||
|  |     template_name = "administration/outpost_service_connection/list.html" | ||||||
|  |     ordering = "pk" | ||||||
|  |     search_fields = ["pk", "name"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OutpostServiceConnectionCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     InheritanceCreateView, | ||||||
|  | ): | ||||||
|  |     """Create new OutpostServiceConnection""" | ||||||
|  |  | ||||||
|  |     model = OutpostServiceConnection | ||||||
|  |     permission_required = "authentik_outposts.add_outpostserviceconnection" | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:outpost-service-connections") | ||||||
|  |     success_message = _("Successfully created OutpostServiceConnection") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OutpostServiceConnectionUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     InheritanceUpdateView, | ||||||
|  | ): | ||||||
|  |     """Update outpostserviceconnection""" | ||||||
|  |  | ||||||
|  |     model = OutpostServiceConnection | ||||||
|  |     permission_required = "authentik_outposts.change_outpostserviceconnection" | ||||||
|  |  | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:outpost-service-connections") | ||||||
|  |     success_message = _("Successfully updated OutpostServiceConnection") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OutpostServiceConnectionDeleteView( | ||||||
|  |     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView | ||||||
|  | ): | ||||||
|  |     """Delete outpostserviceconnection""" | ||||||
|  |  | ||||||
|  |     model = OutpostServiceConnection | ||||||
|  |     permission_required = "authentik_outposts.delete_outpostserviceconnection" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:outpost-service-connections") | ||||||
|  |     success_message = _("Successfully deleted OutpostServiceConnection") | ||||||
							
								
								
									
										45
									
								
								authentik/admin/views/overview.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								authentik/admin/views/overview.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | |||||||
|  | """authentik administration overview""" | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.core.cache import cache | ||||||
|  | from django.http.request import HttpRequest | ||||||
|  | from django.http.response import HttpResponse | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from django.views.generic import FormView | ||||||
|  | from structlog import get_logger | ||||||
|  |  | ||||||
|  | from authentik.admin.forms.overview import FlowCacheClearForm, PolicyCacheClearForm | ||||||
|  | from authentik.admin.mixins import AdminRequiredMixin | ||||||
|  |  | ||||||
|  | LOGGER = get_logger() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PolicyCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView): | ||||||
|  |     """View to clear Policy cache""" | ||||||
|  |  | ||||||
|  |     form_class = PolicyCacheClearForm | ||||||
|  |  | ||||||
|  |     template_name = "generic/form_non_model.html" | ||||||
|  |     success_url = "/" | ||||||
|  |     success_message = _("Successfully cleared Policy cache") | ||||||
|  |  | ||||||
|  |     def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: | ||||||
|  |         keys = cache.keys("policy_*") | ||||||
|  |         cache.delete_many(keys) | ||||||
|  |         LOGGER.debug("Cleared Policy cache", keys=len(keys)) | ||||||
|  |         return super().post(request, *args, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FlowCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView): | ||||||
|  |     """View to clear Flow cache""" | ||||||
|  |  | ||||||
|  |     form_class = FlowCacheClearForm | ||||||
|  |  | ||||||
|  |     template_name = "generic/form_non_model.html" | ||||||
|  |     success_url = "/" | ||||||
|  |     success_message = _("Successfully cleared Flow cache") | ||||||
|  |  | ||||||
|  |     def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: | ||||||
|  |         keys = cache.keys("flow_*") | ||||||
|  |         cache.delete_many(keys) | ||||||
|  |         LOGGER.debug("Cleared flow cache", keys=len(keys)) | ||||||
|  |         return super().post(request, *args, **kwargs) | ||||||
							
								
								
									
										129
									
								
								authentik/admin/views/policies.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								authentik/admin/views/policies.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,129 @@ | |||||||
|  | """authentik Policy administration""" | ||||||
|  | from typing import Any, Dict | ||||||
|  |  | ||||||
|  | from django.contrib import messages | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.db.models import QuerySet | ||||||
|  | from django.http import HttpResponse | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from django.views.generic import FormView | ||||||
|  | from django.views.generic.detail import DetailView | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.forms.policies import PolicyTestForm | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     InheritanceCreateView, | ||||||
|  |     InheritanceListView, | ||||||
|  |     InheritanceUpdateView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.policies.models import Policy, PolicyBinding | ||||||
|  | from authentik.policies.process import PolicyProcess, PolicyRequest | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PolicyListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     InheritanceListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all policies""" | ||||||
|  |  | ||||||
|  |     model = Policy | ||||||
|  |     permission_required = "authentik_policies.view_policy" | ||||||
|  |     ordering = "name" | ||||||
|  |     template_name = "administration/policy/list.html" | ||||||
|  |     search_fields = ["name"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PolicyCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     InheritanceCreateView, | ||||||
|  | ): | ||||||
|  |     """Create new Policy""" | ||||||
|  |  | ||||||
|  |     model = Policy | ||||||
|  |     permission_required = "authentik_policies.add_policy" | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:policies") | ||||||
|  |     success_message = _("Successfully created Policy") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PolicyUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     InheritanceUpdateView, | ||||||
|  | ): | ||||||
|  |     """Update policy""" | ||||||
|  |  | ||||||
|  |     model = Policy | ||||||
|  |     permission_required = "authentik_policies.change_policy" | ||||||
|  |  | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:policies") | ||||||
|  |     success_message = _("Successfully updated Policy") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PolicyDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): | ||||||
|  |     """Delete policy""" | ||||||
|  |  | ||||||
|  |     model = Policy | ||||||
|  |     permission_required = "authentik_policies.delete_policy" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:policies") | ||||||
|  |     success_message = _("Successfully deleted Policy") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, FormView): | ||||||
|  |     """View to test policy(s)""" | ||||||
|  |  | ||||||
|  |     model = Policy | ||||||
|  |     form_class = PolicyTestForm | ||||||
|  |     permission_required = "authentik_policies.view_policy" | ||||||
|  |     template_name = "administration/policy/test.html" | ||||||
|  |     object = None | ||||||
|  |  | ||||||
|  |     def get_object(self, queryset=None) -> QuerySet: | ||||||
|  |         return ( | ||||||
|  |             Policy.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first() | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: | ||||||
|  |         kwargs["policy"] = self.get_object() | ||||||
|  |         return super().get_context_data(**kwargs) | ||||||
|  |  | ||||||
|  |     def post(self, *args, **kwargs) -> HttpResponse: | ||||||
|  |         self.object = self.get_object() | ||||||
|  |         return super().post(*args, **kwargs) | ||||||
|  |  | ||||||
|  |     def form_valid(self, form: PolicyTestForm) -> HttpResponse: | ||||||
|  |         policy = self.get_object() | ||||||
|  |         user = form.cleaned_data.get("user") | ||||||
|  |  | ||||||
|  |         p_request = PolicyRequest(user) | ||||||
|  |         p_request.http_request = self.request | ||||||
|  |         p_request.context = form.cleaned_data | ||||||
|  |  | ||||||
|  |         proc = PolicyProcess(PolicyBinding(policy=policy), p_request, None) | ||||||
|  |         result = proc.execute() | ||||||
|  |         if result.passing: | ||||||
|  |             messages.success(self.request, _("User successfully passed policy.")) | ||||||
|  |         else: | ||||||
|  |             messages.error(self.request, _("User didn't pass policy.")) | ||||||
|  |         return self.render_to_response(self.get_context_data(form=form, result=result)) | ||||||
							
								
								
									
										117
									
								
								authentik/admin/views/policies_bindings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								authentik/admin/views/policies_bindings.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | |||||||
|  | """authentik PolicyBinding administration""" | ||||||
|  | from typing import Any | ||||||
|  |  | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.db.models import Max, QuerySet | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from django.views.generic import ListView, UpdateView | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  | from guardian.shortcuts import get_objects_for_user | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.lib.views import CreateAssignPermView | ||||||
|  | from authentik.policies.forms import PolicyBindingForm | ||||||
|  | from authentik.policies.models import PolicyBinding, PolicyBindingModel | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PolicyBindingListView( | ||||||
|  |     LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView | ||||||
|  | ): | ||||||
|  |     """Show list of all policies""" | ||||||
|  |  | ||||||
|  |     model = PolicyBinding | ||||||
|  |     permission_required = "authentik_policies.view_policybinding" | ||||||
|  |     ordering = ["order", "target"] | ||||||
|  |     template_name = "administration/policy_binding/list.html" | ||||||
|  |  | ||||||
|  |     def get_queryset(self) -> QuerySet: | ||||||
|  |         # Since `select_subclasses` does not work with a foreign key, we have to do two queries here | ||||||
|  |         # First, get all pbm objects that have bindings attached | ||||||
|  |         objects = ( | ||||||
|  |             get_objects_for_user( | ||||||
|  |                 self.request.user, "authentik_policies.view_policybindingmodel" | ||||||
|  |             ) | ||||||
|  |             .filter(policies__isnull=False) | ||||||
|  |             .select_subclasses() | ||||||
|  |             .select_related() | ||||||
|  |             .order_by("pk") | ||||||
|  |         ) | ||||||
|  |         for pbm in objects: | ||||||
|  |             pbm.bindings = get_objects_for_user( | ||||||
|  |                 self.request.user, self.permission_required | ||||||
|  |             ).filter(target__pk=pbm.pbm_uuid) | ||||||
|  |         return objects | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PolicyBindingCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     CreateAssignPermView, | ||||||
|  | ): | ||||||
|  |     """Create new PolicyBinding""" | ||||||
|  |  | ||||||
|  |     model = PolicyBinding | ||||||
|  |     permission_required = "authentik_policies.add_policybinding" | ||||||
|  |     form_class = PolicyBindingForm | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:policies-bindings") | ||||||
|  |     success_message = _("Successfully created PolicyBinding") | ||||||
|  |  | ||||||
|  |     def get_initial(self) -> dict[str, Any]: | ||||||
|  |         if "target" in self.request.GET: | ||||||
|  |             initial_target_pk = self.request.GET["target"] | ||||||
|  |             targets = PolicyBindingModel.objects.filter( | ||||||
|  |                 pk=initial_target_pk | ||||||
|  |             ).select_subclasses() | ||||||
|  |             if not targets.exists(): | ||||||
|  |                 return {} | ||||||
|  |             max_order = PolicyBinding.objects.filter(target=targets.first()).aggregate( | ||||||
|  |                 Max("order") | ||||||
|  |             )["order__max"] | ||||||
|  |             if not isinstance(max_order, int): | ||||||
|  |                 max_order = -1 | ||||||
|  |             return {"target": targets.first(), "order": max_order + 1} | ||||||
|  |         return super().get_initial() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PolicyBindingUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     UpdateView, | ||||||
|  | ): | ||||||
|  |     """Update policybinding""" | ||||||
|  |  | ||||||
|  |     model = PolicyBinding | ||||||
|  |     permission_required = "authentik_policies.change_policybinding" | ||||||
|  |     form_class = PolicyBindingForm | ||||||
|  |  | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:policies-bindings") | ||||||
|  |     success_message = _("Successfully updated PolicyBinding") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PolicyBindingDeleteView( | ||||||
|  |     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView | ||||||
|  | ): | ||||||
|  |     """Delete policybinding""" | ||||||
|  |  | ||||||
|  |     model = PolicyBinding | ||||||
|  |     permission_required = "authentik_policies.delete_policybinding" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:policies-bindings") | ||||||
|  |     success_message = _("Successfully deleted PolicyBinding") | ||||||
							
								
								
									
										83
									
								
								authentik/admin/views/property_mappings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								authentik/admin/views/property_mappings.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | |||||||
|  | """authentik PropertyMapping administration""" | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     InheritanceCreateView, | ||||||
|  |     InheritanceListView, | ||||||
|  |     InheritanceUpdateView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.core.models import PropertyMapping | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PropertyMappingListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     InheritanceListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all property_mappings""" | ||||||
|  |  | ||||||
|  |     model = PropertyMapping | ||||||
|  |     permission_required = "authentik_core.view_propertymapping" | ||||||
|  |     template_name = "administration/property_mapping/list.html" | ||||||
|  |     ordering = "name" | ||||||
|  |     search_fields = ["name", "expression"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PropertyMappingCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     InheritanceCreateView, | ||||||
|  | ): | ||||||
|  |     """Create new PropertyMapping""" | ||||||
|  |  | ||||||
|  |     model = PropertyMapping | ||||||
|  |     permission_required = "authentik_core.add_propertymapping" | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:property-mappings") | ||||||
|  |     success_message = _("Successfully created Property Mapping") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PropertyMappingUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     InheritanceUpdateView, | ||||||
|  | ): | ||||||
|  |     """Update property_mapping""" | ||||||
|  |  | ||||||
|  |     model = PropertyMapping | ||||||
|  |     permission_required = "authentik_core.change_propertymapping" | ||||||
|  |  | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:property-mappings") | ||||||
|  |     success_message = _("Successfully updated Property Mapping") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PropertyMappingDeleteView( | ||||||
|  |     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView | ||||||
|  | ): | ||||||
|  |     """Delete property_mapping""" | ||||||
|  |  | ||||||
|  |     model = PropertyMapping | ||||||
|  |     permission_required = "authentik_core.delete_propertymapping" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:property-mappings") | ||||||
|  |     success_message = _("Successfully deleted Property Mapping") | ||||||
							
								
								
									
										83
									
								
								authentik/admin/views/providers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								authentik/admin/views/providers.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | |||||||
|  | """authentik Provider administration""" | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     InheritanceCreateView, | ||||||
|  |     InheritanceListView, | ||||||
|  |     InheritanceUpdateView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.core.models import Provider | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ProviderListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     InheritanceListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all providers""" | ||||||
|  |  | ||||||
|  |     model = Provider | ||||||
|  |     permission_required = "authentik_core.add_provider" | ||||||
|  |     template_name = "administration/provider/list.html" | ||||||
|  |     ordering = "pk" | ||||||
|  |     search_fields = ["pk", "name"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ProviderCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     InheritanceCreateView, | ||||||
|  | ): | ||||||
|  |     """Create new Provider""" | ||||||
|  |  | ||||||
|  |     model = Provider | ||||||
|  |     permission_required = "authentik_core.add_provider" | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:providers") | ||||||
|  |     success_message = _("Successfully created Provider") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ProviderUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     InheritanceUpdateView, | ||||||
|  | ): | ||||||
|  |     """Update provider""" | ||||||
|  |  | ||||||
|  |     model = Provider | ||||||
|  |     permission_required = "authentik_core.change_provider" | ||||||
|  |  | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:providers") | ||||||
|  |     success_message = _("Successfully updated Provider") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ProviderDeleteView( | ||||||
|  |     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView | ||||||
|  | ): | ||||||
|  |     """Delete provider""" | ||||||
|  |  | ||||||
|  |     model = Provider | ||||||
|  |     permission_required = "authentik_core.delete_provider" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:providers") | ||||||
|  |     success_message = _("Successfully deleted Provider") | ||||||
							
								
								
									
										81
									
								
								authentik/admin/views/sources.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								authentik/admin/views/sources.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,81 @@ | |||||||
|  | """authentik Source administration""" | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     InheritanceCreateView, | ||||||
|  |     InheritanceListView, | ||||||
|  |     InheritanceUpdateView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.core.models import Source | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SourceListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     InheritanceListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all sources""" | ||||||
|  |  | ||||||
|  |     model = Source | ||||||
|  |     permission_required = "authentik_core.view_source" | ||||||
|  |     ordering = "name" | ||||||
|  |     template_name = "administration/source/list.html" | ||||||
|  |     search_fields = ["name", "slug"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SourceCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     InheritanceCreateView, | ||||||
|  | ): | ||||||
|  |     """Create new Source""" | ||||||
|  |  | ||||||
|  |     model = Source | ||||||
|  |     permission_required = "authentik_core.add_source" | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:sources") | ||||||
|  |     success_message = _("Successfully created Source") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SourceUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     InheritanceUpdateView, | ||||||
|  | ): | ||||||
|  |     """Update source""" | ||||||
|  |  | ||||||
|  |     model = Source | ||||||
|  |     permission_required = "authentik_core.change_source" | ||||||
|  |  | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:sources") | ||||||
|  |     success_message = _("Successfully updated Source") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SourceDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): | ||||||
|  |     """Delete source""" | ||||||
|  |  | ||||||
|  |     model = Source | ||||||
|  |     permission_required = "authentik_core.delete_source" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:sources") | ||||||
|  |     success_message = _("Successfully deleted Source") | ||||||
							
								
								
									
										79
									
								
								authentik/admin/views/stages.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								authentik/admin/views/stages.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | |||||||
|  | """authentik Stage administration""" | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     InheritanceCreateView, | ||||||
|  |     InheritanceListView, | ||||||
|  |     InheritanceUpdateView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.flows.models import Stage | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class StageListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     InheritanceListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all stages""" | ||||||
|  |  | ||||||
|  |     model = Stage | ||||||
|  |     template_name = "administration/stage/list.html" | ||||||
|  |     permission_required = "authentik_flows.view_stage" | ||||||
|  |     ordering = "name" | ||||||
|  |     search_fields = ["name"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class StageCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     InheritanceCreateView, | ||||||
|  | ): | ||||||
|  |     """Create new Stage""" | ||||||
|  |  | ||||||
|  |     model = Stage | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     permission_required = "authentik_flows.add_stage" | ||||||
|  |  | ||||||
|  |     success_url = reverse_lazy("authentik_admin:stages") | ||||||
|  |     success_message = _("Successfully created Stage") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class StageUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     InheritanceUpdateView, | ||||||
|  | ): | ||||||
|  |     """Update stage""" | ||||||
|  |  | ||||||
|  |     model = Stage | ||||||
|  |     permission_required = "authentik_flows.update_application" | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:stages") | ||||||
|  |     success_message = _("Successfully updated Stage") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class StageDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): | ||||||
|  |     """Delete stage""" | ||||||
|  |  | ||||||
|  |     model = Stage | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     permission_required = "authentik_flows.delete_stage" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:stages") | ||||||
|  |     success_message = _("Successfully deleted Stage") | ||||||
							
								
								
									
										96
									
								
								authentik/admin/views/stages_bindings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								authentik/admin/views/stages_bindings.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | |||||||
|  | """authentik StageBinding administration""" | ||||||
|  | from typing import Any | ||||||
|  |  | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.db.models import Max | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from django.views.generic import ListView, UpdateView | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.flows.forms import FlowStageBindingForm | ||||||
|  | from authentik.flows.models import Flow, FlowStageBinding | ||||||
|  | from authentik.lib.views import CreateAssignPermView | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class StageBindingListView( | ||||||
|  |     LoginRequiredMixin, PermissionListMixin, UserPaginateListMixin, ListView | ||||||
|  | ): | ||||||
|  |     """Show list of all flows""" | ||||||
|  |  | ||||||
|  |     model = FlowStageBinding | ||||||
|  |     permission_required = "authentik_flows.view_flowstagebinding" | ||||||
|  |     ordering = ["target", "order"] | ||||||
|  |     template_name = "administration/stage_binding/list.html" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class StageBindingCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     CreateAssignPermView, | ||||||
|  | ): | ||||||
|  |     """Create new StageBinding""" | ||||||
|  |  | ||||||
|  |     model = FlowStageBinding | ||||||
|  |     permission_required = "authentik_flows.add_flowstagebinding" | ||||||
|  |     form_class = FlowStageBindingForm | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:stage-bindings") | ||||||
|  |     success_message = _("Successfully created StageBinding") | ||||||
|  |  | ||||||
|  |     def get_initial(self) -> dict[str, Any]: | ||||||
|  |         if "target" in self.request.GET: | ||||||
|  |             initial_target_pk = self.request.GET["target"] | ||||||
|  |             targets = Flow.objects.filter(pk=initial_target_pk).select_subclasses() | ||||||
|  |             if not targets.exists(): | ||||||
|  |                 return {} | ||||||
|  |             max_order = FlowStageBinding.objects.filter( | ||||||
|  |                 target=targets.first() | ||||||
|  |             ).aggregate(Max("order"))["order__max"] | ||||||
|  |             if not isinstance(max_order, int): | ||||||
|  |                 max_order = -1 | ||||||
|  |             return {"target": targets.first(), "order": max_order + 1} | ||||||
|  |         return super().get_initial() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class StageBindingUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     UpdateView, | ||||||
|  | ): | ||||||
|  |     """Update FlowStageBinding""" | ||||||
|  |  | ||||||
|  |     model = FlowStageBinding | ||||||
|  |     permission_required = "authentik_flows.change_flowstagebinding" | ||||||
|  |     form_class = FlowStageBindingForm | ||||||
|  |  | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:stage-bindings") | ||||||
|  |     success_message = _("Successfully updated StageBinding") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class StageBindingDeleteView( | ||||||
|  |     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView | ||||||
|  | ): | ||||||
|  |     """Delete FlowStageBinding""" | ||||||
|  |  | ||||||
|  |     model = FlowStageBinding | ||||||
|  |     permission_required = "authentik_flows.delete_flowstagebinding" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:stage-bindings") | ||||||
|  |     success_message = _("Successfully deleted FlowStageBinding") | ||||||
							
								
								
									
										76
									
								
								authentik/admin/views/stages_invitations.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								authentik/admin/views/stages_invitations.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | |||||||
|  | """authentik Invitation administration""" | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.http import HttpResponseRedirect | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from django.views.generic import ListView | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.lib.views import CreateAssignPermView | ||||||
|  | from authentik.stages.invitation.forms import InvitationForm | ||||||
|  | from authentik.stages.invitation.models import Invitation | ||||||
|  | from authentik.stages.invitation.signals import invitation_created | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class InvitationListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     ListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all invitations""" | ||||||
|  |  | ||||||
|  |     model = Invitation | ||||||
|  |     permission_required = "authentik_stages_invitation.view_invitation" | ||||||
|  |     template_name = "administration/stage_invitation/list.html" | ||||||
|  |     ordering = "-expires" | ||||||
|  |     search_fields = ["created_by__username", "expires", "fixed_data"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class InvitationCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     CreateAssignPermView, | ||||||
|  | ): | ||||||
|  |     """Create new Invitation""" | ||||||
|  |  | ||||||
|  |     model = Invitation | ||||||
|  |     form_class = InvitationForm | ||||||
|  |     permission_required = "authentik_stages_invitation.add_invitation" | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:stage-invitations") | ||||||
|  |     success_message = _("Successfully created Invitation") | ||||||
|  |  | ||||||
|  |     def form_valid(self, form): | ||||||
|  |         obj = form.save(commit=False) | ||||||
|  |         obj.created_by = self.request.user | ||||||
|  |         obj.save() | ||||||
|  |         invitation_created.send(sender=self, request=self.request, invitation=obj) | ||||||
|  |         return HttpResponseRedirect(self.success_url) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class InvitationDeleteView( | ||||||
|  |     LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView | ||||||
|  | ): | ||||||
|  |     """Delete invitation""" | ||||||
|  |  | ||||||
|  |     model = Invitation | ||||||
|  |     permission_required = "authentik_stages_invitation.delete_invitation" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:stage-invitations") | ||||||
|  |     success_message = _("Successfully deleted Invitation") | ||||||
							
								
								
									
										88
									
								
								authentik/admin/views/stages_prompts.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								authentik/admin/views/stages_prompts.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | |||||||
|  | """authentik Prompt administration""" | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from django.views.generic import ListView, UpdateView | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.lib.views import CreateAssignPermView | ||||||
|  | from authentik.stages.prompt.forms import PromptAdminForm | ||||||
|  | from authentik.stages.prompt.models import Prompt | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PromptListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     ListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all prompts""" | ||||||
|  |  | ||||||
|  |     model = Prompt | ||||||
|  |     permission_required = "authentik_stages_prompt.view_prompt" | ||||||
|  |     ordering = "order" | ||||||
|  |     template_name = "administration/stage_prompt/list.html" | ||||||
|  |     search_fields = [ | ||||||
|  |         "field_key", | ||||||
|  |         "label", | ||||||
|  |         "type", | ||||||
|  |         "placeholder", | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PromptCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     CreateAssignPermView, | ||||||
|  | ): | ||||||
|  |     """Create new Prompt""" | ||||||
|  |  | ||||||
|  |     model = Prompt | ||||||
|  |     form_class = PromptAdminForm | ||||||
|  |     permission_required = "authentik_stages_prompt.add_prompt" | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:stage-prompts") | ||||||
|  |     success_message = _("Successfully created Prompt") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PromptUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     UpdateView, | ||||||
|  | ): | ||||||
|  |     """Update prompt""" | ||||||
|  |  | ||||||
|  |     model = Prompt | ||||||
|  |     form_class = PromptAdminForm | ||||||
|  |     permission_required = "authentik_stages_prompt.change_prompt" | ||||||
|  |  | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:stage-prompts") | ||||||
|  |     success_message = _("Successfully updated Prompt") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PromptDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): | ||||||
|  |     """Delete prompt""" | ||||||
|  |  | ||||||
|  |     model = Prompt | ||||||
|  |     permission_required = "authentik_stages_prompt.delete_prompt" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:stage-prompts") | ||||||
|  |     success_message = _("Successfully deleted Prompt") | ||||||
							
								
								
									
										23
									
								
								authentik/admin/views/tasks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								authentik/admin/views/tasks.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | |||||||
|  | """authentik Tasks List""" | ||||||
|  | from typing import Any, Dict | ||||||
|  |  | ||||||
|  | from django.views.generic.base import TemplateView | ||||||
|  |  | ||||||
|  | from authentik.admin.mixins import AdminRequiredMixin | ||||||
|  | from authentik.lib.tasks import TaskInfo, TaskResultStatus | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TaskListView(AdminRequiredMixin, TemplateView): | ||||||
|  |     """Show list of all background tasks""" | ||||||
|  |  | ||||||
|  |     template_name = "administration/task/list.html" | ||||||
|  |  | ||||||
|  |     def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: | ||||||
|  |         kwargs = super().get_context_data(**kwargs) | ||||||
|  |         kwargs["object_list"] = sorted( | ||||||
|  |             TaskInfo.all().values(), key=lambda x: x.task_name | ||||||
|  |         ) | ||||||
|  |         kwargs["task_successful"] = TaskResultStatus.SUCCESSFUL | ||||||
|  |         kwargs["task_warning"] = TaskResultStatus.WARNING | ||||||
|  |         kwargs["task_error"] = TaskResultStatus.ERROR | ||||||
|  |         return kwargs | ||||||
							
								
								
									
										45
									
								
								authentik/admin/views/tokens.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								authentik/admin/views/tokens.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | |||||||
|  | """authentik Token administration""" | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from django.views.generic import ListView | ||||||
|  | from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||||
|  |  | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     DeleteMessageView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.core.models import Token | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TokenListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     ListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all tokens""" | ||||||
|  |  | ||||||
|  |     model = Token | ||||||
|  |     permission_required = "authentik_core.view_token" | ||||||
|  |     ordering = "expires" | ||||||
|  |     template_name = "administration/token/list.html" | ||||||
|  |     search_fields = [ | ||||||
|  |         "identifier", | ||||||
|  |         "intent", | ||||||
|  |         "user__username", | ||||||
|  |         "description", | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): | ||||||
|  |     """Delete token""" | ||||||
|  |  | ||||||
|  |     model = Token | ||||||
|  |     permission_required = "authentik_core.delete_token" | ||||||
|  |  | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:tokens") | ||||||
|  |     success_message = _("Successfully deleted Token") | ||||||
							
								
								
									
										168
									
								
								authentik/admin/views/users.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								authentik/admin/views/users.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,168 @@ | |||||||
|  | """authentik User administration""" | ||||||
|  | from django.contrib import messages | ||||||
|  | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.contrib.auth.mixins import ( | ||||||
|  |     PermissionRequiredMixin as DjangoPermissionRequiredMixin, | ||||||
|  | ) | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.http import HttpRequest, HttpResponse | ||||||
|  | from django.http.response import HttpResponseRedirect | ||||||
|  | from django.shortcuts import redirect | ||||||
|  | from django.urls import reverse, reverse_lazy | ||||||
|  | from django.utils.http import urlencode | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  | from django.views.generic import DetailView, ListView, UpdateView | ||||||
|  | from guardian.mixins import ( | ||||||
|  |     PermissionListMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     get_anonymous_user, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from authentik.admin.forms.users import UserForm | ||||||
|  | from authentik.admin.views.utils import ( | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     DeleteMessageView, | ||||||
|  |     SearchListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  | ) | ||||||
|  | from authentik.core.models import Token, User | ||||||
|  | from authentik.lib.views import CreateAssignPermView | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserListView( | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionListMixin, | ||||||
|  |     UserPaginateListMixin, | ||||||
|  |     SearchListMixin, | ||||||
|  |     ListView, | ||||||
|  | ): | ||||||
|  |     """Show list of all users""" | ||||||
|  |  | ||||||
|  |     model = User | ||||||
|  |     permission_required = "authentik_core.view_user" | ||||||
|  |     ordering = "username" | ||||||
|  |     template_name = "administration/user/list.html" | ||||||
|  |     search_fields = ["username", "name", "attributes"] | ||||||
|  |  | ||||||
|  |     def get_queryset(self): | ||||||
|  |         return super().get_queryset().exclude(pk=get_anonymous_user().pk) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserCreateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     DjangoPermissionRequiredMixin, | ||||||
|  |     CreateAssignPermView, | ||||||
|  | ): | ||||||
|  |     """Create user""" | ||||||
|  |  | ||||||
|  |     model = User | ||||||
|  |     form_class = UserForm | ||||||
|  |     permission_required = "authentik_core.add_user" | ||||||
|  |  | ||||||
|  |     template_name = "generic/create.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:users") | ||||||
|  |     success_message = _("Successfully created User") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserUpdateView( | ||||||
|  |     SuccessMessageMixin, | ||||||
|  |     BackSuccessUrlMixin, | ||||||
|  |     LoginRequiredMixin, | ||||||
|  |     PermissionRequiredMixin, | ||||||
|  |     UpdateView, | ||||||
|  | ): | ||||||
|  |     """Update user""" | ||||||
|  |  | ||||||
|  |     model = User | ||||||
|  |     form_class = UserForm | ||||||
|  |     permission_required = "authentik_core.change_user" | ||||||
|  |  | ||||||
|  |     # By default the object's name is user which is used by other checks | ||||||
|  |     context_object_name = "object" | ||||||
|  |     template_name = "generic/update.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:users") | ||||||
|  |     success_message = _("Successfully updated User") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): | ||||||
|  |     """Delete user""" | ||||||
|  |  | ||||||
|  |     model = User | ||||||
|  |     permission_required = "authentik_core.delete_user" | ||||||
|  |  | ||||||
|  |     # By default the object's name is user which is used by other checks | ||||||
|  |     context_object_name = "object" | ||||||
|  |     template_name = "generic/delete.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:users") | ||||||
|  |     success_message = _("Successfully deleted User") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserDisableView( | ||||||
|  |     LoginRequiredMixin, PermissionRequiredMixin, BackSuccessUrlMixin, DeleteMessageView | ||||||
|  | ): | ||||||
|  |     """Disable user""" | ||||||
|  |  | ||||||
|  |     object: User | ||||||
|  |  | ||||||
|  |     model = User | ||||||
|  |     permission_required = "authentik_core.update_user" | ||||||
|  |  | ||||||
|  |     # By default the object's name is user which is used by other checks | ||||||
|  |     context_object_name = "object" | ||||||
|  |     template_name = "administration/user/disable.html" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:users") | ||||||
|  |     success_message = _("Successfully disabled User") | ||||||
|  |  | ||||||
|  |     def delete(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: | ||||||
|  |         self.object: User = self.get_object() | ||||||
|  |         success_url = self.get_success_url() | ||||||
|  |         self.object.is_active = False | ||||||
|  |         self.object.save() | ||||||
|  |         return HttpResponseRedirect(success_url) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserEnableView( | ||||||
|  |     LoginRequiredMixin, PermissionRequiredMixin, BackSuccessUrlMixin, DetailView | ||||||
|  | ): | ||||||
|  |     """Enable user""" | ||||||
|  |  | ||||||
|  |     object: User | ||||||
|  |  | ||||||
|  |     model = User | ||||||
|  |     permission_required = "authentik_core.update_user" | ||||||
|  |  | ||||||
|  |     # By default the object's name is user which is used by other checks | ||||||
|  |     context_object_name = "object" | ||||||
|  |     success_url = reverse_lazy("authentik_admin:users") | ||||||
|  |     success_message = _("Successfully enabled User") | ||||||
|  |  | ||||||
|  |     def get(self, request: HttpRequest, *args, **kwargs): | ||||||
|  |         self.object: User = self.get_object() | ||||||
|  |         success_url = self.get_success_url() | ||||||
|  |         self.object.is_active = True | ||||||
|  |         self.object.save() | ||||||
|  |         return HttpResponseRedirect(success_url) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): | ||||||
|  |     """Get Password reset link for user""" | ||||||
|  |  | ||||||
|  |     model = User | ||||||
|  |     permission_required = "authentik_core.reset_user_password" | ||||||
|  |  | ||||||
|  |     def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: | ||||||
|  |         """Create token for user and return link""" | ||||||
|  |         super().get(request, *args, **kwargs) | ||||||
|  |         token, __ = Token.objects.get_or_create( | ||||||
|  |             identifier="password-reset-temp", user=self.object | ||||||
|  |         ) | ||||||
|  |         querystring = urlencode({"token": token.key}) | ||||||
|  |         link = request.build_absolute_uri( | ||||||
|  |             reverse("authentik_flows:default-recovery") + f"?{querystring}" | ||||||
|  |         ) | ||||||
|  |         messages.success( | ||||||
|  |             request, _("Password reset link: <pre>%(link)s</pre>" % {"link": link}) | ||||||
|  |         ) | ||||||
|  |         return redirect("authentik_admin:users") | ||||||
							
								
								
									
										124
									
								
								authentik/admin/views/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								authentik/admin/views/utils.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,124 @@ | |||||||
|  | """authentik admin util views""" | ||||||
|  | from typing import Any, Dict, List, Optional | ||||||
|  | from urllib.parse import urlparse | ||||||
|  |  | ||||||
|  | from django.contrib import messages | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.contrib.postgres.search import SearchQuery, SearchVector | ||||||
|  | from django.db.models import QuerySet | ||||||
|  | from django.http import Http404 | ||||||
|  | from django.http.request import HttpRequest | ||||||
|  | from django.views.generic import DeleteView, ListView, UpdateView | ||||||
|  | from django.views.generic.list import MultipleObjectMixin | ||||||
|  |  | ||||||
|  | from authentik.lib.utils.reflection import all_subclasses | ||||||
|  | from authentik.lib.views import CreateAssignPermView | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class DeleteMessageView(SuccessMessageMixin, DeleteView): | ||||||
|  |     """DeleteView which shows `self.success_message` on successful deletion""" | ||||||
|  |  | ||||||
|  |     def delete(self, request, *args, **kwargs): | ||||||
|  |         messages.success(self.request, self.success_message) | ||||||
|  |         return super().delete(request, *args, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class InheritanceListView(ListView): | ||||||
|  |     """ListView for objects using InheritanceManager""" | ||||||
|  |  | ||||||
|  |     def get_context_data(self, **kwargs): | ||||||
|  |         kwargs["types"] = {x.__name__: x for x in all_subclasses(self.model)} | ||||||
|  |         return super().get_context_data(**kwargs) | ||||||
|  |  | ||||||
|  |     def get_queryset(self): | ||||||
|  |         return super().get_queryset().select_subclasses() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SearchListMixin(MultipleObjectMixin): | ||||||
|  |     """Accept search query using `search` querystring parameter. Requires self.search_fields, | ||||||
|  |     a list of all fields to search. Can contain special lookups like __icontains""" | ||||||
|  |  | ||||||
|  |     search_fields: List[str] | ||||||
|  |  | ||||||
|  |     def get_queryset(self) -> QuerySet: | ||||||
|  |         queryset = super().get_queryset() | ||||||
|  |         if "search" in self.request.GET: | ||||||
|  |             raw_query = self.request.GET["search"] | ||||||
|  |             if raw_query == "": | ||||||
|  |                 # Empty query, don't search at all | ||||||
|  |                 return queryset | ||||||
|  |             search = SearchQuery(raw_query, search_type="websearch") | ||||||
|  |             return queryset.annotate(search=SearchVector(*self.search_fields)).filter( | ||||||
|  |                 search=search | ||||||
|  |             ) | ||||||
|  |         return queryset | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class InheritanceCreateView(CreateAssignPermView): | ||||||
|  |     """CreateView for objects using InheritanceManager""" | ||||||
|  |  | ||||||
|  |     def get_form_class(self): | ||||||
|  |         provider_type = self.request.GET.get("type") | ||||||
|  |         try: | ||||||
|  |             model = next( | ||||||
|  |                 x for x in all_subclasses(self.model) if x.__name__ == provider_type | ||||||
|  |             ) | ||||||
|  |         except StopIteration as exc: | ||||||
|  |             raise Http404 from exc | ||||||
|  |         return model().form | ||||||
|  |  | ||||||
|  |     def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: | ||||||
|  |         kwargs = super().get_context_data(**kwargs) | ||||||
|  |         form_cls = self.get_form_class() | ||||||
|  |         if hasattr(form_cls, "template_name"): | ||||||
|  |             kwargs["base_template"] = form_cls.template_name | ||||||
|  |         return kwargs | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class InheritanceUpdateView(UpdateView): | ||||||
|  |     """UpdateView for objects using InheritanceManager""" | ||||||
|  |  | ||||||
|  |     def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: | ||||||
|  |         kwargs = super().get_context_data(**kwargs) | ||||||
|  |         form_cls = self.get_form_class() | ||||||
|  |         if hasattr(form_cls, "template_name"): | ||||||
|  |             kwargs["base_template"] = form_cls.template_name | ||||||
|  |         return kwargs | ||||||
|  |  | ||||||
|  |     def get_form_class(self): | ||||||
|  |         return self.get_object().form | ||||||
|  |  | ||||||
|  |     def get_object(self, queryset=None): | ||||||
|  |         return ( | ||||||
|  |             self.model.objects.filter(pk=self.kwargs.get("pk")) | ||||||
|  |             .select_subclasses() | ||||||
|  |             .first() | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class BackSuccessUrlMixin: | ||||||
|  |     """Checks if a relative URL has been given as ?back param, and redirect to it. Otherwise | ||||||
|  |     default to self.success_url.""" | ||||||
|  |  | ||||||
|  |     request: HttpRequest | ||||||
|  |  | ||||||
|  |     success_url: Optional[str] | ||||||
|  |  | ||||||
|  |     def get_success_url(self) -> str: | ||||||
|  |         """get_success_url from FormMixin""" | ||||||
|  |         back_param = self.request.GET.get("back") | ||||||
|  |         if back_param: | ||||||
|  |             if not bool(urlparse(back_param).netloc): | ||||||
|  |                 return back_param | ||||||
|  |         return str(self.success_url) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserPaginateListMixin: | ||||||
|  |     """Get paginate_by value from user's attributes, defaulting to 15""" | ||||||
|  |  | ||||||
|  |     request: HttpRequest | ||||||
|  |  | ||||||
|  |     # pylint: disable=unused-argument | ||||||
|  |     def get_paginate_by(self, queryset: QuerySet) -> int: | ||||||
|  |         """get_paginate_by Function of ListView""" | ||||||
|  |         return self.request.user.attributes.get("paginate_by", 15) | ||||||
							
								
								
									
										58
									
								
								authentik/api/auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								authentik/api/auth.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | |||||||
|  | """API Authentication""" | ||||||
|  | from base64 import b64decode | ||||||
|  | from binascii import Error | ||||||
|  | from typing import Any, Optional, Tuple, Union | ||||||
|  |  | ||||||
|  | from rest_framework.authentication import BaseAuthentication, get_authorization_header | ||||||
|  | from rest_framework.request import Request | ||||||
|  | from structlog import get_logger | ||||||
|  |  | ||||||
|  | from authentik.core.models import Token, TokenIntents, User | ||||||
|  |  | ||||||
|  | LOGGER = get_logger() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def token_from_header(raw_header: bytes) -> Optional[Token]: | ||||||
|  |     """raw_header in the Format of `Basic dGVzdDp0ZXN0`""" | ||||||
|  |     auth_credentials = raw_header.decode() | ||||||
|  |     # Accept headers with Type format and without | ||||||
|  |     if " " in auth_credentials: | ||||||
|  |         auth_type, auth_credentials = auth_credentials.split() | ||||||
|  |         if auth_type.lower() != "basic": | ||||||
|  |             LOGGER.debug( | ||||||
|  |                 "Unsupported authentication type, denying", type=auth_type.lower() | ||||||
|  |             ) | ||||||
|  |             return None | ||||||
|  |     try: | ||||||
|  |         auth_credentials = b64decode(auth_credentials.encode()).decode() | ||||||
|  |     except (UnicodeDecodeError, Error): | ||||||
|  |         return None | ||||||
|  |     # Accept credentials with username and without | ||||||
|  |     if ":" in auth_credentials: | ||||||
|  |         _, password = auth_credentials.split(":") | ||||||
|  |     else: | ||||||
|  |         password = auth_credentials | ||||||
|  |     if password == "":  # nosec | ||||||
|  |         return None | ||||||
|  |     tokens = Token.filter_not_expired(key=password, intent=TokenIntents.INTENT_API) | ||||||
|  |     if not tokens.exists(): | ||||||
|  |         LOGGER.debug("Token not found") | ||||||
|  |         return None | ||||||
|  |     return tokens.first() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AuthentikTokenAuthentication(BaseAuthentication): | ||||||
|  |     """Token-based authentication using HTTP Basic authentication""" | ||||||
|  |  | ||||||
|  |     def authenticate(self, request: Request) -> Union[Tuple[User, Any], None]: | ||||||
|  |         """Token-based authentication using HTTP Basic authentication""" | ||||||
|  |         auth = get_authorization_header(request) | ||||||
|  |  | ||||||
|  |         token = token_from_header(auth) | ||||||
|  |         if not token: | ||||||
|  |             return None | ||||||
|  |  | ||||||
|  |         return (token.user, None) | ||||||
|  |  | ||||||
|  |     def authenticate_header(self, request: Request) -> str: | ||||||
|  |         return 'Basic realm="authentik"' | ||||||
| @ -1,57 +0,0 @@ | |||||||
| """API Authentication""" |  | ||||||
| from base64 import b64decode |  | ||||||
| from binascii import Error |  | ||||||
| from typing import Any, Optional, Union |  | ||||||
|  |  | ||||||
| from rest_framework.authentication import BaseAuthentication, get_authorization_header |  | ||||||
| from rest_framework.exceptions import AuthenticationFailed |  | ||||||
| from rest_framework.request import Request |  | ||||||
| from structlog.stdlib import get_logger |  | ||||||
|  |  | ||||||
| from authentik.core.models import Token, TokenIntents, User |  | ||||||
|  |  | ||||||
| LOGGER = get_logger() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # pylint: disable=too-many-return-statements |  | ||||||
| def token_from_header(raw_header: bytes) -> Optional[Token]: |  | ||||||
|     """raw_header in the Format of `Bearer dGVzdDp0ZXN0`""" |  | ||||||
|     auth_credentials = raw_header.decode() |  | ||||||
|     if auth_credentials == "": |  | ||||||
|         return None |  | ||||||
|     auth_type, auth_credentials = auth_credentials.split() |  | ||||||
|     if auth_type.lower() not in ["basic", "bearer"]: |  | ||||||
|         LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower()) |  | ||||||
|         raise AuthenticationFailed("Unsupported authentication type") |  | ||||||
|     password = auth_credentials |  | ||||||
|     if auth_type.lower() == "basic": |  | ||||||
|         try: |  | ||||||
|             auth_credentials = b64decode(auth_credentials.encode()).decode() |  | ||||||
|         except (UnicodeDecodeError, Error): |  | ||||||
|             raise AuthenticationFailed("Malformed header") |  | ||||||
|         # Accept credentials with username and without |  | ||||||
|         if ":" in auth_credentials: |  | ||||||
|             _, password = auth_credentials.split(":") |  | ||||||
|         else: |  | ||||||
|             password = auth_credentials |  | ||||||
|     if password == "":  # nosec |  | ||||||
|         raise AuthenticationFailed("Malformed header") |  | ||||||
|     tokens = Token.filter_not_expired(key=password, intent=TokenIntents.INTENT_API) |  | ||||||
|     if not tokens.exists(): |  | ||||||
|         raise AuthenticationFailed("Token invalid/expired") |  | ||||||
|     return tokens.first() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TokenAuthentication(BaseAuthentication): |  | ||||||
|     """Token-based authentication using HTTP Bearer authentication""" |  | ||||||
|  |  | ||||||
|     def authenticate(self, request: Request) -> Union[tuple[User, Any], None]: |  | ||||||
|         """Token-based authentication using HTTP Bearer authentication""" |  | ||||||
|         auth = get_authorization_header(request) |  | ||||||
|  |  | ||||||
|         token = token_from_header(auth) |  | ||||||
|         # None is only returned when the header isn't set. |  | ||||||
|         if not token: |  | ||||||
|             return None |  | ||||||
|  |  | ||||||
|         return (token.user, None)  # pragma: no cover |  | ||||||
| @ -1,35 +0,0 @@ | |||||||
| """API Authorization""" |  | ||||||
| from django.db.models import Model |  | ||||||
| from django.db.models.query import QuerySet |  | ||||||
| from rest_framework.filters import BaseFilterBackend |  | ||||||
| from rest_framework.permissions import BasePermission |  | ||||||
| from rest_framework.request import Request |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class OwnerFilter(BaseFilterBackend): |  | ||||||
|     """Filter objects by their owner""" |  | ||||||
|  |  | ||||||
|     owner_key = "user" |  | ||||||
|  |  | ||||||
|     def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet: |  | ||||||
|         return queryset.filter(**{self.owner_key: request.user}) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class OwnerPermissions(BasePermission): |  | ||||||
|     """Authorize requests by an object's owner matching the requesting user""" |  | ||||||
|  |  | ||||||
|     owner_key = "user" |  | ||||||
|  |  | ||||||
|     def has_permission(self, request: Request, view) -> bool: |  | ||||||
|         """If the user is authenticated, we allow all requests here. For listing, the |  | ||||||
|         object-level permissions are done by the filter backend""" |  | ||||||
|         return request.user.is_authenticated |  | ||||||
|  |  | ||||||
|     def has_object_permission(self, request: Request, view, obj: Model) -> bool: |  | ||||||
|         """Check if the object's owner matches the currently logged in user""" |  | ||||||
|         if not hasattr(obj, self.owner_key): |  | ||||||
|             return False |  | ||||||
|         owner = getattr(obj, self.owner_key) |  | ||||||
|         if owner != request.user: |  | ||||||
|             return False |  | ||||||
|         return True |  | ||||||
| @ -1,32 +0,0 @@ | |||||||
| """API Decorators""" |  | ||||||
| from functools import wraps |  | ||||||
| from typing import Callable, Optional |  | ||||||
|  |  | ||||||
| from rest_framework.request import Request |  | ||||||
| from rest_framework.response import Response |  | ||||||
| from rest_framework.viewsets import ModelViewSet |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def permission_required( |  | ||||||
|     perm: Optional[str] = None, other_perms: Optional[list[str]] = None |  | ||||||
| ): |  | ||||||
|     """Check permissions for a single custom action""" |  | ||||||
|  |  | ||||||
|     def wrapper_outter(func: Callable): |  | ||||||
|         """Check permissions for a single custom action""" |  | ||||||
|  |  | ||||||
|         @wraps(func) |  | ||||||
|         def wrapper(self: ModelViewSet, request: Request, *args, **kwargs) -> Response: |  | ||||||
|             if perm: |  | ||||||
|                 obj = self.get_object() |  | ||||||
|                 if not request.user.has_perm(perm, obj): |  | ||||||
|                     return self.permission_denied(request) |  | ||||||
|             if other_perms: |  | ||||||
|                 for other_perm in other_perms: |  | ||||||
|                     if not request.user.has_perm(other_perm): |  | ||||||
|                         return self.permission_denied(request) |  | ||||||
|             return func(self, request, *args, **kwargs) |  | ||||||
|  |  | ||||||
|         return wrapper |  | ||||||
|  |  | ||||||
|     return wrapper_outter |  | ||||||
| @ -6,7 +6,6 @@ from rest_framework.response import Response | |||||||
| class Pagination(pagination.PageNumberPagination): | class Pagination(pagination.PageNumberPagination): | ||||||
|     """Pagination which includes total pages and current page""" |     """Pagination which includes total pages and current page""" | ||||||
|  |  | ||||||
|     page_query_param = "page" |  | ||||||
|     page_size_query_param = "page_size" |     page_size_query_param = "page_size" | ||||||
|  |  | ||||||
|     def get_paginated_response(self, data): |     def get_paginated_response(self, data): | ||||||
|  | |||||||
| @ -1,97 +0,0 @@ | |||||||
| """Swagger Pagination Schema class""" |  | ||||||
| from typing import OrderedDict |  | ||||||
|  |  | ||||||
| from drf_yasg import openapi |  | ||||||
| from drf_yasg.inspectors import PaginatorInspector |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PaginationInspector(PaginatorInspector): |  | ||||||
|     """Swagger Pagination Schema class""" |  | ||||||
|  |  | ||||||
|     def get_paginated_response(self, paginator, response_schema): |  | ||||||
|         """ |  | ||||||
|         :param BasePagination paginator: the paginator |  | ||||||
|         :param openapi.Schema response_schema: the response schema that must be paged. |  | ||||||
|         :rtype: openapi.Schema |  | ||||||
|         """ |  | ||||||
|  |  | ||||||
|         return openapi.Schema( |  | ||||||
|             type=openapi.TYPE_OBJECT, |  | ||||||
|             properties=OrderedDict( |  | ||||||
|                 ( |  | ||||||
|                     ( |  | ||||||
|                         "pagination", |  | ||||||
|                         openapi.Schema( |  | ||||||
|                             type=openapi.TYPE_OBJECT, |  | ||||||
|                             properties=OrderedDict( |  | ||||||
|                                 ( |  | ||||||
|                                     ("next", openapi.Schema(type=openapi.TYPE_NUMBER)), |  | ||||||
|                                     ( |  | ||||||
|                                         "previous", |  | ||||||
|                                         openapi.Schema(type=openapi.TYPE_NUMBER), |  | ||||||
|                                     ), |  | ||||||
|                                     ("count", openapi.Schema(type=openapi.TYPE_NUMBER)), |  | ||||||
|                                     ( |  | ||||||
|                                         "current", |  | ||||||
|                                         openapi.Schema(type=openapi.TYPE_NUMBER), |  | ||||||
|                                     ), |  | ||||||
|                                     ( |  | ||||||
|                                         "total_pages", |  | ||||||
|                                         openapi.Schema(type=openapi.TYPE_NUMBER), |  | ||||||
|                                     ), |  | ||||||
|                                     ( |  | ||||||
|                                         "start_index", |  | ||||||
|                                         openapi.Schema(type=openapi.TYPE_NUMBER), |  | ||||||
|                                     ), |  | ||||||
|                                     ( |  | ||||||
|                                         "end_index", |  | ||||||
|                                         openapi.Schema(type=openapi.TYPE_NUMBER), |  | ||||||
|                                     ), |  | ||||||
|                                 ) |  | ||||||
|                             ), |  | ||||||
|                             required=[ |  | ||||||
|                                 "next", |  | ||||||
|                                 "previous", |  | ||||||
|                                 "count", |  | ||||||
|                                 "current", |  | ||||||
|                                 "total_pages", |  | ||||||
|                                 "start_index", |  | ||||||
|                                 "end_index", |  | ||||||
|                             ], |  | ||||||
|                         ), |  | ||||||
|                     ), |  | ||||||
|                     ("results", response_schema), |  | ||||||
|                 ) |  | ||||||
|             ), |  | ||||||
|             required=["results", "pagination"], |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def get_paginator_parameters(self, paginator): |  | ||||||
|         """ |  | ||||||
|         Get the pagination parameters for a single paginator **instance**. |  | ||||||
|  |  | ||||||
|         Should return :data:`.NotHandled` if this inspector |  | ||||||
|         does not know how to handle the given `paginator`. |  | ||||||
|  |  | ||||||
|         :param BasePagination paginator: the paginator |  | ||||||
|         :rtype: list[openapi.Parameter] |  | ||||||
|         """ |  | ||||||
|  |  | ||||||
|         return [ |  | ||||||
|             openapi.Parameter( |  | ||||||
|                 "page", |  | ||||||
|                 openapi.IN_QUERY, |  | ||||||
|                 "Page Index", |  | ||||||
|                 False, |  | ||||||
|                 None, |  | ||||||
|                 openapi.TYPE_INTEGER, |  | ||||||
|             ), |  | ||||||
|             openapi.Parameter( |  | ||||||
|                 "page_size", |  | ||||||
|                 openapi.IN_QUERY, |  | ||||||
|                 "Page Size", |  | ||||||
|                 False, |  | ||||||
|                 None, |  | ||||||
|                 openapi.TYPE_INTEGER, |  | ||||||
|             ), |  | ||||||
|         ] |  | ||||||
| @ -1,102 +0,0 @@ | |||||||
| """Error Response schema, from https://github.com/axnsan12/drf-yasg/issues/224""" |  | ||||||
| from drf_yasg import openapi |  | ||||||
| from drf_yasg.inspectors.view import SwaggerAutoSchema |  | ||||||
| from drf_yasg.utils import force_real_str, is_list_view |  | ||||||
| from rest_framework import exceptions, status |  | ||||||
| from rest_framework.settings import api_settings |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ErrorResponseAutoSchema(SwaggerAutoSchema): |  | ||||||
|     """Inspector which includes an error schema""" |  | ||||||
|  |  | ||||||
|     def get_generic_error_schema(self): |  | ||||||
|         """Get a generic error schema""" |  | ||||||
|         return openapi.Schema( |  | ||||||
|             "Generic API Error", |  | ||||||
|             type=openapi.TYPE_OBJECT, |  | ||||||
|             properties={ |  | ||||||
|                 "detail": openapi.Schema( |  | ||||||
|                     type=openapi.TYPE_STRING, description="Error details" |  | ||||||
|                 ), |  | ||||||
|                 "code": openapi.Schema( |  | ||||||
|                     type=openapi.TYPE_STRING, description="Error code" |  | ||||||
|                 ), |  | ||||||
|             }, |  | ||||||
|             required=["detail"], |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def get_validation_error_schema(self): |  | ||||||
|         """Get a generic validation error schema""" |  | ||||||
|         return openapi.Schema( |  | ||||||
|             "Validation Error", |  | ||||||
|             type=openapi.TYPE_OBJECT, |  | ||||||
|             properties={ |  | ||||||
|                 api_settings.NON_FIELD_ERRORS_KEY: openapi.Schema( |  | ||||||
|                     description="List of validation errors not related to any field", |  | ||||||
|                     type=openapi.TYPE_ARRAY, |  | ||||||
|                     items=openapi.Schema(type=openapi.TYPE_STRING), |  | ||||||
|                 ), |  | ||||||
|             }, |  | ||||||
|             additional_properties=openapi.Schema( |  | ||||||
|                 description=( |  | ||||||
|                     "A list of error messages for each " |  | ||||||
|                     "field that triggered a validation error" |  | ||||||
|                 ), |  | ||||||
|                 type=openapi.TYPE_ARRAY, |  | ||||||
|                 items=openapi.Schema(type=openapi.TYPE_STRING), |  | ||||||
|             ), |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     def get_response_serializers(self): |  | ||||||
|         responses = super().get_response_serializers() |  | ||||||
|         definitions = self.components.with_scope( |  | ||||||
|             openapi.SCHEMA_DEFINITIONS |  | ||||||
|         )  # type: openapi.ReferenceResolver |  | ||||||
|  |  | ||||||
|         definitions.setdefault("GenericError", self.get_generic_error_schema) |  | ||||||
|         definitions.setdefault("ValidationError", self.get_validation_error_schema) |  | ||||||
|         definitions.setdefault("APIException", self.get_generic_error_schema) |  | ||||||
|  |  | ||||||
|         if self.get_request_serializer() or self.get_query_serializer(): |  | ||||||
|             responses.setdefault( |  | ||||||
|                 exceptions.ValidationError.status_code, |  | ||||||
|                 openapi.Response( |  | ||||||
|                     description=force_real_str( |  | ||||||
|                         exceptions.ValidationError.default_detail |  | ||||||
|                     ), |  | ||||||
|                     schema=openapi.SchemaRef(definitions, "ValidationError"), |  | ||||||
|                 ), |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         security = self.get_security() |  | ||||||
|         if security is None or len(security) > 0: |  | ||||||
|             # Note: 401 error codes are coerced  into 403 see |  | ||||||
|             # rest_framework/views.py:433:handle_exception |  | ||||||
|             # This is b/c the API uses token auth which doesn't have WWW-Authenticate header |  | ||||||
|             responses.setdefault( |  | ||||||
|                 status.HTTP_403_FORBIDDEN, |  | ||||||
|                 openapi.Response( |  | ||||||
|                     description="Authentication credentials were invalid, absent or insufficient.", |  | ||||||
|                     schema=openapi.SchemaRef(definitions, "GenericError"), |  | ||||||
|                 ), |  | ||||||
|             ) |  | ||||||
|         if not is_list_view(self.path, self.method, self.view): |  | ||||||
|             responses.setdefault( |  | ||||||
|                 exceptions.PermissionDenied.status_code, |  | ||||||
|                 openapi.Response( |  | ||||||
|                     description="Permission denied.", |  | ||||||
|                     schema=openapi.SchemaRef(definitions, "APIException"), |  | ||||||
|                 ), |  | ||||||
|             ) |  | ||||||
|             responses.setdefault( |  | ||||||
|                 exceptions.NotFound.status_code, |  | ||||||
|                 openapi.Response( |  | ||||||
|                     description=( |  | ||||||
|                         "Object does not exist or caller " |  | ||||||
|                         "has insufficient permissions to access it." |  | ||||||
|                     ), |  | ||||||
|                     schema=openapi.SchemaRef(definitions, "APIException"), |  | ||||||
|                 ), |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         return responses |  | ||||||
| @ -1,49 +0,0 @@ | |||||||
| {% extends "base/skeleton.html" %} |  | ||||||
|  |  | ||||||
| {% load static %} |  | ||||||
|  |  | ||||||
| {% block title %} |  | ||||||
| API Browser - {{ config.authentik.branding.title }} |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block head %} |  | ||||||
| <script type="module" src="{% static 'dist/rapidoc-min.js' %}"></script> |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| {% block body %} |  | ||||||
| <script> |  | ||||||
| function getCookie(name) { |  | ||||||
|     let cookieValue = ""; |  | ||||||
|     if (document.cookie && document.cookie !== "") { |  | ||||||
|         const cookies = document.cookie.split(";"); |  | ||||||
|         for (let i = 0; i < cookies.length; i++) { |  | ||||||
|             const cookie = cookies[i].trim(); |  | ||||||
|             // Does this cookie string begin with the name we want? |  | ||||||
|             if (cookie.substring(0, name.length + 1) === name + "=") { |  | ||||||
|                 cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return cookieValue; |  | ||||||
| } |  | ||||||
| window.addEventListener('DOMContentLoaded', (event) => { |  | ||||||
|     const rapidocEl = document.querySelector('rapi-doc'); |  | ||||||
|     rapidocEl.addEventListener('before-try', (e) => { |  | ||||||
|         e.detail.request.headers.append('X-CSRFToken', getCookie("authentik_csrf")); |  | ||||||
|     }); |  | ||||||
| }); |  | ||||||
| </script> |  | ||||||
| <rapi-doc |  | ||||||
|     spec-url="{{ path }}" |  | ||||||
|     heading-text="authentik" |  | ||||||
|     theme="dark" |  | ||||||
|     render-style="view" |  | ||||||
|     primary-color="#fd4b2d" |  | ||||||
|     allow-spec-url-load="false" |  | ||||||
|     allow-spec-file-load="false"> |  | ||||||
|     <div slot="logo"> |  | ||||||
|         <img src="{% static 'dist/assets/icons/icon.png' %}" style="width:50px; height:50px" /> |  | ||||||
|     </div> |  | ||||||
| </rapi-doc> |  | ||||||
| {% endblock %} |  | ||||||
							
								
								
									
										7
									
								
								authentik/api/templates/rest_framework/api.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								authentik/api/templates/rest_framework/api.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | {% extends "rest_framework/base.html" %} | ||||||
|  |  | ||||||
|  | {% block branding %} | ||||||
|  | <span class='navbar-brand'> | ||||||
|  |     authentik | ||||||
|  | </span> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										37
									
								
								authentik/api/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								authentik/api/tests.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | |||||||
|  | """Test API Authentication""" | ||||||
|  | from base64 import b64encode | ||||||
|  |  | ||||||
|  | from django.test import TestCase | ||||||
|  | from guardian.shortcuts import get_anonymous_user | ||||||
|  |  | ||||||
|  | from authentik.api.auth import token_from_header | ||||||
|  | from authentik.core.models import Token, TokenIntents | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestAPIAuth(TestCase): | ||||||
|  |     """Test API Authentication""" | ||||||
|  |  | ||||||
|  |     def test_valid(self): | ||||||
|  |         """Test valid token""" | ||||||
|  |         token = Token.objects.create( | ||||||
|  |             intent=TokenIntents.INTENT_API, user=get_anonymous_user() | ||||||
|  |         ) | ||||||
|  |         auth = b64encode(f":{token.key}".encode()).decode() | ||||||
|  |         self.assertEqual(token_from_header(f"Basic {auth}".encode()), token) | ||||||
|  |  | ||||||
|  |     def test_invalid_type(self): | ||||||
|  |         """Test invalid type""" | ||||||
|  |         self.assertIsNone(token_from_header("foo bar".encode())) | ||||||
|  |  | ||||||
|  |     def test_invalid_decode(self): | ||||||
|  |         """Test invalid bas64""" | ||||||
|  |         self.assertIsNone(token_from_header("Basic bar".encode())) | ||||||
|  |  | ||||||
|  |     def test_invalid_empty_password(self): | ||||||
|  |         """Test invalid with empty password""" | ||||||
|  |         self.assertIsNone(token_from_header("Basic :".encode())) | ||||||
|  |  | ||||||
|  |     def test_invalid_no_token(self): | ||||||
|  |         """Test invalid with no token""" | ||||||
|  |         auth = b64encode(":abc".encode()).decode() | ||||||
|  |         self.assertIsNone(token_from_header(f"Basic :{auth}".encode())) | ||||||
| @ -1,49 +0,0 @@ | |||||||
| """Test API Authentication""" |  | ||||||
| from base64 import b64encode |  | ||||||
|  |  | ||||||
| from django.test import TestCase |  | ||||||
| from guardian.shortcuts import get_anonymous_user |  | ||||||
| from rest_framework.exceptions import AuthenticationFailed |  | ||||||
|  |  | ||||||
| from authentik.api.authentication import token_from_header |  | ||||||
| from authentik.core.models import Token, TokenIntents |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestAPIAuth(TestCase): |  | ||||||
|     """Test API Authentication""" |  | ||||||
|  |  | ||||||
|     def test_valid_basic(self): |  | ||||||
|         """Test valid token""" |  | ||||||
|         token = Token.objects.create( |  | ||||||
|             intent=TokenIntents.INTENT_API, user=get_anonymous_user() |  | ||||||
|         ) |  | ||||||
|         auth = b64encode(f":{token.key}".encode()).decode() |  | ||||||
|         self.assertEqual(token_from_header(f"Basic {auth}".encode()), token) |  | ||||||
|  |  | ||||||
|     def test_valid_bearer(self): |  | ||||||
|         """Test valid token""" |  | ||||||
|         token = Token.objects.create( |  | ||||||
|             intent=TokenIntents.INTENT_API, user=get_anonymous_user() |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(token_from_header(f"Bearer {token.key}".encode()), token) |  | ||||||
|  |  | ||||||
|     def test_invalid_type(self): |  | ||||||
|         """Test invalid type""" |  | ||||||
|         with self.assertRaises(AuthenticationFailed): |  | ||||||
|             token_from_header("foo bar".encode()) |  | ||||||
|  |  | ||||||
|     def test_invalid_decode(self): |  | ||||||
|         """Test invalid bas64""" |  | ||||||
|         with self.assertRaises(AuthenticationFailed): |  | ||||||
|             token_from_header("Basic bar".encode()) |  | ||||||
|  |  | ||||||
|     def test_invalid_empty_password(self): |  | ||||||
|         """Test invalid with empty password""" |  | ||||||
|         with self.assertRaises(AuthenticationFailed): |  | ||||||
|             token_from_header("Basic :".encode()) |  | ||||||
|  |  | ||||||
|     def test_invalid_no_token(self): |  | ||||||
|         """Test invalid with no token""" |  | ||||||
|         with self.assertRaises(AuthenticationFailed): |  | ||||||
|             auth = b64encode(":abc".encode()).decode() |  | ||||||
|             self.assertIsNone(token_from_header(f"Basic :{auth}".encode())) |  | ||||||
| @ -1,16 +0,0 @@ | |||||||
| """Test config API""" |  | ||||||
| from json import loads |  | ||||||
|  |  | ||||||
| from django.urls import reverse |  | ||||||
| from rest_framework.test import APITestCase |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestConfig(APITestCase): |  | ||||||
|     """Test config API""" |  | ||||||
|  |  | ||||||
|     def test_config(self): |  | ||||||
|         """Test YAML generation""" |  | ||||||
|         response = self.client.get( |  | ||||||
|             reverse("authentik_api:configs-list"), |  | ||||||
|         ) |  | ||||||
|         self.assertTrue(loads(response.content.decode())) |  | ||||||
| @ -1,33 +0,0 @@ | |||||||
| """test decorators api""" |  | ||||||
| from django.urls import reverse |  | ||||||
| from guardian.shortcuts import assign_perm |  | ||||||
| from rest_framework.test import APITestCase |  | ||||||
|  |  | ||||||
| from authentik.core.models import Application, User |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestAPIDecorators(APITestCase): |  | ||||||
|     """test decorators api""" |  | ||||||
|  |  | ||||||
|     def setUp(self) -> None: |  | ||||||
|         super().setUp() |  | ||||||
|         self.user = User.objects.create(username="test-user") |  | ||||||
|  |  | ||||||
|     def test_obj_perm_denied(self): |  | ||||||
|         """Test object perm denied""" |  | ||||||
|         self.client.force_login(self.user) |  | ||||||
|         app = Application.objects.create(name="denied", slug="denied") |  | ||||||
|         response = self.client.get( |  | ||||||
|             reverse("authentik_api:application-metrics", kwargs={"slug": app.slug}) |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(response.status_code, 403) |  | ||||||
|  |  | ||||||
|     def test_other_perm_denied(self): |  | ||||||
|         """Test other perm denied""" |  | ||||||
|         self.client.force_login(self.user) |  | ||||||
|         app = Application.objects.create(name="denied", slug="denied") |  | ||||||
|         assign_perm("authentik_core.view_application", self.user, app) |  | ||||||
|         response = self.client.get( |  | ||||||
|             reverse("authentik_api:application-metrics", kwargs={"slug": app.slug}) |  | ||||||
|         ) |  | ||||||
|         self.assertEqual(response.status_code, 403) |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	