Compare commits
	
		
			1 Commits
		
	
	
		
			version/20
			...
			version/0.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 53d9092022 | 
| @ -1,11 +1,9 @@ | ||||
| [bumpversion] | ||||
| current_version = 2021.4.3 | ||||
| current_version = 0.14.0-rc1 | ||||
| tag = True | ||||
| commit = True | ||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*) | ||||
| serialize =  | ||||
| 	{major}.{minor}.{patch}-{release} | ||||
| 	{major}.{minor}.{patch} | ||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*) | ||||
| serialize = {major}.{minor}.{patch}-{release} | ||||
| message = release: {new_version} | ||||
| tag_name = version/{new_version} | ||||
|  | ||||
| @ -33,12 +31,6 @@ values = | ||||
|  | ||||
| [bumpversion:file:authentik/__init__.py] | ||||
|  | ||||
| [bumpversion:file:outpost/pkg/version.go] | ||||
| [bumpversion:file:proxy/pkg/version.go] | ||||
|  | ||||
| [bumpversion:file:web/src/constants.ts] | ||||
|  | ||||
| [bumpversion:file:web/nginx.conf] | ||||
|  | ||||
| [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 | ||||
							
								
								
									
										12
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,7 +1,7 @@ | ||||
| version: 2 | ||||
| updates: | ||||
| - package-ecosystem: gomod | ||||
|   directory: "/outpost" | ||||
|   directory: "/proxy" | ||||
|   schedule: | ||||
|     interval: daily | ||||
|     time: "04:00" | ||||
| @ -16,14 +16,6 @@ updates: | ||||
|   open-pull-requests-limit: 10 | ||||
|   assignees: | ||||
|   - BeryJu | ||||
| - package-ecosystem: npm | ||||
|   directory: "/website" | ||||
|   schedule: | ||||
|     interval: daily | ||||
|     time: "04:00" | ||||
|   open-pull-requests-limit: 10 | ||||
|   assignees: | ||||
|   - BeryJu | ||||
| - package-ecosystem: pip | ||||
|   directory: "/" | ||||
|   schedule: | ||||
| @ -41,7 +33,7 @@ updates: | ||||
|   assignees: | ||||
|   - BeryJu | ||||
| - package-ecosystem: docker | ||||
|   directory: "/outpost" | ||||
|   directory: "/proxy" | ||||
|   schedule: | ||||
|     interval: daily | ||||
|     time: "04:00" | ||||
|  | ||||
							
								
								
									
										23
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @ -18,11 +18,11 @@ jobs: | ||||
|       - name: Building Docker Image | ||||
|         run: docker build | ||||
|           --no-cache | ||||
|           -t beryju/authentik:2021.4.3 | ||||
|           -t beryju/authentik:0.14.0-rc1 | ||||
|           -t beryju/authentik:latest | ||||
|           -f Dockerfile . | ||||
|       - name: Push Docker Container to Registry (versioned) | ||||
|         run: docker push beryju/authentik:2021.4.3 | ||||
|         run: docker push beryju/authentik:0.14.0-rc1 | ||||
|       - name: Push Docker Container to Registry (latest) | ||||
|         run: docker push beryju/authentik:latest | ||||
|   build-proxy: | ||||
| @ -34,7 +34,7 @@ jobs: | ||||
|           go-version: "^1.15" | ||||
|       - name: prepare go api client | ||||
|         run: | | ||||
|           cd outpost | ||||
|           cd proxy | ||||
|           go get -u github.com/go-swagger/go-swagger/cmd/swagger | ||||
|           swagger generate client -f ../swagger.yaml -A authentik -t pkg/ | ||||
|           go build -v . | ||||
| @ -45,23 +45,20 @@ jobs: | ||||
|         run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD | ||||
|       - name: Building Docker Image | ||||
|         run: | | ||||
|           cd outpost/ | ||||
|           cd proxy/ | ||||
|           docker build \ | ||||
|           --no-cache \ | ||||
|           -t beryju/authentik-proxy:2021.4.3 \ | ||||
|           -t beryju/authentik-proxy:0.14.0-rc1 \ | ||||
|           -t beryju/authentik-proxy:latest \ | ||||
|           -f proxy.Dockerfile . | ||||
|           -f Dockerfile . | ||||
|       - name: Push Docker Container to Registry (versioned) | ||||
|         run: docker push beryju/authentik-proxy:2021.4.3 | ||||
|         run: docker push beryju/authentik-proxy:0.14.0-rc1 | ||||
|       - name: Push Docker Container to Registry (latest) | ||||
|         run: docker push beryju/authentik-proxy:latest | ||||
|   build-static: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v1 | ||||
|       - 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: Docker Login Registry | ||||
|         env: | ||||
|           DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | ||||
| @ -72,11 +69,11 @@ jobs: | ||||
|           cd web/ | ||||
|           docker build \ | ||||
|           --no-cache \ | ||||
|           -t beryju/authentik-static:2021.4.3 \ | ||||
|           -t beryju/authentik-static:0.14.0-rc1 \ | ||||
|           -t beryju/authentik-static:latest \ | ||||
|           -f Dockerfile . | ||||
|       - name: Push Docker Container to Registry (versioned) | ||||
|         run: docker push beryju/authentik-static:2021.4.3 | ||||
|         run: docker push beryju/authentik-static:0.14.0-rc1 | ||||
|       - name: Push Docker Container to Registry (latest) | ||||
|         run: docker push beryju/authentik-static:latest | ||||
|   test-release: | ||||
| @ -110,5 +107,5 @@ jobs: | ||||
|           SENTRY_PROJECT: authentik | ||||
|           SENTRY_URL: https://sentry.beryju.org | ||||
|         with: | ||||
|           tagName: 2021.4.3 | ||||
|           tagName: 0.14.0-rc1 | ||||
|           environment: beryjuorg-prod | ||||
|  | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -201,4 +201,3 @@ local.env.yml | ||||
| selenium_screenshots/ | ||||
| backups/ | ||||
| media/ | ||||
| *mmdb | ||||
|  | ||||
							
								
								
									
										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 | ||||
| @ -15,15 +15,12 @@ WORKDIR / | ||||
| COPY --from=locker /app/requirements.txt / | ||||
| COPY --from=locker /app/requirements-dev.txt / | ||||
|  | ||||
| ARG GIT_BUILD_HASH | ||||
| ENV GIT_BUILD_HASH=$GIT_BUILD_HASH | ||||
|  | ||||
| RUN apt-get update && \ | ||||
|     apt-get install -y --no-install-recommends curl ca-certificates gnupg && \ | ||||
|     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 && \ | ||||
|     apt-get update && \ | ||||
|     apt-get install -y --no-install-recommends postgresql-client-12 postgresql-client-11 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 && \ | ||||
|     apt-get clean && \ | ||||
|     pip install -r /requirements.txt --no-cache-dir && \ | ||||
|     apt-get remove --purge -y build-essential && \ | ||||
| @ -40,7 +37,7 @@ RUN apt-get update && \ | ||||
|     chown authentik:authentik /backups | ||||
|  | ||||
| COPY ./authentik/ /authentik | ||||
| COPY ./pyproject.toml / | ||||
| COPY ./pytest.ini / | ||||
| COPY ./xml /xml | ||||
| COPY ./manage.py / | ||||
| COPY ./lifecycle/ /lifecycle | ||||
| @ -48,5 +45,4 @@ COPY ./lifecycle/ /lifecycle | ||||
| USER authentik | ||||
| STOPSIGNAL SIGINT | ||||
| ENV TMPDIR /dev/shm/ | ||||
| ENV PYTHONUBUFFERED 1 | ||||
| ENTRYPOINT [ "/lifecycle/bootstrap.sh" ] | ||||
|  | ||||
							
								
								
									
										12
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								Makefile
									
									
									
									
									
								
							| @ -1,26 +1,32 @@ | ||||
| all: lint-fix lint coverage gen | ||||
|  | ||||
| test-full: | ||||
| 	coverage run manage.py test --failfast -v 3 . | ||||
| 	coverage html | ||||
| 	coverage report | ||||
|  | ||||
| test-integration: | ||||
| 	k3d cluster create || exit 0 | ||||
| 	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: | ||||
| 	coverage run manage.py test --failfast -v 3 tests/e2e | ||||
|  | ||||
| coverage: | ||||
| 	coverage run manage.py test -v 3 authentik | ||||
| 	coverage run manage.py test --failfast -v 3 authentik | ||||
| 	coverage html | ||||
| 	coverage report | ||||
|  | ||||
| lint-fix: | ||||
| 	isort authentik tests lifecycle | ||||
| 	isort -rc authentik tests lifecycle | ||||
| 	black authentik tests lifecycle | ||||
|  | ||||
| lint: | ||||
| 	pyright authentik tests lifecycle | ||||
| 	bandit -r authentik tests lifecycle -x node_modules | ||||
| 	pylint authentik tests lifecycle | ||||
| 	prospector | ||||
|  | ||||
| gen: coverage | ||||
| 	./manage.py generate_swagger -o swagger.yaml -f yaml | ||||
|  | ||||
							
								
								
									
										33
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								Pipfile
									
									
									
									
									
								
							| @ -6,56 +6,59 @@ verify_ssl = true | ||||
| [packages] | ||||
| boto3 = "*" | ||||
| celery = "*" | ||||
| channels = "*" | ||||
| channels-redis = "*" | ||||
| dacite = "*" | ||||
| defusedxml = "*" | ||||
| django = "*" | ||||
| django-cors-middleware = "*" | ||||
| django-dbbackup = "*" | ||||
| django-filter = "*" | ||||
| django-guardian = "*" | ||||
| django-model-utils = "*" | ||||
| django-otp = "*" | ||||
| django-prometheus = "*" | ||||
| django-recaptcha = "*" | ||||
| django-redis = "*" | ||||
| django-storages = "*" | ||||
| djangorestframework = "*" | ||||
| django-storages = "*" | ||||
| djangorestframework-guardian = "*" | ||||
| docker = "*" | ||||
| drf_yasg = "*" | ||||
| drf_yasg2 = "*" | ||||
| facebook-sdk = "*" | ||||
| geoip2 = "*" | ||||
| gunicorn = "*" | ||||
| kubernetes = "*" | ||||
| ldap3 = "*" | ||||
| lxml = ">=4.6.3" | ||||
| lxml = "*" | ||||
| packaging = "*" | ||||
| psycopg2-binary = "*" | ||||
| pycryptodome = "*" | ||||
| pyjwkest = "*" | ||||
| uvicorn = {extras = ["standard"],version = "*"} | ||||
| gunicorn = "*" | ||||
| pyyaml = "*" | ||||
| qrcode = "*" | ||||
| requests-oauthlib = "*" | ||||
| sentry-sdk = "*" | ||||
| service_identity = "*" | ||||
| structlog = "*" | ||||
| swagger-spec-validator = "*" | ||||
| twisted = "==20.3.0" | ||||
| urllib3 = {extras = ["secure"],version = "*"} | ||||
| uvicorn = {extras = ["standard"],version = "*"} | ||||
| webauthn = "*" | ||||
| dacite = "*" | ||||
| channels = "*" | ||||
| channels-redis = "*" | ||||
| kubernetes = "*" | ||||
| docker = "*" | ||||
| xmlsec = "*" | ||||
|  | ||||
| [requires] | ||||
| python_version = "3.9" | ||||
|  | ||||
| [dev-packages] | ||||
| autopep8 = "*" | ||||
| bandit = "*" | ||||
| black = "==20.8b1" | ||||
| bump2version = "*" | ||||
| bumpversion = "*" | ||||
| colorama = "*" | ||||
| coverage = "*" | ||||
| django-debug-toolbar = "*" | ||||
| pylint = "*" | ||||
| pylint-django = "*" | ||||
| selenium = "*" | ||||
| prospector = "*" | ||||
| pytest = "*" | ||||
| pytest-django = "*" | ||||
| selenium = "*" | ||||
|  | ||||
							
								
								
									
										1585
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1585
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							| @ -1,10 +1,7 @@ | ||||
| <p align="center"> | ||||
|     <img src="https://goauthentik.io/img/icon_top_brand_colour.svg" height="150" alt="authentik logo"> | ||||
| </p> | ||||
| <img src="https://goauthentik.io/img/icon_top_brand_colour.svg" height="250" alt="authentik logo"> | ||||
|  | ||||
| --- | ||||
|  | ||||
| [](https://discord.gg/jg33eMhnj6) | ||||
| [](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1) | ||||
| [](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1) | ||||
| [](https://codecov.io/gh/BeryJu/authentik) | ||||
| @ -24,10 +21,8 @@ For bigger setups, there is a Helm Chart in the `helm/` directory. This is docum | ||||
|  | ||||
| ## Screenshots | ||||
|  | ||||
| Light | Dark | ||||
| --- | --- | ||||
|  |  | ||||
|  |  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## Development | ||||
|  | ||||
|  | ||||
							
								
								
									
										11
									
								
								SECURITY.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								SECURITY.md
									
									
									
									
									
								
							| @ -2,10 +2,13 @@ | ||||
|  | ||||
| ## Supported Versions | ||||
|  | ||||
| | Version    | Supported          | | ||||
| | ---------- | ------------------ | | ||||
| | 2021.3.x   | :white_check_mark: | | ||||
| | 2021.4.x   | :white_check_mark: | | ||||
| As authentik is currently in a pre-stable, only the latest "stable" version is supported. After authentik 1.0, this will change. | ||||
|  | ||||
| | Version  | Supported          | | ||||
| | -------- | ------------------ | | ||||
| | 0.11.x   | :white_check_mark: | | ||||
| | 0.12.x   | :white_check_mark: | | ||||
| | 0.13.x   | :white_check_mark: | | ||||
|  | ||||
| ## Reporting a Vulnerability | ||||
|  | ||||
|  | ||||
| @ -1,3 +1,2 @@ | ||||
| """authentik""" | ||||
| __version__ = "2021.4.3" | ||||
| ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ||||
| __version__ = "0.14.0-rc1" | ||||
|  | ||||
| @ -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,24 @@ | ||||
| import time | ||||
| from collections import Counter | ||||
| 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, Model | ||||
| from django.db.models.fields import DurationField | ||||
| from django.db.models.functions import ExtractHour | ||||
| from django.utils.timezone import now | ||||
| from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method | ||||
| from rest_framework.fields import IntegerField, SerializerMethodField | ||||
| from drf_yasg2.utils import swagger_auto_schema | ||||
| from rest_framework.fields import SerializerMethodField | ||||
| from rest_framework.permissions import IsAdminUser | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import Serializer | ||||
| from rest_framework.viewsets import ViewSet | ||||
|  | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
| 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""" | ||||
|     date_from = now() - timedelta(days=1) | ||||
|     result = ( | ||||
| @ -31,51 +32,47 @@ def get_events_per_1h(**filter_kwargs) -> list[dict[str, int]]: | ||||
|         .annotate(count=Count("pk")) | ||||
|         .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 = [] | ||||
|     _now = now() | ||||
|     for hour in range(0, -24, -1): | ||||
|         results.append( | ||||
|             { | ||||
|                 "x_cord": time.mktime((_now + timedelta(hours=hour)).timetuple()) | ||||
|                 * 1000, | ||||
|                 "y_cord": data[hour * -1], | ||||
|                 "x": time.mktime((_now + timedelta(hours=hour)).timetuple()) * 1000, | ||||
|                 "y": data[hour * -1], | ||||
|             } | ||||
|         ) | ||||
|     return results | ||||
|  | ||||
|  | ||||
| class CoordinateSerializer(PassiveSerializer): | ||||
|     """Coordinates for diagrams""" | ||||
|  | ||||
|     x_cord = IntegerField(read_only=True) | ||||
|     y_cord = IntegerField(read_only=True) | ||||
|  | ||||
|  | ||||
| class LoginMetricsSerializer(PassiveSerializer): | ||||
| class AdministrationMetricsSerializer(Serializer): | ||||
|     """Login Metrics per 1h""" | ||||
|  | ||||
|     logins_per_1h = SerializerMethodField() | ||||
|     logins_failed_per_1h = SerializerMethodField() | ||||
|  | ||||
|     @swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True)) | ||||
|     def get_logins_per_1h(self, _): | ||||
|         """Get successful logins per hour for the last 24 hours""" | ||||
|         return get_events_per_1h(action=EventAction.LOGIN) | ||||
|  | ||||
|     @swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True)) | ||||
|     def get_logins_failed_per_1h(self, _): | ||||
|         """Get failed logins per hour for the last 24 hours""" | ||||
|         return get_events_per_1h(action=EventAction.LOGIN_FAILED) | ||||
|  | ||||
|     def create(self, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def update(self, instance: Model, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|  | ||||
| class AdministrationMetricsViewSet(ViewSet): | ||||
|     """Login Metrics per 1h""" | ||||
|  | ||||
|     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: | ||||
|         """Login Metrics per 1h""" | ||||
|         serializer = LoginMetricsSerializer(True) | ||||
|         serializer = AdministrationMetricsSerializer(True) | ||||
|         return Response(serializer.data) | ||||
|  | ||||
| @ -2,63 +2,48 @@ | ||||
| from importlib import import_module | ||||
|  | ||||
| from django.contrib import messages | ||||
| from django.db.models import Model | ||||
| from django.http.response import Http404 | ||||
| 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.fields import CharField, ChoiceField, DateTimeField, ListField | ||||
| from rest_framework.fields import CharField, DateTimeField, IntegerField, ListField | ||||
| from rest_framework.permissions import IsAdminUser | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import Serializer | ||||
| from rest_framework.viewsets import ViewSet | ||||
|  | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
| from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus | ||||
| from authentik.lib.tasks import TaskInfo | ||||
|  | ||||
|  | ||||
| class TaskSerializer(PassiveSerializer): | ||||
| class TaskSerializer(Serializer): | ||||
|     """Serialize TaskInfo and TaskResult""" | ||||
|  | ||||
|     task_name = CharField() | ||||
|     task_description = CharField() | ||||
|     task_finish_timestamp = DateTimeField(source="finish_timestamp") | ||||
|  | ||||
|     status = ChoiceField( | ||||
|         source="result.status.name", | ||||
|         choices=[(x.name, x.name) for x in TaskResultStatus], | ||||
|     ) | ||||
|     status = IntegerField(source="result.status.value") | ||||
|     messages = ListField(source="result.messages") | ||||
|  | ||||
|     def create(self, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def update(self, instance: Model, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|  | ||||
| class TaskViewSet(ViewSet): | ||||
|     """Read-only view set that returns all background tasks""" | ||||
|  | ||||
|     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)}) | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """List system tasks""" | ||||
|         tasks = sorted(TaskInfo.all().values(), key=lambda task: task.task_name) | ||||
|         return Response(TaskSerializer(tasks, many=True).data) | ||||
|         """List current messages and pass into Serializer""" | ||||
|         return Response(TaskSerializer(TaskInfo.all().values(), 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"]) | ||||
|     # pylint: disable=invalid-name | ||||
|     def retry(self, request: Request, pk=None) -> Response: | ||||
| @ -77,8 +62,12 @@ class TaskViewSet(ViewSet): | ||||
|                     % {"name": task.task_name} | ||||
|                 ), | ||||
|             ) | ||||
|             return Response(status=204) | ||||
|             return Response( | ||||
|                 { | ||||
|                     "successful": True, | ||||
|                 } | ||||
|             ) | ||||
|         except ImportError:  # pragma: no cover | ||||
|             # if we get an import error, the module path has probably changed | ||||
|             task.delete() | ||||
|             return Response(status=500) | ||||
|             return Response({"successful": False}) | ||||
|  | ||||
| @ -1,33 +1,27 @@ | ||||
| """authentik administration overview""" | ||||
| from os import environ | ||||
|  | ||||
| from django.core.cache import cache | ||||
| from drf_yasg.utils import swagger_auto_schema | ||||
| from django.db.models import Model | ||||
| from drf_yasg2.utils import swagger_auto_schema | ||||
| from packaging.version import parse | ||||
| from rest_framework.fields import SerializerMethodField | ||||
| 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.response import Response | ||||
| from rest_framework.serializers import Serializer | ||||
| 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.core.api.utils import PassiveSerializer | ||||
|  | ||||
|  | ||||
| class VersionSerializer(PassiveSerializer): | ||||
| class VersionSerializer(Serializer): | ||||
|     """Get running and latest version.""" | ||||
|  | ||||
|     version_current = SerializerMethodField() | ||||
|     version_latest = SerializerMethodField() | ||||
|     build_hash = 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: | ||||
|         """Get current version""" | ||||
|         return __version__ | ||||
| @ -46,18 +40,22 @@ class VersionSerializer(PassiveSerializer): | ||||
|             self.get_version_latest(instance) | ||||
|         ) | ||||
|  | ||||
|     def create(self, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def update(self, instance: Model, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|  | ||||
| class VersionViewSet(ListModelMixin, GenericViewSet): | ||||
|     """Get running and latest version.""" | ||||
|  | ||||
|     permission_classes = [IsAuthenticated] | ||||
|     pagination_class = None | ||||
|     filter_backends = [] | ||||
|     permission_classes = [IsAdminUser] | ||||
|  | ||||
|     def get_queryset(self):  # pragma: no cover | ||||
|         return None | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: VersionSerializer(many=False)}) | ||||
|     @swagger_auto_schema(responses={200: VersionSerializer(many=True)}) | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """Get running and latest version.""" | ||||
|         return Response(VersionSerializer(True).data) | ||||
|  | ||||
| @ -7,4 +7,5 @@ class AuthentikAdminConfig(AppConfig): | ||||
|  | ||||
|     name = "authentik.admin" | ||||
|     label = "authentik_admin" | ||||
|     mountpoint = "administration/" | ||||
|     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) | ||||
							
								
								
									
										19
									
								
								authentik/admin/forms/source.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								authentik/admin/forms/source.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| """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", | ||||
|     "verbose_name", | ||||
|     "verbose_name_plural", | ||||
| ] | ||||
							
								
								
									
										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 | ||||
| @ -1,22 +1,17 @@ | ||||
| """authentik admin tasks""" | ||||
| import re | ||||
|  | ||||
| from django.core.cache import cache | ||||
| from django.core.validators import URLValidator | ||||
| from packaging.version import parse | ||||
| from requests import RequestException, get | ||||
| from structlog.stdlib import get_logger | ||||
| from structlog import get_logger | ||||
|  | ||||
| from authentik import __version__ | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus | ||||
| from authentik.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus | ||||
| from authentik.root.celery import CELERY_APP | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| VERSION_CACHE_KEY = "authentik_latest_version" | ||||
| VERSION_CACHE_TIMEOUT = 8 * 60 * 60  # 8 hours | ||||
| # Chop of the first ^ because we want to search the entire string | ||||
| URL_FINDER = URLValidator.regex.pattern[1:] | ||||
| VERSION_CACHE_TIMEOUT = 2 * 60 * 60  # 2 hours | ||||
|  | ||||
|  | ||||
| @CELERY_APP.task(bind=True, base=MonitoredTask) | ||||
| @ -44,10 +39,7 @@ def update_latest_version(self: MonitoredTask): | ||||
|                 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() | ||||
|             Event.new(EventAction.UPDATE_AVAILABLE, new_version=upstream_version).save() | ||||
|     except (RequestException, IndexError) as exc: | ||||
|         cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT) | ||||
|         self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc)) | ||||
|  | ||||
							
								
								
									
										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 %} | ||||
							
								
								
									
										170
									
								
								authentik/admin/templates/administration/provider/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								authentik/admin/templates/administration/provider/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,170 @@ | ||||
| {% 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 %} | ||||
|                             <li> | ||||
|                                 <ak-modal-button href="{% url 'authentik_admin:provider-saml-from-metadata' %}"> | ||||
|                                     <button slot="trigger" class="pf-c-dropdown__menu-item"> | ||||
|                                         {% trans 'SAML Provider from Metadata' %}<br> | ||||
|                                         <small> | ||||
|                                             {% trans "Create a SAML Provider by importing its Metadata." %} | ||||
|                                         </small> | ||||
|                                     </button> | ||||
|                                     <div slot="modal"></div> | ||||
|                                 </ak-modal-button> | ||||
|                             </li> | ||||
|                         </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,109 @@ | ||||
| {% 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 'ID' %}</th> | ||||
|                     <th role="columnheader" scope="col">{% trans 'Created by' %}</th> | ||||
|                     <th role="columnheader" scope="col">{% trans 'Expiry' %}</th> | ||||
|                     <th role="cell"></th> | ||||
|                 </tr> | ||||
|             </thead> | ||||
|             <tbody role="rowgroup"> | ||||
|                 {% for invitation in object_list %} | ||||
|                 <tr role="row"> | ||||
|                     <td role="cell"> | ||||
|                         <span> | ||||
|                             {{ invitation.invite_uuid }} | ||||
|                         </span> | ||||
|                     </td> | ||||
|                     <td role="cell"> | ||||
|                         <span> | ||||
|                             {{ invitation.created_by }} | ||||
|                         </span> | ||||
|                     </td> | ||||
|                     <td role="cell"> | ||||
|                         <span> | ||||
|                             {{ invitation.expiry|default:"-" }} | ||||
|                         </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,8 +1,8 @@ | ||||
| """test admin api""" | ||||
| from json import loads | ||||
|  | ||||
| from django.shortcuts import reverse | ||||
| from django.test import TestCase | ||||
| from django.urls import reverse | ||||
|  | ||||
| from authentik import __version__ | ||||
| from authentik.core.models import Group, User | ||||
| @ -27,7 +27,7 @@ class TestAdminAPI(TestCase): | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         body = loads(response.content) | ||||
|         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_retry(self): | ||||
| @ -39,7 +39,9 @@ class TestAdminAPI(TestCase): | ||||
|                 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): | ||||
|         """Test Task API (retry, 404)""" | ||||
| @ -69,8 +71,3 @@ class TestAdminAPI(TestCase): | ||||
|         """Test metrics API""" | ||||
|         response = self.client.get(reverse("authentik_api:admin_metrics-list")) | ||||
|         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, | ||||
|             ) | ||||
|         ) | ||||
| @ -32,8 +32,7 @@ REQUEST_MOCK_VALID = Mock( | ||||
|     return_value=MockResponse( | ||||
|         200, | ||||
|         """{ | ||||
|             "tag_name": "version/99999999.9999999", | ||||
|             "body": "https://goauthentik.io/test" | ||||
|             "tag_name": "version/1.2.3" | ||||
|         }""", | ||||
|     ) | ||||
| ) | ||||
| @ -48,12 +47,10 @@ class TestAdminTasks(TestCase): | ||||
|     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.assertEqual(cache.get(VERSION_CACHE_KEY), "1.2.3") | ||||
|         self.assertTrue( | ||||
|             Event.objects.filter( | ||||
|                 action=EventAction.UPDATE_AVAILABLE, | ||||
|                 context__new_version="99999999.9999999", | ||||
|                 context__message="Changelog: https://goauthentik.io/test", | ||||
|                 action=EventAction.UPDATE_AVAILABLE, context__new_version="1.2.3" | ||||
|             ).exists() | ||||
|         ) | ||||
|         # test that a consecutive check doesn't create a duplicate event | ||||
| @ -61,9 +58,7 @@ class TestAdminTasks(TestCase): | ||||
|         self.assertEqual( | ||||
|             len( | ||||
|                 Event.objects.filter( | ||||
|                     action=EventAction.UPDATE_AVAILABLE, | ||||
|                     context__new_version="99999999.9999999", | ||||
|                     context__message="Changelog: https://goauthentik.io/test", | ||||
|                     action=EventAction.UPDATE_AVAILABLE, context__new_version="1.2.3" | ||||
|                 ) | ||||
|             ), | ||||
|             1, | ||||
|  | ||||
							
								
								
									
										355
									
								
								authentik/admin/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										355
									
								
								authentik/admin/urls.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,355 @@ | ||||
| """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, | ||||
| ) | ||||
| from authentik.providers.saml.views import MetadataImportView | ||||
|  | ||||
| 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/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/create/saml/from-metadata/", | ||||
|         MetadataImportView.as_view(), | ||||
|         name="provider-saml-from-metadata", | ||||
|     ), | ||||
|     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", | ||||
|     ), | ||||
| ] | ||||
							
								
								
									
										64
									
								
								authentik/admin/views/applications.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								authentik/admin/views/applications.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | ||||
| """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 UpdateView | ||||
| from guardian.mixins import PermissionRequiredMixin | ||||
|  | ||||
| from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView | ||||
| from authentik.core.forms.applications import ApplicationForm | ||||
| from authentik.core.models import Application | ||||
| from authentik.lib.views import CreateAssignPermView | ||||
|  | ||||
|  | ||||
| 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") | ||||
							
								
								
									
										74
									
								
								authentik/admin/views/stages_invitations.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								authentik/admin/views/stages_invitations.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | ||||
| """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 | ||||
|  | ||||
|  | ||||
| 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() | ||||
|         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) | ||||
| @ -1,65 +1,58 @@ | ||||
| """API Authentication""" | ||||
| from base64 import b64decode, b64encode | ||||
| from base64 import b64decode | ||||
| from binascii import Error | ||||
| from typing import Any, Optional, Union | ||||
| from typing import Any, Optional, Tuple, 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 structlog 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`""" | ||||
|     """raw_header in the Format of `Basic dGVzdDp0ZXN0`""" | ||||
|     auth_credentials = raw_header.decode() | ||||
|     if auth_credentials == "": | ||||
|     # 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 | ||||
|     # Legacy, accept basic auth thats fully encoded (2021.3 outposts) | ||||
|     if " " not in auth_credentials: | ||||
|         try: | ||||
|             plain = b64decode(auth_credentials.encode()).decode() | ||||
|             auth_type, body = plain.split() | ||||
|             auth_credentials = f"{auth_type} {b64encode(body.encode()).decode()}" | ||||
|         except (UnicodeDecodeError, Error): | ||||
|             raise AuthenticationFailed("Malformed header") | ||||
|     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 | ||||
|     # 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") | ||||
|         return None | ||||
|     tokens = Token.filter_not_expired(key=password, intent=TokenIntents.INTENT_API) | ||||
|     if not tokens.exists(): | ||||
|         raise AuthenticationFailed("Token invalid/expired") | ||||
|         LOGGER.debug("Token not found") | ||||
|         return None | ||||
|     return tokens.first() | ||||
|  | ||||
|  | ||||
| class AuthentikTokenAuthentication(BaseAuthentication): | ||||
|     """Token-based authentication using HTTP Bearer authentication""" | ||||
|     """Token-based authentication using HTTP Basic authentication""" | ||||
|  | ||||
|     def authenticate(self, request: Request) -> Union[tuple[User, Any], None]: | ||||
|         """Token-based authentication using HTTP Bearer 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) | ||||
|         # None is only returned when the header isn't set. | ||||
|         if not token: | ||||
|             return None | ||||
|  | ||||
|         return (token.user, None) | ||||
|  | ||||
|     def authenticate_header(self, request: Request) -> str: | ||||
|         return 'Basic realm="authentik"' | ||||
|  | ||||
| @ -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): | ||||
|     """Pagination which includes total pages and current page""" | ||||
|  | ||||
|     page_query_param = "page" | ||||
|     page_size_query_param = "page_size" | ||||
|  | ||||
|     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 %} | ||||
| authentik API Browser | ||||
| {% 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 %} | ||||
| @ -3,7 +3,6 @@ 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.auth import token_from_header | ||||
| from authentik.core.models import Token, TokenIntents | ||||
| @ -12,7 +11,7 @@ from authentik.core.models import Token, TokenIntents | ||||
| class TestAPIAuth(TestCase): | ||||
|     """Test API Authentication""" | ||||
| 
 | ||||
|     def test_valid_basic(self): | ||||
|     def test_valid(self): | ||||
|         """Test valid token""" | ||||
|         token = Token.objects.create( | ||||
|             intent=TokenIntents.INTENT_API, user=get_anonymous_user() | ||||
| @ -20,30 +19,19 @@ class TestAPIAuth(TestCase): | ||||
|         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()) | ||||
|         self.assertIsNone(token_from_header("foo bar".encode())) | ||||
| 
 | ||||
|     def test_invalid_decode(self): | ||||
|         """Test invalid bas64""" | ||||
|         with self.assertRaises(AuthenticationFailed): | ||||
|             token_from_header("Basic bar".encode()) | ||||
|         self.assertIsNone(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()) | ||||
|         self.assertIsNone(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())) | ||||
|         auth = b64encode(":abc".encode()).decode() | ||||
|         self.assertIsNone(token_from_header(f"Basic :{auth}".encode())) | ||||
| @ -1,24 +0,0 @@ | ||||
| """Swagger generation tests""" | ||||
| from json import loads | ||||
|  | ||||
| from django.urls import reverse | ||||
| from rest_framework.test import APITestCase | ||||
| from yaml import safe_load | ||||
|  | ||||
|  | ||||
| class TestSwaggerGeneration(APITestCase): | ||||
|     """Generic admin tests""" | ||||
|  | ||||
|     def test_yaml(self): | ||||
|         """Test YAML generation""" | ||||
|         response = self.client.get( | ||||
|             reverse("authentik_api:schema-json", kwargs={"format": ".yaml"}), | ||||
|         ) | ||||
|         self.assertTrue(safe_load(response.content.decode())) | ||||
|  | ||||
|     def test_json(self): | ||||
|         """Test JSON generation""" | ||||
|         response = self.client.get( | ||||
|             reverse("authentik_api:schema-json", kwargs={"format": ".json"}), | ||||
|         ) | ||||
|         self.assertTrue(loads(response.content.decode())) | ||||
| @ -1,32 +1,30 @@ | ||||
| """core Configs API""" | ||||
| from drf_yasg.utils import swagger_auto_schema | ||||
| from rest_framework.fields import BooleanField, CharField, ListField | ||||
| from django.db.models import Model | ||||
| from drf_yasg2.utils import swagger_auto_schema | ||||
| from rest_framework.permissions import AllowAny | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ReadOnlyField, Serializer | ||||
| from rest_framework.viewsets import ViewSet | ||||
|  | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
| from authentik.lib.config import CONFIG | ||||
|  | ||||
|  | ||||
| class FooterLinkSerializer(PassiveSerializer): | ||||
|     """Links returned in Config API""" | ||||
|  | ||||
|     href = CharField(read_only=True) | ||||
|     name = CharField(read_only=True) | ||||
|  | ||||
|  | ||||
| class ConfigSerializer(PassiveSerializer): | ||||
| class ConfigSerializer(Serializer): | ||||
|     """Serialize authentik Config into DRF Object""" | ||||
|  | ||||
|     branding_logo = CharField(read_only=True) | ||||
|     branding_title = CharField(read_only=True) | ||||
|     ui_footer_links = ListField(child=FooterLinkSerializer(), read_only=True) | ||||
|     branding_logo = ReadOnlyField() | ||||
|     branding_title = ReadOnlyField() | ||||
|  | ||||
|     error_reporting_enabled = BooleanField(read_only=True) | ||||
|     error_reporting_environment = CharField(read_only=True) | ||||
|     error_reporting_send_pii = BooleanField(read_only=True) | ||||
|     error_reporting_enabled = ReadOnlyField() | ||||
|     error_reporting_environment = ReadOnlyField() | ||||
|     error_reporting_send_pii = ReadOnlyField() | ||||
|  | ||||
|     def create(self, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def update(self, instance: Model, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|  | ||||
| class ConfigsViewSet(ViewSet): | ||||
| @ -34,7 +32,7 @@ class ConfigsViewSet(ViewSet): | ||||
|  | ||||
|     permission_classes = [AllowAny] | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: ConfigSerializer(many=False)}) | ||||
|     @swagger_auto_schema(responses={200: ConfigSerializer(many=True)}) | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """Retrive public configuration options""" | ||||
|         config = ConfigSerializer( | ||||
| @ -44,7 +42,6 @@ class ConfigsViewSet(ViewSet): | ||||
|                 "error_reporting_enabled": CONFIG.y("error_reporting.enabled"), | ||||
|                 "error_reporting_environment": CONFIG.y("error_reporting.environment"), | ||||
|                 "error_reporting_send_pii": CONFIG.y("error_reporting.send_pii"), | ||||
|                 "ui_footer_links": CONFIG.y("authentik.footer_links"), | ||||
|             } | ||||
|         ) | ||||
|         return Response(config.data) | ||||
|  | ||||
							
								
								
									
										37
									
								
								authentik/api/v2/messages.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								authentik/api/v2/messages.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| """core messages API""" | ||||
| from django.contrib.messages import get_messages | ||||
| from django.db.models import Model | ||||
| from drf_yasg2.utils import swagger_auto_schema | ||||
| from rest_framework.permissions import AllowAny | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ReadOnlyField, Serializer | ||||
| from rest_framework.viewsets import ViewSet | ||||
|  | ||||
|  | ||||
| class MessageSerializer(Serializer): | ||||
|     """Serialize Django Message into DRF Object""" | ||||
|  | ||||
|     message = ReadOnlyField() | ||||
|     level = ReadOnlyField() | ||||
|     tags = ReadOnlyField() | ||||
|     extra_tags = ReadOnlyField() | ||||
|     level_tag = ReadOnlyField() | ||||
|  | ||||
|     def create(self, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def update(self, instance: Model, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|  | ||||
| class MessagesViewSet(ViewSet): | ||||
|     """Read-only view set that returns the current session's messages""" | ||||
|  | ||||
|     permission_classes = [AllowAny] | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: MessageSerializer(many=True)}) | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """List current messages and pass into Serializer""" | ||||
|         all_messages = list(get_messages(request)) | ||||
|         return Response(MessageSerializer(all_messages, many=True).data) | ||||
| @ -1,17 +1,16 @@ | ||||
| """api v2 urls""" | ||||
| from django.urls import path, re_path | ||||
| from drf_yasg import openapi | ||||
| from drf_yasg.views import get_schema_view | ||||
| from drf_yasg2 import openapi | ||||
| from drf_yasg2.views import get_schema_view | ||||
| from rest_framework import routers | ||||
| from rest_framework.permissions import AllowAny | ||||
|  | ||||
| from authentik.admin.api.meta import AppsViewSet | ||||
| from authentik.admin.api.metrics import AdministrationMetricsViewSet | ||||
| from authentik.admin.api.tasks import TaskViewSet | ||||
| from authentik.admin.api.version import VersionViewSet | ||||
| from authentik.admin.api.workers import WorkerViewSet | ||||
| from authentik.api.v2.config import ConfigsViewSet | ||||
| from authentik.api.views import SwaggerView | ||||
| from authentik.api.v2.messages import MessagesViewSet | ||||
| from authentik.core.api.applications import ApplicationViewSet | ||||
| from authentik.core.api.groups import GroupViewSet | ||||
| from authentik.core.api.propertymappings import PropertyMappingViewSet | ||||
| @ -20,75 +19,48 @@ from authentik.core.api.sources import SourceViewSet | ||||
| from authentik.core.api.tokens import TokenViewSet | ||||
| from authentik.core.api.users import UserViewSet | ||||
| from authentik.crypto.api import CertificateKeyPairViewSet | ||||
| from authentik.events.api.event import EventViewSet | ||||
| from authentik.events.api.notification import NotificationViewSet | ||||
| from authentik.events.api.notification_rule import NotificationRuleViewSet | ||||
| from authentik.events.api.notification_transport import NotificationTransportViewSet | ||||
| from authentik.flows.api.bindings import FlowStageBindingViewSet | ||||
| from authentik.flows.api.flows import FlowViewSet | ||||
| from authentik.flows.api.stages import StageViewSet | ||||
| from authentik.flows.views import FlowExecutorView | ||||
| from authentik.outposts.api.outpost_service_connections import ( | ||||
| from authentik.events.api import EventViewSet | ||||
| from authentik.flows.api import ( | ||||
|     FlowCacheViewSet, | ||||
|     FlowStageBindingViewSet, | ||||
|     FlowViewSet, | ||||
|     StageViewSet, | ||||
| ) | ||||
| from authentik.outposts.api import ( | ||||
|     DockerServiceConnectionViewSet, | ||||
|     KubernetesServiceConnectionViewSet, | ||||
|     ServiceConnectionViewSet, | ||||
|     OutpostViewSet, | ||||
| ) | ||||
| from authentik.policies.api import ( | ||||
|     PolicyBindingViewSet, | ||||
|     PolicyCacheViewSet, | ||||
|     PolicyViewSet, | ||||
| ) | ||||
| from authentik.outposts.api.outposts import OutpostViewSet | ||||
| from authentik.policies.api.bindings import PolicyBindingViewSet | ||||
| from authentik.policies.api.policies import PolicyViewSet | ||||
| from authentik.policies.dummy.api import DummyPolicyViewSet | ||||
| from authentik.policies.event_matcher.api import EventMatcherPolicyViewSet | ||||
| from authentik.policies.expiry.api import PasswordExpiryPolicyViewSet | ||||
| from authentik.policies.expression.api import ExpressionPolicyViewSet | ||||
| from authentik.policies.group_membership.api import GroupMembershipPolicyViewSet | ||||
| from authentik.policies.hibp.api import HaveIBeenPwendPolicyViewSet | ||||
| from authentik.policies.password.api import PasswordPolicyViewSet | ||||
| from authentik.policies.reputation.api import ( | ||||
|     IPReputationViewSet, | ||||
|     ReputationPolicyViewSet, | ||||
|     UserReputationViewSet, | ||||
| ) | ||||
| from authentik.providers.oauth2.api.provider import OAuth2ProviderViewSet | ||||
| from authentik.providers.oauth2.api.scope import ScopeMappingViewSet | ||||
| from authentik.providers.oauth2.api.tokens import ( | ||||
|     AuthorizationCodeViewSet, | ||||
|     RefreshTokenViewSet, | ||||
| ) | ||||
| from authentik.policies.reputation.api import ReputationPolicyViewSet | ||||
| from authentik.providers.oauth2.api import OAuth2ProviderViewSet, ScopeMappingViewSet | ||||
| from authentik.providers.proxy.api import ( | ||||
|     ProxyOutpostConfigViewSet, | ||||
|     ProxyProviderViewSet, | ||||
| ) | ||||
| from authentik.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProviderViewSet | ||||
| from authentik.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet | ||||
| from authentik.sources.oauth.api.source import OAuthSourceViewSet | ||||
| from authentik.sources.oauth.api.source_connection import ( | ||||
|     UserOAuthSourceConnectionViewSet, | ||||
| ) | ||||
| from authentik.sources.oauth.api import OAuthSourceViewSet | ||||
| from authentik.sources.saml.api import SAMLSourceViewSet | ||||
| from authentik.stages.authenticator_static.api import ( | ||||
|     AuthenticatorStaticStageViewSet, | ||||
|     StaticAdminDeviceViewSet, | ||||
|     StaticDeviceViewSet, | ||||
| ) | ||||
| from authentik.stages.authenticator_totp.api import ( | ||||
|     AuthenticatorTOTPStageViewSet, | ||||
|     TOTPAdminDeviceViewSet, | ||||
|     TOTPDeviceViewSet, | ||||
| ) | ||||
| from authentik.stages.authenticator_validate.api import ( | ||||
|     AuthenticatorValidateStageViewSet, | ||||
| ) | ||||
| from authentik.stages.authenticator_webauthn.api import ( | ||||
|     AuthenticateWebAuthnStageViewSet, | ||||
|     WebAuthnAdminDeviceViewSet, | ||||
|     WebAuthnDeviceViewSet, | ||||
| ) | ||||
| from authentik.stages.captcha.api import CaptchaStageViewSet | ||||
| from authentik.stages.consent.api import ConsentStageViewSet, UserConsentViewSet | ||||
| from authentik.stages.deny.api import DenyStageViewSet | ||||
| from authentik.stages.consent.api import ConsentStageViewSet | ||||
| from authentik.stages.dummy.api import DummyStageViewSet | ||||
| from authentik.stages.email.api import EmailStageViewSet | ||||
| from authentik.stages.identification.api import IdentificationStageViewSet | ||||
| from authentik.stages.invitation.api import InvitationStageViewSet, InvitationViewSet | ||||
| from authentik.stages.otp_static.api import OTPStaticStageViewSet | ||||
| from authentik.stages.otp_time.api import OTPTimeStageViewSet | ||||
| from authentik.stages.otp_validate.api import OTPValidateStageViewSet | ||||
| from authentik.stages.password.api import PasswordStageViewSet | ||||
| from authentik.stages.prompt.api import PromptStageViewSet, PromptViewSet | ||||
| from authentik.stages.user_delete.api import UserDeleteStageViewSet | ||||
| @ -98,23 +70,20 @@ from authentik.stages.user_write.api import UserWriteStageViewSet | ||||
|  | ||||
| router = routers.DefaultRouter() | ||||
|  | ||||
| router.register("root/messages", MessagesViewSet, basename="messages") | ||||
| router.register("root/config", ConfigsViewSet, basename="configs") | ||||
|  | ||||
| router.register("admin/version", VersionViewSet, basename="admin_version") | ||||
| router.register("admin/workers", WorkerViewSet, basename="admin_workers") | ||||
| router.register("admin/metrics", AdministrationMetricsViewSet, basename="admin_metrics") | ||||
| router.register("admin/system_tasks", TaskViewSet, basename="admin_system_tasks") | ||||
| router.register("admin/apps", AppsViewSet, basename="apps") | ||||
|  | ||||
| router.register("core/applications", ApplicationViewSet) | ||||
| router.register("core/groups", GroupViewSet) | ||||
| router.register("core/users", UserViewSet) | ||||
| router.register("core/user_consent", UserConsentViewSet) | ||||
| router.register("core/tokens", TokenViewSet) | ||||
|  | ||||
| router.register("outposts/outposts", OutpostViewSet) | ||||
| router.register("outposts/instances", OutpostViewSet) | ||||
| router.register("outposts/service_connections/all", ServiceConnectionViewSet) | ||||
| router.register("outposts/service_connections/docker", DockerServiceConnectionViewSet) | ||||
| router.register( | ||||
|     "outposts/service_connections/kubernetes", KubernetesServiceConnectionViewSet | ||||
| @ -122,30 +91,26 @@ router.register( | ||||
| router.register("outposts/proxy", ProxyOutpostConfigViewSet) | ||||
|  | ||||
| router.register("flows/instances", FlowViewSet) | ||||
| router.register("flows/cached", FlowCacheViewSet, basename="flows_cache") | ||||
| router.register("flows/bindings", FlowStageBindingViewSet) | ||||
|  | ||||
| router.register("crypto/certificatekeypairs", CertificateKeyPairViewSet) | ||||
|  | ||||
| router.register("events/events", EventViewSet) | ||||
| router.register("events/notifications", NotificationViewSet) | ||||
| router.register("events/transports", NotificationTransportViewSet) | ||||
| router.register("events/rules", NotificationRuleViewSet) | ||||
|  | ||||
| router.register("sources/all", SourceViewSet) | ||||
| router.register("sources/oauth_user_connections", UserOAuthSourceConnectionViewSet) | ||||
| router.register("sources/ldap", LDAPSourceViewSet) | ||||
| router.register("sources/saml", SAMLSourceViewSet) | ||||
| router.register("sources/oauth", OAuthSourceViewSet) | ||||
|  | ||||
| router.register("policies/all", PolicyViewSet) | ||||
| router.register("policies/cached", PolicyCacheViewSet, basename="policies_cache") | ||||
| router.register("policies/bindings", PolicyBindingViewSet) | ||||
| router.register("policies/expression", ExpressionPolicyViewSet) | ||||
| router.register("policies/event_matcher", EventMatcherPolicyViewSet) | ||||
| router.register("policies/group_membership", GroupMembershipPolicyViewSet) | ||||
| router.register("policies/haveibeenpwned", HaveIBeenPwendPolicyViewSet) | ||||
| router.register("policies/password_expiry", PasswordExpiryPolicyViewSet) | ||||
| router.register("policies/password", PasswordPolicyViewSet) | ||||
| router.register("policies/reputation/users", UserReputationViewSet) | ||||
| router.register("policies/reputation/ips", IPReputationViewSet) | ||||
| router.register("policies/reputation", ReputationPolicyViewSet) | ||||
|  | ||||
| router.register("providers/all", ProviderViewSet) | ||||
| @ -153,33 +118,21 @@ router.register("providers/proxy", ProxyProviderViewSet) | ||||
| router.register("providers/oauth2", OAuth2ProviderViewSet) | ||||
| router.register("providers/saml", SAMLProviderViewSet) | ||||
|  | ||||
| router.register("oauth2/authorization_codes", AuthorizationCodeViewSet) | ||||
| router.register("oauth2/refresh_tokens", RefreshTokenViewSet) | ||||
|  | ||||
| router.register("propertymappings/all", PropertyMappingViewSet) | ||||
| router.register("propertymappings/ldap", LDAPPropertyMappingViewSet) | ||||
| router.register("propertymappings/saml", SAMLPropertyMappingViewSet) | ||||
| router.register("propertymappings/scope", ScopeMappingViewSet) | ||||
|  | ||||
| router.register("authenticators/static", StaticDeviceViewSet) | ||||
| router.register("authenticators/totp", TOTPDeviceViewSet) | ||||
| router.register("authenticators/webauthn", WebAuthnDeviceViewSet) | ||||
| router.register("authenticators/admin/static", StaticAdminDeviceViewSet) | ||||
| router.register("authenticators/admin/totp", TOTPAdminDeviceViewSet) | ||||
| router.register("authenticators/admin/webauthn", WebAuthnAdminDeviceViewSet) | ||||
|  | ||||
| router.register("stages/all", StageViewSet) | ||||
| router.register("stages/authenticator/static", AuthenticatorStaticStageViewSet) | ||||
| router.register("stages/authenticator/totp", AuthenticatorTOTPStageViewSet) | ||||
| router.register("stages/authenticator/validate", AuthenticatorValidateStageViewSet) | ||||
| router.register("stages/authenticator/webauthn", AuthenticateWebAuthnStageViewSet) | ||||
| router.register("stages/captcha", CaptchaStageViewSet) | ||||
| router.register("stages/consent", ConsentStageViewSet) | ||||
| router.register("stages/deny", DenyStageViewSet) | ||||
| router.register("stages/email", EmailStageViewSet) | ||||
| router.register("stages/identification", IdentificationStageViewSet) | ||||
| router.register("stages/invitation", InvitationStageViewSet) | ||||
| router.register("stages/invitation/invitations", InvitationViewSet) | ||||
| router.register("stages/invitation/stages", InvitationStageViewSet) | ||||
| router.register("stages/otp_static", OTPStaticStageViewSet) | ||||
| router.register("stages/otp_time", OTPTimeStageViewSet) | ||||
| router.register("stages/otp_validate", OTPValidateStageViewSet) | ||||
| router.register("stages/password", PasswordStageViewSet) | ||||
| router.register("stages/prompt/prompts", PromptViewSet) | ||||
| router.register("stages/prompt/stages", PromptStageViewSet) | ||||
| @ -193,29 +146,28 @@ router.register("policies/dummy", DummyPolicyViewSet) | ||||
|  | ||||
| info = openapi.Info( | ||||
|     title="authentik API", | ||||
|     default_version="v2beta", | ||||
|     default_version="v2", | ||||
|     contact=openapi.Contact(email="hello@beryju.org"), | ||||
|     license=openapi.License( | ||||
|         name="GNU GPLv3", url="https://github.com/BeryJu/authentik/blob/master/LICENSE" | ||||
|     ), | ||||
| ) | ||||
| SchemaView = get_schema_view(info, public=True, permission_classes=(AllowAny,)) | ||||
|  | ||||
| urlpatterns = ( | ||||
|     [ | ||||
|         path("", SwaggerView.as_view(), name="swagger"), | ||||
|     ] | ||||
|     + router.urls | ||||
|     + [ | ||||
|         path( | ||||
|             "flows/executor/<slug:flow_slug>/", | ||||
|             FlowExecutorView.as_view(), | ||||
|             name="flow-executor", | ||||
|         ), | ||||
|         re_path( | ||||
|             r"^swagger(?P<format>\.json|\.yaml)$", | ||||
|             SchemaView.without_ui(cache_timeout=0), | ||||
|             name="schema-json", | ||||
|         ), | ||||
|     ] | ||||
| SchemaView = get_schema_view( | ||||
|     info, | ||||
|     public=True, | ||||
|     permission_classes=(AllowAny,), | ||||
| ) | ||||
|  | ||||
| urlpatterns = [ | ||||
|     re_path( | ||||
|         r"^swagger(?P<format>\.json|\.yaml)$", | ||||
|         SchemaView.without_ui(cache_timeout=0), | ||||
|         name="schema-json", | ||||
|     ), | ||||
|     path( | ||||
|         "swagger/", | ||||
|         SchemaView.with_ui("swagger", cache_timeout=0), | ||||
|         name="schema-swagger-ui", | ||||
|     ), | ||||
|     path("redoc/", SchemaView.with_ui("redoc", cache_timeout=0), name="schema-redoc"), | ||||
| ] + router.urls | ||||
|  | ||||
| @ -1,22 +0,0 @@ | ||||
| """General API Views""" | ||||
| from typing import Any | ||||
|  | ||||
| from django.urls import reverse | ||||
| from django.views.generic import TemplateView | ||||
|  | ||||
|  | ||||
| class SwaggerView(TemplateView): | ||||
|     """Show swagger view based on rapi-doc""" | ||||
|  | ||||
|     template_name = "api/swagger.html" | ||||
|  | ||||
|     def get_context_data(self, **kwargs: Any) -> dict[str, Any]: | ||||
|         path = self.request.build_absolute_uri( | ||||
|             reverse( | ||||
|                 "authentik_api:schema-json", | ||||
|                 kwargs={ | ||||
|                     "format": ".json", | ||||
|                 }, | ||||
|             ) | ||||
|         ) | ||||
|         return super().get_context_data(path=path, **kwargs) | ||||
							
								
								
									
										24
									
								
								authentik/core/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								authentik/core/admin.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| """authentik core admin""" | ||||
|  | ||||
| from django.apps import AppConfig, apps | ||||
| from django.contrib import admin | ||||
| from django.contrib.admin.sites import AlreadyRegistered | ||||
| from guardian.admin import GuardedModelAdmin | ||||
| from structlog import get_logger | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| def admin_autoregister(app: AppConfig): | ||||
|     """Automatically register all models from app""" | ||||
|     for model in app.get_models(): | ||||
|         try: | ||||
|             admin.site.register(model, GuardedModelAdmin) | ||||
|         except AlreadyRegistered: | ||||
|             pass | ||||
|  | ||||
|  | ||||
| for _app in apps.get_app_configs(): | ||||
|     if _app.label.startswith("authentik_"): | ||||
|         LOGGER.debug("Registering application for dj-admin", application=_app.label) | ||||
|         admin_autoregister(_app) | ||||
| @ -1,45 +1,32 @@ | ||||
| """Application API Views""" | ||||
| from typing import Optional | ||||
|  | ||||
| from django.core.cache import cache | ||||
| from django.db.models import QuerySet | ||||
| from django.http.response import HttpResponseBadRequest | ||||
| from drf_yasg import openapi | ||||
| from drf_yasg.utils import no_body, swagger_auto_schema | ||||
| from django.http.response import Http404 | ||||
| from guardian.shortcuts import get_objects_for_user | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.fields import SerializerMethodField | ||||
| from rest_framework.parsers import MultiPartParser | ||||
| from rest_framework.generics import get_object_or_404 | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ModelSerializer | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
| from rest_framework_guardian.filters import ObjectPermissionsFilter | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h | ||||
| from authentik.api.decorators import permission_required | ||||
| from authentik.admin.api.metrics import get_events_per_1h | ||||
| from authentik.core.api.providers import ProviderSerializer | ||||
| from authentik.core.models import Application | ||||
| from authentik.events.models import EventAction | ||||
| from authentik.policies.engine import PolicyEngine | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| def user_app_cache_key(user_pk: str) -> str: | ||||
|     """Cache key where application list for user is saved""" | ||||
|     return f"user_app_cache_{user_pk}" | ||||
|  | ||||
|  | ||||
| class ApplicationSerializer(ModelSerializer): | ||||
|     """Application Serializer""" | ||||
|  | ||||
|     launch_url = SerializerMethodField() | ||||
|     provider_obj = ProviderSerializer(source="get_provider", required=False) | ||||
|     provider = ProviderSerializer(source="get_provider", required=False) | ||||
|  | ||||
|     def get_launch_url(self, instance: Application) -> Optional[str]: | ||||
|     def get_launch_url(self, instance: Application) -> str: | ||||
|         """Get generated launch URL""" | ||||
|         return instance.get_launch_url() | ||||
|         return instance.get_launch_url() or "" | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
| @ -49,13 +36,12 @@ class ApplicationSerializer(ModelSerializer): | ||||
|             "name", | ||||
|             "slug", | ||||
|             "provider", | ||||
|             "provider_obj", | ||||
|             "launch_url", | ||||
|             "meta_launch_url", | ||||
|             "meta_icon", | ||||
|             "meta_description", | ||||
|             "meta_publisher", | ||||
|             "policy_engine_mode", | ||||
|             "policies", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| @ -82,94 +68,28 @@ class ApplicationViewSet(ModelViewSet): | ||||
|             queryset = backend().filter_queryset(self.request, queryset, self) | ||||
|         return queryset | ||||
|  | ||||
|     def _get_allowed_applications(self, queryset: QuerySet) -> list[Application]: | ||||
|         applications = [] | ||||
|         for application in queryset: | ||||
|             engine = PolicyEngine(application, self.request.user, self.request) | ||||
|             engine.build() | ||||
|             if engine.passing: | ||||
|                 applications.append(application) | ||||
|         return applications | ||||
|  | ||||
|     @swagger_auto_schema( | ||||
|         manual_parameters=[ | ||||
|             openapi.Parameter( | ||||
|                 name="superuser_full_list", | ||||
|                 in_=openapi.IN_QUERY, | ||||
|                 type=openapi.TYPE_BOOLEAN, | ||||
|             ) | ||||
|         ] | ||||
|     ) | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """Custom list method that checks Policy based access instead of guardian""" | ||||
|         queryset = self._filter_queryset_for_list(self.get_queryset()) | ||||
|         self.paginate_queryset(queryset) | ||||
|  | ||||
|         should_cache = request.GET.get("search", "") == "" | ||||
|  | ||||
|         superuser_full_list = ( | ||||
|             str(request.GET.get("superuser_full_list", "false")).lower() == "true" | ||||
|         ) | ||||
|         if superuser_full_list and request.user.is_superuser: | ||||
|             serializer = self.get_serializer(queryset, many=True) | ||||
|             return self.get_paginated_response(serializer.data) | ||||
|  | ||||
|         allowed_applications = [] | ||||
|         if not should_cache: | ||||
|             allowed_applications = self._get_allowed_applications(queryset) | ||||
|         if should_cache: | ||||
|             LOGGER.debug("Caching allowed application list") | ||||
|             allowed_applications = cache.get(user_app_cache_key(self.request.user.pk)) | ||||
|             if not allowed_applications: | ||||
|                 allowed_applications = self._get_allowed_applications(queryset) | ||||
|                 cache.set( | ||||
|                     user_app_cache_key(self.request.user.pk), | ||||
|                     allowed_applications, | ||||
|                     timeout=86400, | ||||
|                 ) | ||||
|         for application in queryset: | ||||
|             engine = PolicyEngine(application, self.request.user, self.request) | ||||
|             engine.build() | ||||
|             if engine.passing: | ||||
|                 allowed_applications.append(application) | ||||
|         serializer = self.get_serializer(allowed_applications, many=True) | ||||
|         return self.get_paginated_response(serializer.data) | ||||
|  | ||||
|     @permission_required("authentik_core.change_application") | ||||
|     @swagger_auto_schema( | ||||
|         request_body=no_body, | ||||
|         manual_parameters=[ | ||||
|             openapi.Parameter( | ||||
|                 name="file", | ||||
|                 in_=openapi.IN_FORM, | ||||
|                 type=openapi.TYPE_FILE, | ||||
|                 required=True, | ||||
|             ) | ||||
|         ], | ||||
|         responses={200: "Success", 400: "Bad request"}, | ||||
|     ) | ||||
|     @action( | ||||
|         detail=True, | ||||
|         pagination_class=None, | ||||
|         filter_backends=[], | ||||
|         methods=["POST"], | ||||
|         parser_classes=(MultiPartParser,), | ||||
|     ) | ||||
|     # pylint: disable=unused-argument | ||||
|     def set_icon(self, request: Request, slug: str): | ||||
|         """Set application icon""" | ||||
|         app: Application = self.get_object() | ||||
|         icon = request.FILES.get("file", None) | ||||
|         if not icon: | ||||
|             return HttpResponseBadRequest() | ||||
|         app.meta_icon = icon | ||||
|         app.save() | ||||
|         return Response({}) | ||||
|  | ||||
|     @permission_required( | ||||
|         "authentik_core.view_application", ["authentik_events.view_event"] | ||||
|     ) | ||||
|     @swagger_auto_schema(responses={200: CoordinateSerializer(many=True)}) | ||||
|     @action(detail=True, pagination_class=None, filter_backends=[]) | ||||
|     # pylint: disable=unused-argument | ||||
|     @action(detail=True) | ||||
|     def metrics(self, request: Request, slug: str): | ||||
|         """Metrics for application logins""" | ||||
|         app = self.get_object() | ||||
|         app = get_object_or_404( | ||||
|             get_objects_for_user(request.user, "authentik_core.view_application"), | ||||
|             slug=slug, | ||||
|         ) | ||||
|         if not request.user.has_perm("authentik_events.view_event"): | ||||
|             raise Http404 | ||||
|         return Response( | ||||
|             get_events_per_1h( | ||||
|                 action=EventAction.AUTHORIZE_APPLICATION, | ||||
|  | ||||
| @ -1,17 +1,13 @@ | ||||
| """Groups API Viewset""" | ||||
| from rest_framework.fields import JSONField | ||||
| from rest_framework.serializers import ModelSerializer | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
|  | ||||
| from authentik.core.api.utils import is_dict | ||||
| from authentik.core.models import Group | ||||
|  | ||||
|  | ||||
| class GroupSerializer(ModelSerializer): | ||||
|     """Group Serializer""" | ||||
|  | ||||
|     attributes = JSONField(validators=[is_dict], required=False) | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         model = Group | ||||
| @ -23,6 +19,3 @@ class GroupViewSet(ModelViewSet): | ||||
|  | ||||
|     queryset = Group.objects.all() | ||||
|     serializer_class = GroupSerializer | ||||
|     search_fields = ["name", "is_superuser"] | ||||
|     filterset_fields = ["name", "is_superuser"] | ||||
|     ordering = ["name"] | ||||
|  | ||||
| @ -1,146 +1,30 @@ | ||||
| """PropertyMapping API Views""" | ||||
| from json import dumps | ||||
|  | ||||
| from drf_yasg import openapi | ||||
| from drf_yasg.utils import swagger_auto_schema | ||||
| from guardian.shortcuts import get_objects_for_user | ||||
| from rest_framework import mixins | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.exceptions import PermissionDenied | ||||
| from rest_framework.fields import BooleanField, CharField | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ModelSerializer, SerializerMethodField | ||||
| from rest_framework.viewsets import GenericViewSet | ||||
| from rest_framework.viewsets import ReadOnlyModelViewSet | ||||
|  | ||||
| from authentik.api.decorators import permission_required | ||||
| from authentik.core.api.utils import ( | ||||
|     MetaNameSerializer, | ||||
|     PassiveSerializer, | ||||
|     TypeCreateSerializer, | ||||
| ) | ||||
| from authentik.core.expression import PropertyMappingEvaluator | ||||
| from authentik.core.models import PropertyMapping | ||||
| from authentik.lib.utils.reflection import all_subclasses | ||||
| from authentik.managed.api import ManagedSerializer | ||||
| from authentik.policies.api.exec import PolicyTestSerializer | ||||
|  | ||||
|  | ||||
| class PropertyMappingTestResultSerializer(PassiveSerializer): | ||||
|     """Result of a Property-mapping test""" | ||||
|  | ||||
|     result = CharField(read_only=True) | ||||
|     successful = BooleanField(read_only=True) | ||||
|  | ||||
|  | ||||
| class PropertyMappingSerializer(ManagedSerializer, ModelSerializer, MetaNameSerializer): | ||||
| class PropertyMappingSerializer(ModelSerializer): | ||||
|     """PropertyMapping Serializer""" | ||||
|  | ||||
|     component = SerializerMethodField() | ||||
|     __type__ = SerializerMethodField(method_name="get_type") | ||||
|  | ||||
|     def get_component(self, obj: PropertyMapping) -> str: | ||||
|         """Get object's component so that we know how to edit the object""" | ||||
|         return obj.component | ||||
|  | ||||
|     def validate_expression(self, expression: str) -> str: | ||||
|         """Test Syntax""" | ||||
|         evaluator = PropertyMappingEvaluator() | ||||
|         evaluator.validate(expression) | ||||
|         return expression | ||||
|     def get_type(self, obj): | ||||
|         """Get object type so that we know which API Endpoint to use to get the full object""" | ||||
|         return obj._meta.object_name.lower().replace("propertymapping", "") | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         model = PropertyMapping | ||||
|         fields = [ | ||||
|             "pk", | ||||
|             "managed", | ||||
|             "name", | ||||
|             "expression", | ||||
|             "component", | ||||
|             "verbose_name", | ||||
|             "verbose_name_plural", | ||||
|         ] | ||||
|         fields = ["pk", "name", "expression", "__type__"] | ||||
|  | ||||
|  | ||||
| class PropertyMappingViewSet( | ||||
|     mixins.RetrieveModelMixin, | ||||
|     mixins.DestroyModelMixin, | ||||
|     mixins.ListModelMixin, | ||||
|     GenericViewSet, | ||||
| ): | ||||
| class PropertyMappingViewSet(ReadOnlyModelViewSet): | ||||
|     """PropertyMapping Viewset""" | ||||
|  | ||||
|     queryset = PropertyMapping.objects.none() | ||||
|     queryset = PropertyMapping.objects.all() | ||||
|     serializer_class = PropertyMappingSerializer | ||||
|     search_fields = [ | ||||
|         "name", | ||||
|     ] | ||||
|     filterset_fields = {"managed": ["isnull"]} | ||||
|     ordering = ["name"] | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         return PropertyMapping.objects.select_subclasses() | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) | ||||
|     @action(detail=False, pagination_class=None, filter_backends=[]) | ||||
|     def types(self, request: Request) -> Response: | ||||
|         """Get all creatable property-mapping types""" | ||||
|         data = [] | ||||
|         for subclass in all_subclasses(self.queryset.model): | ||||
|             subclass: PropertyMapping | ||||
|             data.append( | ||||
|                 { | ||||
|                     "name": subclass._meta.verbose_name, | ||||
|                     "description": subclass.__doc__, | ||||
|                     # pyright: reportGeneralTypeIssues=false | ||||
|                     "component": subclass().component, | ||||
|                     "model_name": subclass._meta.model_name, | ||||
|                 } | ||||
|             ) | ||||
|         return Response(TypeCreateSerializer(data, many=True).data) | ||||
|  | ||||
|     @permission_required("authentik_core.view_propertymapping") | ||||
|     @swagger_auto_schema( | ||||
|         request_body=PolicyTestSerializer(), | ||||
|         responses={200: PropertyMappingTestResultSerializer, 400: "Invalid parameters"}, | ||||
|         manual_parameters=[ | ||||
|             openapi.Parameter( | ||||
|                 name="format_result", | ||||
|                 in_=openapi.IN_QUERY, | ||||
|                 type=openapi.TYPE_BOOLEAN, | ||||
|             ) | ||||
|         ], | ||||
|     ) | ||||
|     @action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"]) | ||||
|     # pylint: disable=unused-argument, invalid-name | ||||
|     def test(self, request: Request, pk: str) -> Response: | ||||
|         """Test Property Mapping""" | ||||
|         mapping: PropertyMapping = self.get_object() | ||||
|         test_params = PolicyTestSerializer(data=request.data) | ||||
|         if not test_params.is_valid(): | ||||
|             return Response(test_params.errors, status=400) | ||||
|  | ||||
|         format_result = str(request.GET.get("format_result", "false")).lower() == "true" | ||||
|  | ||||
|         # User permission check, only allow mapping testing for users that are readable | ||||
|         users = get_objects_for_user(request.user, "authentik_core.view_user").filter( | ||||
|             pk=test_params.validated_data["user"].pk | ||||
|         ) | ||||
|         if not users.exists(): | ||||
|             raise PermissionDenied() | ||||
|  | ||||
|         response_data = {"successful": True, "result": ""} | ||||
|         try: | ||||
|             result = mapping.evaluate( | ||||
|                 users.first(), | ||||
|                 self.request, | ||||
|                 **test_params.validated_data.get("context", {}), | ||||
|             ) | ||||
|             response_data["result"] = dumps( | ||||
|                 result, indent=(4 if format_result else None) | ||||
|             ) | ||||
|         except Exception as exc:  # pylint: disable=broad-except | ||||
|             response_data["result"] = str(exc) | ||||
|             response_data["successful"] = False | ||||
|         response = PropertyMappingTestResultSerializer(response_data) | ||||
|         return Response(response.data) | ||||
|  | ||||
| @ -1,33 +1,25 @@ | ||||
| """Provider API Views""" | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from drf_yasg.utils import swagger_auto_schema | ||||
| from rest_framework import mixins | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.fields import ReadOnlyField | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ModelSerializer, SerializerMethodField | ||||
| from rest_framework.viewsets import GenericViewSet | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
|  | ||||
| from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer | ||||
| from authentik.core.api.utils import MetaNameSerializer | ||||
| from authentik.core.models import Provider | ||||
| from authentik.lib.utils.reflection import all_subclasses | ||||
|  | ||||
|  | ||||
| class ProviderSerializer(ModelSerializer, MetaNameSerializer): | ||||
|     """Provider Serializer""" | ||||
|  | ||||
|     assigned_application_slug = ReadOnlyField(source="application.slug") | ||||
|     assigned_application_name = ReadOnlyField(source="application.name") | ||||
|     object_type = SerializerMethodField() | ||||
|  | ||||
|     component = SerializerMethodField() | ||||
|     def get_object_type(self, obj): | ||||
|         """Get object type so that we know which API Endpoint to use to get the full object""" | ||||
|         return obj._meta.object_name.lower().replace("provider", "") | ||||
|  | ||||
|     def get_component(self, obj: Provider):  # pragma: no cover | ||||
|         """Get object component so that we know how to edit the object""" | ||||
|     def to_representation(self, instance: Provider): | ||||
|         # pyright: reportGeneralTypeIssues=false | ||||
|         if obj.__class__ == Provider: | ||||
|             return "" | ||||
|         return obj.component | ||||
|         if instance.__class__ == Provider: | ||||
|             return super().to_representation(instance) | ||||
|         return instance.serializer(instance=instance).data | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
| @ -35,58 +27,23 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer): | ||||
|         fields = [ | ||||
|             "pk", | ||||
|             "name", | ||||
|             "application", | ||||
|             "authorization_flow", | ||||
|             "property_mappings", | ||||
|             "component", | ||||
|             "assigned_application_slug", | ||||
|             "assigned_application_name", | ||||
|             "object_type", | ||||
|             "verbose_name", | ||||
|             "verbose_name_plural", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class ProviderViewSet( | ||||
|     mixins.RetrieveModelMixin, | ||||
|     mixins.DestroyModelMixin, | ||||
|     mixins.ListModelMixin, | ||||
|     GenericViewSet, | ||||
| ): | ||||
| class ProviderViewSet(ModelViewSet): | ||||
|     """Provider Viewset""" | ||||
|  | ||||
|     queryset = Provider.objects.none() | ||||
|     queryset = Provider.objects.all() | ||||
|     serializer_class = ProviderSerializer | ||||
|     filterset_fields = { | ||||
|         "application": ["isnull"], | ||||
|     } | ||||
|     search_fields = [ | ||||
|         "name", | ||||
|         "application__name", | ||||
|     ] | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         return Provider.objects.select_subclasses() | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) | ||||
|     @action(detail=False, pagination_class=None, filter_backends=[]) | ||||
|     def types(self, request: Request) -> Response: | ||||
|         """Get all creatable provider types""" | ||||
|         data = [] | ||||
|         for subclass in all_subclasses(self.queryset.model): | ||||
|             subclass: Provider | ||||
|             data.append( | ||||
|                 { | ||||
|                     "name": subclass._meta.verbose_name, | ||||
|                     "description": subclass.__doc__, | ||||
|                     "component": subclass().component, | ||||
|                     "model_name": subclass._meta.model_name, | ||||
|                 } | ||||
|             ) | ||||
|         data.append( | ||||
|             { | ||||
|                 "name": _("SAML Provider from Metadata"), | ||||
|                 "description": _("Create a SAML Provider by importing its Metadata."), | ||||
|                 "component": "ak-provider-saml-import-form", | ||||
|                 "model_name": "", | ||||
|             } | ||||
|         ) | ||||
|         return Response(TypeCreateSerializer(data, many=True).data) | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	