Compare commits
	
		
			392 Commits
		
	
	
		
			version/20
			...
			version/20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 32e5ebb8a3 | |||
| 597e00dd86 | |||
| dd31191845 | |||
| e9d95b1311 | |||
| 3319547a0e | |||
| 1a00730cdd | |||
| 466723573c | |||
| ea784d47f4 | |||
| 77d5ba2862 | |||
| f4580a1097 | |||
| 9e3d1f0baa | |||
| c002c4b610 | |||
| dde5e910cf | |||
| 5218332bce | |||
| 28cd08bbba | |||
| 3cb0575a1e | |||
| dc1c1b9569 | |||
| 662d117b66 | |||
| b2449757f9 | |||
| a0753bfc88 | |||
| e2a771bdaa | |||
| 23de9df2a5 | |||
| 5c739ebed2 | |||
| d3f8d7120f | |||
| 21fd251edf | |||
| 28cededb90 | |||
| d420719649 | |||
| 0018fbacd3 | |||
| 8c41d2f4cb | |||
| 3941590d0c | |||
| dc4a7c35da | |||
| e8c9b70ae8 | |||
| 74d240dfd4 | |||
| 7d296b2119 | |||
| 373793ce9a | |||
| 5c0ec7554b | |||
| 792fa45dca | |||
| 743aaea15e | |||
| de03ed0aec | |||
| e68ec16a34 | |||
| 68a0219d0f | |||
| 38d9533afd | |||
| 7538af5e09 | |||
| 2e659c1ab0 | |||
| ad0cc5f0be | |||
| 7ae9482e7b | |||
| 7fb95dfabf | |||
| 83cc5d24f2 | |||
| 38b3096c9a | |||
| df8f21e559 | |||
| f4979fcf19 | |||
| 431b7375c1 | |||
| a6627145c8 | |||
| 3045cf1aef | |||
| c65b2944b3 | |||
| 2ae5a81c15 | |||
| ed8b78600e | |||
| 644a03e40e | |||
| 88ce93ab04 | |||
| 8878dc61d3 | |||
| 03d38557e5 | |||
| 37b59bb5b9 | |||
| 19eea68e0f | |||
| ce7aae16c9 | |||
| fd9ba97479 | |||
| 919debdd13 | |||
| 36690de285 | |||
| ca4ead8fd8 | |||
| a81f981471 | |||
| d6fd2b0afa | |||
| 0478ae3da8 | |||
| 9c33f4858f | |||
| f2eaa9052e | |||
| 21d0641110 | |||
| 67d05f99e9 | |||
| 21d6a28715 | |||
| 1149a8d9a4 | |||
| 5e98172afb | |||
| 9b3e94c7c8 | |||
| 30a1b65e94 | |||
| 9bb46ecb88 | |||
| 269e6c4f38 | |||
| 7f65ae3f92 | |||
| ee6b365003 | |||
| 2ad4bd5c0a | |||
| 0958740b51 | |||
| 9cdfd8b75a | |||
| 3c8a0081bc | |||
| 088e0e736a | |||
| cbb0681f95 | |||
| 55c408a8bf | |||
| 07379acf7f | |||
| a1af93f8be | |||
| b9a9da4ec7 | |||
| 05a5b5b675 | |||
| 0fb17eee43 | |||
| a1474e09e5 | |||
| a33c7d7786 | |||
| c08d9762d9 | |||
| d43e6e5736 | |||
| 380786bfde | |||
| ffcf064f83 | |||
| 252718bbaf | |||
| 5725e54334 | |||
| c20856ca17 | |||
| 402afa1e85 | |||
| 5b4e75000b | |||
| 9c73e9cf4e | |||
| b10c3db13d | |||
| 1a052913e9 | |||
| e930a1d0dc | |||
| fe290aa214 | |||
| a2e69bd250 | |||
| d2a35eb8de | |||
| 3437d8b4b0 | |||
| b862bf4284 | |||
| de22a367b1 | |||
| 17ab895652 | |||
| a4d5815e1b | |||
| 4cbfaaa72b | |||
| 92943f08d9 | |||
| 10ef1c7e93 | |||
| 02c762c268 | |||
| bbf0ca92af | |||
| d2dfc6d63b | |||
| a18240fcd7 | |||
| d36e5dccf9 | |||
| 9af1d6f63b | |||
| ab6d46558b | |||
| e94abfc986 | |||
| 5c652c1f79 | |||
| 89aa0f0cc8 | |||
| 085589bcec | |||
| 95d0d6f3e8 | |||
| c62ef4ae81 | |||
| 3df81ca6f0 | |||
| 578326eccd | |||
| 2335ccddaa | |||
| 477e30f542 | |||
| 7bf3d7e10a | |||
| 1bef659b10 | |||
| e3f7bd8ab8 | |||
| 45c731de3c | |||
| 535770abbd | |||
| eccea8eba0 | |||
| ab200a1dfb | |||
| ca122b20c9 | |||
| 74b407ebc7 | |||
| fbf2fe2404 | |||
| b968adffc1 | |||
| c275992f7b | |||
| 4e2c686db1 | |||
| bfc69562d8 | |||
| 9e6a7bf16b | |||
| 890e0e9054 | |||
| cf7e7c44ff | |||
| 0f169f176d | |||
| 429fc921b1 | |||
| e7a9a41a2f | |||
| d1c24f47b2 | |||
| 007676b400 | |||
| c0c235bead | |||
| a3aacb5285 | |||
| 5977c09b05 | |||
| e81d3dad3e | |||
| 5aabaebd96 | |||
| 7b60bca297 | |||
| a07d7456c8 | |||
| f33369bf0c | |||
| 1abcff39c7 | |||
| c1caf84d92 | |||
| 86c069fe64 | |||
| ce0140ef67 | |||
| bba43c5109 | |||
| d99a415502 | |||
| 9049593ff5 | |||
| e74c098b7a | |||
| d06a44378d | |||
| 0a8da376fc | |||
| 2a0f940a42 | |||
| 8aa067795a | |||
| 3cdb81c5ba | |||
| e8259791f0 | |||
| 55af786852 | |||
| 8a916602c4 | |||
| 7101c7987c | |||
| bd48955f39 | |||
| 53adcd9157 | |||
| c5a2bb8914 | |||
| 66e5958283 | |||
| 9db445c3ee | |||
| 574438b51e | |||
| a05885140d | |||
| 8878fac4e7 | |||
| 7ee97a961c | |||
| 737ff62e92 | |||
| 07ada5a1b7 | |||
| 8caeed6b18 | |||
| b5adff5327 | |||
| 3894895d32 | |||
| 7f53c97fb2 | |||
| 44bd4b9511 | |||
| 2a1b5e0154 | |||
| 8c0d48fe0a | |||
| 0863e60d29 | |||
| 451c117ea4 | |||
| 388c8c8bec | |||
| 5904070bb2 | |||
| 35ac87ec10 | |||
| 8f8c2a291b | |||
| 592a2dcede | |||
| a3221475e5 | |||
| 25f5031422 | |||
| 63b94263af | |||
| 217595bb01 | |||
| 2dd8119abe | |||
| 20e0fe3941 | |||
| 0fa97de06e | |||
| 38da13fea3 | |||
| fb9880bff4 | |||
| acc790f590 | |||
| 76c572cf7c | |||
| 0904fea109 | |||
| 6df89e7abf | |||
| 21afda6dc2 | |||
| 74c0ed27ba | |||
| dc680a3385 | |||
| 88e5b22d16 | |||
| 27cd10e072 | |||
| d35f524865 | |||
| ca223fa4df | |||
| 14962eb6cc | |||
| b9f409d6d9 | |||
| a8681ac88f | |||
| c1e6786ea1 | |||
| 1c8d101fc3 | |||
| 7a9140bdcd | |||
| 511f94fc7f | |||
| 548b1ead2f | |||
| 33f67140f2 | |||
| 8787dc23d0 | |||
| e0ae92ccc7 | |||
| bdb86d7119 | |||
| a1a3d316e3 | |||
| 672b86ef88 | |||
| a3c9d5873c | |||
| 0e975757b8 | |||
| 391ee10cb8 | |||
| 4f374c0c01 | |||
| dde303f13a | |||
| 264c678eaa | |||
| 854d94056e | |||
| 9d4c22c706 | |||
| 9b12895fab | |||
| 93478a55d7 | |||
| a76cbf8b70 | |||
| 6597d5bd28 | |||
| fd28f37c0d | |||
| d219f65e7a | |||
| 865f652476 | |||
| 8008918d8b | |||
| 75d0bd01c2 | |||
| 029c6cd182 | |||
| 71f771c22c | |||
| 0993d5ce4a | |||
| 38bd05867d | |||
| 79089d8981 | |||
| 44e51970e1 | |||
| 47bde052ca | |||
| bd6a473d4f | |||
| cd23053007 | |||
| 6e11fd0f2e | |||
| 277b4336d3 | |||
| 1c1f9b6cb8 | |||
| c23df5e1d5 | |||
| c47cef6fbf | |||
| 83b7b3257a | |||
| 270be95e68 | |||
| 1c919b8b88 | |||
| 1e51a2cdd7 | |||
| 7ba44b15a7 | |||
| 4a94f515b3 | |||
| b229b2f40d | |||
| e4f0613fab | |||
| ecff810021 | |||
| fdde97cbbf | |||
| c2a5641e6a | |||
| 5a47c4850d | |||
| 70b8a941bb | |||
| eb01b42425 | |||
| 8708e487ae | |||
| e020b8bf32 | |||
| 8e27121e10 | |||
| 06870b4f64 | |||
| 4cfcc48b23 | |||
| 60c244c31d | |||
| d122bddae2 | |||
| 69e6221906 | |||
| 68eefd083e | |||
| a647917074 | |||
| 099197ba8c | |||
| baa2ed5ecc | |||
| f8ba623fc1 | |||
| 6bcdf36ca6 | |||
| 416d949d80 | |||
| 0b75a0028b | |||
| 0901d7461e | |||
| 61772b75ff | |||
| 0ade57b5a6 | |||
| 61604adf9a | |||
| 8bd147b205 | |||
| 724f53e972 | |||
| c10478ec68 | |||
| cdf12ee03d | |||
| 964a8dbb82 | |||
| 7ad48bfc44 | |||
| da90510b98 | |||
| 4bd1598c2c | |||
| 6aa8d56d9f | |||
| ccf7d794e9 | |||
| 50ed2fb257 | |||
| 5ae030997a | |||
| 52dabcaad9 | |||
| 35e8a0c374 | |||
| be292729a5 | |||
| 1649c478b6 | |||
| 42feb54d80 | |||
| bbd088a957 | |||
| 5417d0a90c | |||
| 417b5d61a4 | |||
| f13aad21cb | |||
| 79e8ee46c0 | |||
| e3eaaeaf17 | |||
| e550216f85 | |||
| 1afb4a7a76 | |||
| 391eb9d469 | |||
| 494f094fa1 | |||
| aa0f5df218 | |||
| 6fc740a98b | |||
| 7da90ff7e4 | |||
| 61b5714652 | |||
| d2df426489 | |||
| e6c75ed173 | |||
| a353c6956e | |||
| a367d8515f | |||
| 2b7a22a29a | |||
| e6712a50d2 | |||
| c621f62d92 | |||
| a0648cd925 | |||
| 2650e672bb | |||
| 53b9376789 | |||
| d15e50025c | |||
| 0af66a26ab | |||
| bf754369d9 | |||
| 02dc112f8f | |||
| 2d4e7ebab5 | |||
| a7d0a50859 | |||
| 71c9108f89 | |||
| f8bcdb26b3 | |||
| 45f1d95bf9 | |||
| 5dab198c47 | |||
| ad91abe9de | |||
| fa30755241 | |||
| 552f8c6a9a | |||
| 101f916247 | |||
| 2acdcf74e1 | |||
| ddb8610032 | |||
| 22ad850e6c | |||
| 57925ed60a | |||
| 48cc2f17c1 | |||
| 448108fca0 | |||
| c1254f6212 | |||
| c8120c0d3e | |||
| 52016e0806 | |||
| e555bdd42b | |||
| 1a619c90de | |||
| 18faf30b0c | |||
| b3bd979ecd | |||
| db113c5e8f | |||
| 78bcb90a1e | |||
| b64ecbde22 | |||
| 43bab840ec | |||
| f020b79384 | |||
| 820f658b49 | |||
| 5d460a2537 | |||
| efc46f52e6 | |||
| 9fac51f8c7 | |||
| fe4b2d1a34 | |||
| f8abe3e210 | |||
| 3ced67b151 | |||
| cd5631ec76 | |||
| 95df7c7f30 | 
| @ -1,9 +1,11 @@ | ||||
| [bumpversion] | ||||
| current_version = 2021.2.1-rc2 | ||||
| current_version = 2021.3.1 | ||||
| tag = True | ||||
| commit = True | ||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*) | ||||
| serialize = {major}.{minor}.{patch}-{release} | ||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*) | ||||
| serialize =  | ||||
| 	{major}.{minor}.{patch}-{release} | ||||
| 	{major}.{minor}.{patch} | ||||
| message = release: {new_version} | ||||
| tag_name = version/{new_version} | ||||
|  | ||||
|  | ||||
							
								
								
									
										4
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							| @ -1,7 +1,7 @@ | ||||
| version: 2 | ||||
| updates: | ||||
| - package-ecosystem: gomod | ||||
|   directory: "/proxy" | ||||
|   directory: "/outpost" | ||||
|   schedule: | ||||
|     interval: daily | ||||
|     time: "04:00" | ||||
| @ -41,7 +41,7 @@ updates: | ||||
|   assignees: | ||||
|   - BeryJu | ||||
| - package-ecosystem: docker | ||||
|   directory: "/proxy" | ||||
|   directory: "/outpost" | ||||
|   schedule: | ||||
|     interval: daily | ||||
|     time: "04:00" | ||||
|  | ||||
							
								
								
									
										14
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @ -18,11 +18,11 @@ jobs: | ||||
|       - name: Building Docker Image | ||||
|         run: docker build | ||||
|           --no-cache | ||||
|           -t beryju/authentik:2021.2.1-rc2 | ||||
|           -t beryju/authentik:2021.3.1 | ||||
|           -t beryju/authentik:latest | ||||
|           -f Dockerfile . | ||||
|       - name: Push Docker Container to Registry (versioned) | ||||
|         run: docker push beryju/authentik:2021.2.1-rc2 | ||||
|         run: docker push beryju/authentik:2021.3.1 | ||||
|       - name: Push Docker Container to Registry (latest) | ||||
|         run: docker push beryju/authentik:latest | ||||
|   build-proxy: | ||||
| @ -48,11 +48,11 @@ jobs: | ||||
|           cd outpost/ | ||||
|           docker build \ | ||||
|           --no-cache \ | ||||
|           -t beryju/authentik-proxy:2021.2.1-rc2 \ | ||||
|           -t beryju/authentik-proxy:2021.3.1 \ | ||||
|           -t beryju/authentik-proxy:latest \ | ||||
|           -f proxy.Dockerfile . | ||||
|       - name: Push Docker Container to Registry (versioned) | ||||
|         run: docker push beryju/authentik-proxy:2021.2.1-rc2 | ||||
|         run: docker push beryju/authentik-proxy:2021.3.1 | ||||
|       - name: Push Docker Container to Registry (latest) | ||||
|         run: docker push beryju/authentik-proxy:latest | ||||
|   build-static: | ||||
| @ -69,11 +69,11 @@ jobs: | ||||
|           cd web/ | ||||
|           docker build \ | ||||
|           --no-cache \ | ||||
|           -t beryju/authentik-static:2021.2.1-rc2 \ | ||||
|           -t beryju/authentik-static:2021.3.1 \ | ||||
|           -t beryju/authentik-static:latest \ | ||||
|           -f Dockerfile . | ||||
|       - name: Push Docker Container to Registry (versioned) | ||||
|         run: docker push beryju/authentik-static:2021.2.1-rc2 | ||||
|         run: docker push beryju/authentik-static:2021.3.1 | ||||
|       - name: Push Docker Container to Registry (latest) | ||||
|         run: docker push beryju/authentik-static:latest | ||||
|   test-release: | ||||
| @ -107,5 +107,5 @@ jobs: | ||||
|           SENTRY_PROJECT: authentik | ||||
|           SENTRY_URL: https://sentry.beryju.org | ||||
|         with: | ||||
|           tagName: 2021.2.1-rc2 | ||||
|           tagName: 2021.3.1 | ||||
|           environment: beryjuorg-prod | ||||
|  | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -201,3 +201,4 @@ local.env.yml | ||||
| selenium_screenshots/ | ||||
| backups/ | ||||
| media/ | ||||
| *mmdb | ||||
|  | ||||
| @ -20,7 +20,7 @@ RUN apt-get update && \ | ||||
|     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 && \ | ||||
|     apt-get install -y --no-install-recommends postgresql-client-12 postgresql-client-11 build-essential libxmlsec1-dev pkg-config libmaxminddb0 && \ | ||||
|     apt-get clean && \ | ||||
|     pip install -r /requirements.txt --no-cache-dir && \ | ||||
|     apt-get remove --purge -y build-essential && \ | ||||
|  | ||||
							
								
								
									
										11
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								Makefile
									
									
									
									
									
								
							| @ -1,20 +1,15 @@ | ||||
| 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 --failfast -v 3 tests/integration | ||||
| 	coverage run manage.py test -v 3 tests/integration | ||||
|  | ||||
| test-e2e: | ||||
| 	coverage run manage.py test --failfast -v 3 tests/e2e | ||||
| 	coverage run manage.py test -v 3 tests/e2e | ||||
|  | ||||
| coverage: | ||||
| 	coverage run manage.py test --failfast -v 3 authentik | ||||
| 	coverage run manage.py test -v 3 authentik | ||||
| 	coverage html | ||||
| 	coverage report | ||||
|  | ||||
|  | ||||
							
								
								
									
										24
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								Pipfile
									
									
									
									
									
								
							| @ -6,6 +6,9 @@ verify_ssl = true | ||||
| [packages] | ||||
| boto3 = "*" | ||||
| celery = "*" | ||||
| channels = "*" | ||||
| channels-redis = "*" | ||||
| dacite = "*" | ||||
| defusedxml = "*" | ||||
| django = "*" | ||||
| django-cors-middleware = "*" | ||||
| @ -15,35 +18,33 @@ django-guardian = "*" | ||||
| django-model-utils = "*" | ||||
| django-otp = "*" | ||||
| django-prometheus = "*" | ||||
| django-recaptcha = "*" | ||||
| django-redis = "*" | ||||
| djangorestframework = "*" | ||||
| django-storages = "*" | ||||
| djangorestframework = "*" | ||||
| djangorestframework-guardian = "*" | ||||
| docker = "*" | ||||
| drf_yasg2 = "*" | ||||
| facebook-sdk = "*" | ||||
| geoip2 = "*" | ||||
| gunicorn = "*" | ||||
| kubernetes = "*" | ||||
| ldap3 = "*" | ||||
| lxml = "*" | ||||
| packaging = "*" | ||||
| psycopg2-binary = "*" | ||||
| pycryptodome = "*" | ||||
| pyjwkest = "*" | ||||
| uvicorn = {extras = ["standard"],version = "*"} | ||||
| gunicorn = "*" | ||||
| pyyaml = "*" | ||||
| qrcode = "*" | ||||
| requests-oauthlib = "*" | ||||
| sentry-sdk = "*" | ||||
| service_identity = "*" | ||||
| structlog = "*" | ||||
| swagger-spec-validator = "*" | ||||
| urllib3 = {extras = ["secure"],version = "*"} | ||||
| dacite = "*" | ||||
| channels = "*" | ||||
| channels-redis = "*" | ||||
| kubernetes = "*" | ||||
| docker = "*" | ||||
| uvicorn = {extras = ["standard"],version = "*"} | ||||
| webauthn = "*" | ||||
| xmlsec = "*" | ||||
| twisted = "==20.3.0" | ||||
|  | ||||
| [requires] | ||||
| python_version = "3.9" | ||||
| @ -55,8 +56,7 @@ black = "==20.8b1" | ||||
| bumpversion = "*" | ||||
| colorama = "*" | ||||
| coverage = "*" | ||||
| django-debug-toolbar = "*" | ||||
| pylint = "*" | ||||
| pylint = "<=2.6.0" | ||||
| pylint-django = "*" | ||||
| selenium = "*" | ||||
| prospector = "*" | ||||
|  | ||||
							
								
								
									
										681
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										681
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							| @ -1,7 +1,7 @@ | ||||
| { | ||||
|     "_meta": { | ||||
|         "hash": { | ||||
|             "sha256": "53ad00e394a5f2e2462837c4ceff837d1e593469af9505726048bed72ce0b81a" | ||||
|             "sha256": "d2d51b999d57d01e1f8e6450ae54f8e58e1eb213ae457cc43f7314daa8e11541" | ||||
|         }, | ||||
|         "pipfile-spec": 6, | ||||
|         "requires": { | ||||
| @ -16,6 +16,48 @@ | ||||
|         ] | ||||
|     }, | ||||
|     "default": { | ||||
|         "aiohttp": { | ||||
|             "hashes": [ | ||||
|                 "sha256:119feb2bd551e58d83d1b38bfa4cb921af8ddedec9fad7183132db334c3133e0", | ||||
|                 "sha256:16d0683ef8a6d803207f02b899c928223eb219111bd52420ef3d7a8aa76227b6", | ||||
|                 "sha256:2eb3efe243e0f4ecbb654b08444ae6ffab37ac0ef8f69d3a2ffb958905379daf", | ||||
|                 "sha256:2ffea7904e70350da429568113ae422c88d2234ae776519549513c8f217f58a9", | ||||
|                 "sha256:40bd1b101b71a18a528ffce812cc14ff77d4a2a1272dfb8b11b200967489ef3e", | ||||
|                 "sha256:418597633b5cd9639e514b1d748f358832c08cd5d9ef0870026535bd5eaefdd0", | ||||
|                 "sha256:481d4b96969fbfdcc3ff35eea5305d8565a8300410d3d269ccac69e7256b1329", | ||||
|                 "sha256:4c1bdbfdd231a20eee3e56bd0ac1cd88c4ff41b64ab679ed65b75c9c74b6c5c2", | ||||
|                 "sha256:5563ad7fde451b1986d42b9bb9140e2599ecf4f8e42241f6da0d3d624b776f40", | ||||
|                 "sha256:58c62152c4c8731a3152e7e650b29ace18304d086cb5552d317a54ff2749d32a", | ||||
|                 "sha256:5b50e0b9460100fe05d7472264d1975f21ac007b35dcd6fd50279b72925a27f4", | ||||
|                 "sha256:5d84ecc73141d0a0d61ece0742bb7ff5751b0657dab8405f899d3ceb104cc7de", | ||||
|                 "sha256:5dde6d24bacac480be03f4f864e9a67faac5032e28841b00533cd168ab39cad9", | ||||
|                 "sha256:5e91e927003d1ed9283dee9abcb989334fc8e72cf89ebe94dc3e07e3ff0b11e9", | ||||
|                 "sha256:62bc216eafac3204877241569209d9ba6226185aa6d561c19159f2e1cbb6abfb", | ||||
|                 "sha256:6c8200abc9dc5f27203986100579fc19ccad7a832c07d2bc151ce4ff17190076", | ||||
|                 "sha256:6ca56bdfaf825f4439e9e3673775e1032d8b6ea63b8953d3812c71bd6a8b81de", | ||||
|                 "sha256:71680321a8a7176a58dfbc230789790639db78dad61a6e120b39f314f43f1907", | ||||
|                 "sha256:7c7820099e8b3171e54e7eedc33e9450afe7cd08172632d32128bd527f8cb77d", | ||||
|                 "sha256:7dbd087ff2f4046b9b37ba28ed73f15fd0bc9f4fdc8ef6781913da7f808d9536", | ||||
|                 "sha256:822bd4fd21abaa7b28d65fc9871ecabaddc42767884a626317ef5b75c20e8a2d", | ||||
|                 "sha256:8ec1a38074f68d66ccb467ed9a673a726bb397142c273f90d4ba954666e87d54", | ||||
|                 "sha256:950b7ef08b2afdab2488ee2edaff92a03ca500a48f1e1aaa5900e73d6cf992bc", | ||||
|                 "sha256:99c5a5bf7135607959441b7d720d96c8e5c46a1f96e9d6d4c9498be8d5f24212", | ||||
|                 "sha256:b84ad94868e1e6a5e30d30ec419956042815dfaea1b1df1cef623e4564c374d9", | ||||
|                 "sha256:bc3d14bf71a3fb94e5acf5bbf67331ab335467129af6416a437bd6024e4f743d", | ||||
|                 "sha256:c2a80fd9a8d7e41b4e38ea9fe149deed0d6aaede255c497e66b8213274d6d61b", | ||||
|                 "sha256:c44d3c82a933c6cbc21039326767e778eface44fca55c65719921c4b9661a3f7", | ||||
|                 "sha256:cc31e906be1cc121ee201adbdf844522ea3349600dd0a40366611ca18cd40e81", | ||||
|                 "sha256:d5d102e945ecca93bcd9801a7bb2fa703e37ad188a2f81b1e65e4abe4b51b00c", | ||||
|                 "sha256:dd7936f2a6daa861143e376b3a1fb56e9b802f4980923594edd9ca5670974895", | ||||
|                 "sha256:dee68ec462ff10c1d836c0ea2642116aba6151c6880b688e56b4c0246770f297", | ||||
|                 "sha256:e76e78863a4eaec3aee5722d85d04dcbd9844bc6cd3bfa6aa880ff46ad16bfcb", | ||||
|                 "sha256:eab51036cac2da8a50d7ff0ea30be47750547c9aa1aa2cf1a1b710a1827e7dbe", | ||||
|                 "sha256:f4496d8d04da2e98cc9133e238ccebf6a13ef39a93da2e87146c8c8ac9768242", | ||||
|                 "sha256:fbd3b5e18d34683decc00d9a360179ac1e7a320a5fee10ab8053ffd6deab76e0", | ||||
|                 "sha256:feb24ff1226beeb056e247cf2e24bba5232519efb5645121c4aea5b6ad74c1f2" | ||||
|             ], | ||||
|             "version": "==3.7.4" | ||||
|         }, | ||||
|         "aioredis": { | ||||
|             "hashes": [ | ||||
|                 "sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a", | ||||
| @ -53,10 +95,10 @@ | ||||
|         }, | ||||
|         "autobahn": { | ||||
|             "hashes": [ | ||||
|                 "sha256:93df8fc9d1821c9dabff9fed52181a9ad6eea5e9989d53102c391607d7c1666e", | ||||
|                 "sha256:cceed2121b7a93024daa93c91fae33007f8346f0e522796421f36a6183abea99" | ||||
|                 "sha256:9195df8af03b0ff29ccd4b7f5abbde957ee90273465942205f9a1bad6c3f07ac", | ||||
|                 "sha256:e126c1f583e872fb59e79d36977cfa1f2d0a8a79f90ae31f406faae7664b8e03" | ||||
|             ], | ||||
|             "version": "==21.1.1" | ||||
|             "version": "==21.3.1" | ||||
|         }, | ||||
|         "automat": { | ||||
|             "hashes": [ | ||||
| @ -74,18 +116,18 @@ | ||||
|         }, | ||||
|         "boto3": { | ||||
|             "hashes": [ | ||||
|                 "sha256:92041aa7589c886020cabd80eb58b89ace2f0094571792fccae24b9a8b3b97d7", | ||||
|                 "sha256:9f132c34e20110dea019293c89cede49b0a56be615b3e1debf98390ed9f1f7b9" | ||||
|                 "sha256:c9513a9ea00f8d17ecdc02c391ae956bf0f990aa07deec11c421607c09b294e1", | ||||
|                 "sha256:f84ca60e9605af69022f039c035b33d519531eeaac52724b9223a5465f4a8b6b" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==1.17.3" | ||||
|             "version": "==1.17.19" | ||||
|         }, | ||||
|         "botocore": { | ||||
|             "hashes": [ | ||||
|                 "sha256:1dae84c68b109f596f58cc2e9fa87704ccd40dcbc12144a89205f85efa7f9135", | ||||
|                 "sha256:a0fdded1c9636899ab273f50bf123f79b91439a8c282b5face8b5f4a48b493cb" | ||||
|                 "sha256:135b5f30e6662b46d804f993bf31d9c7769c6c0848321ed0aa0393f5b9c19a94", | ||||
|                 "sha256:8e42c78d2eb888551635309158c04ef2648a96d8c2c70dbce7712c6ce8629759" | ||||
|             ], | ||||
|             "version": "==1.20.3" | ||||
|             "version": "==1.20.19" | ||||
|         }, | ||||
|         "cachetools": { | ||||
|             "hashes": [ | ||||
| @ -94,6 +136,12 @@ | ||||
|             ], | ||||
|             "version": "==4.2.1" | ||||
|         }, | ||||
|         "cbor2": { | ||||
|             "hashes": [ | ||||
|                 "sha256:a33aa2e5534fd74401ac95686886e655e3b2ce6383b3f958199b6e70a87c94bf" | ||||
|             ], | ||||
|             "version": "==5.2.0" | ||||
|         }, | ||||
|         "celery": { | ||||
|             "hashes": [ | ||||
|                 "sha256:5e8d364e058554e83bbb116e8377d90c79be254785f357cb2cec026e79febe13", | ||||
| @ -111,45 +159,45 @@ | ||||
|         }, | ||||
|         "cffi": { | ||||
|             "hashes": [ | ||||
|                 "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e", | ||||
|                 "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d", | ||||
|                 "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a", | ||||
|                 "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec", | ||||
|                 "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362", | ||||
|                 "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668", | ||||
|                 "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c", | ||||
|                 "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b", | ||||
|                 "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06", | ||||
|                 "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698", | ||||
|                 "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2", | ||||
|                 "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c", | ||||
|                 "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7", | ||||
|                 "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009", | ||||
|                 "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03", | ||||
|                 "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b", | ||||
|                 "sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e", | ||||
|                 "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909", | ||||
|                 "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53", | ||||
|                 "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35", | ||||
|                 "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26", | ||||
|                 "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b", | ||||
|                 "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01", | ||||
|                 "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb", | ||||
|                 "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293", | ||||
|                 "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd", | ||||
|                 "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d", | ||||
|                 "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3", | ||||
|                 "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d", | ||||
|                 "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e", | ||||
|                 "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca", | ||||
|                 "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d", | ||||
|                 "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775", | ||||
|                 "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375", | ||||
|                 "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b", | ||||
|                 "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b", | ||||
|                 "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f" | ||||
|                 "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", | ||||
|                 "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", | ||||
|                 "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", | ||||
|                 "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", | ||||
|                 "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", | ||||
|                 "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", | ||||
|                 "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", | ||||
|                 "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", | ||||
|                 "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", | ||||
|                 "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", | ||||
|                 "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", | ||||
|                 "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", | ||||
|                 "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5", | ||||
|                 "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", | ||||
|                 "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", | ||||
|                 "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", | ||||
|                 "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", | ||||
|                 "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", | ||||
|                 "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", | ||||
|                 "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369", | ||||
|                 "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827", | ||||
|                 "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053", | ||||
|                 "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa", | ||||
|                 "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4", | ||||
|                 "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322", | ||||
|                 "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132", | ||||
|                 "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62", | ||||
|                 "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa", | ||||
|                 "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0", | ||||
|                 "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396", | ||||
|                 "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", | ||||
|                 "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", | ||||
|                 "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", | ||||
|                 "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", | ||||
|                 "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", | ||||
|                 "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", | ||||
|                 "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" | ||||
|             ], | ||||
|             "version": "==1.14.4" | ||||
|             "version": "==1.14.5" | ||||
|         }, | ||||
|         "channels": { | ||||
|             "hashes": [ | ||||
| @ -169,10 +217,10 @@ | ||||
|         }, | ||||
|         "chardet": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", | ||||
|                 "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" | ||||
|                 "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", | ||||
|                 "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" | ||||
|             ], | ||||
|             "version": "==4.0.0" | ||||
|             "version": "==3.0.4" | ||||
|         }, | ||||
|         "click": { | ||||
|             "hashes": [ | ||||
| @ -224,22 +272,20 @@ | ||||
|         }, | ||||
|         "cryptography": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0003a52a123602e1acee177dc90dd201f9bb1e73f24a070db7d36c588e8f5c7d", | ||||
|                 "sha256:0e85aaae861d0485eb5a79d33226dd6248d2a9f133b81532c8f5aae37de10ff7", | ||||
|                 "sha256:594a1db4511bc4d960571536abe21b4e5c3003e8750ab8365fafce71c5d86901", | ||||
|                 "sha256:69e836c9e5ff4373ce6d3ab311c1a2eed274793083858d3cd4c7d12ce20d5f9c", | ||||
|                 "sha256:788a3c9942df5e4371c199d10383f44a105d67d401fb4304178020142f020244", | ||||
|                 "sha256:7e177e4bea2de937a584b13645cab32f25e3d96fc0bc4a4cf99c27dc77682be6", | ||||
|                 "sha256:83d9d2dfec70364a74f4e7c70ad04d3ca2e6a08b703606993407bf46b97868c5", | ||||
|                 "sha256:84ef7a0c10c24a7773163f917f1cb6b4444597efd505a8aed0a22e8c4780f27e", | ||||
|                 "sha256:9e21301f7a1e7c03dbea73e8602905a4ebba641547a462b26dd03451e5769e7c", | ||||
|                 "sha256:9f6b0492d111b43de5f70052e24c1f0951cb9e6022188ebcb1cc3a3d301469b0", | ||||
|                 "sha256:a69bd3c68b98298f490e84519b954335154917eaab52cf582fa2c5c7efc6e812", | ||||
|                 "sha256:b4890d5fb9b7a23e3bf8abf5a8a7da8e228f1e97dc96b30b95685df840b6914a", | ||||
|                 "sha256:c366df0401d1ec4e548bebe8f91d55ebcc0ec3137900d214dd7aac8427ef3030", | ||||
|                 "sha256:dc42f645f8f3a489c3dd416730a514e7a91a59510ddaadc09d04224c098d3302" | ||||
|                 "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b", | ||||
|                 "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336", | ||||
|                 "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87", | ||||
|                 "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7", | ||||
|                 "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799", | ||||
|                 "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b", | ||||
|                 "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df", | ||||
|                 "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0", | ||||
|                 "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3", | ||||
|                 "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724", | ||||
|                 "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2", | ||||
|                 "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964" | ||||
|             ], | ||||
|             "version": "==3.3.1" | ||||
|             "version": "==3.4.6" | ||||
|         }, | ||||
|         "dacite": { | ||||
|             "hashes": [ | ||||
| @ -266,11 +312,11 @@ | ||||
|         }, | ||||
|         "django": { | ||||
|             "hashes": [ | ||||
|                 "sha256:169e2e7b4839a7910b393eec127fd7cbae62e80fa55f89c6510426abf673fe5f", | ||||
|                 "sha256:c6c0462b8b361f8691171af1fb87eceb4442da28477e12200c40420176206ba7" | ||||
|                 "sha256:32ce792ee9b6a0cbbec340123e229ac9f765dff8c2a4ae9247a14b2ba3a365a7", | ||||
|                 "sha256:baf099db36ad31f970775d0be5587cc58a6256a6771a44eb795b554d45f211b8" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==3.1.6" | ||||
|             "version": "==3.1.7" | ||||
|         }, | ||||
|         "django-cors-middleware": { | ||||
|             "hashes": [ | ||||
| @ -327,13 +373,6 @@ | ||||
|             "index": "pypi", | ||||
|             "version": "==2.1.0" | ||||
|         }, | ||||
|         "django-recaptcha": { | ||||
|             "hashes": [ | ||||
|                 "sha256:567784963fd5400feaf92e8951d8dbbbdb4b4c48a76e225d4baa63a2c9d2cd8c" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==2.0.6" | ||||
|         }, | ||||
|         "django-redis": { | ||||
|             "hashes": [ | ||||
|                 "sha256:1133b26b75baa3664164c3f44b9d5d133d1b8de45d94d79f38d1adc5b1d502e5", | ||||
| @ -368,11 +407,11 @@ | ||||
|         }, | ||||
|         "docker": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0604a74719d5d2de438753934b755bfcda6f62f49b8e4b30969a4b0a2a8a1220", | ||||
|                 "sha256:e455fa49aabd4f22da9f4e1c1f9d16308286adc60abaf64bf3e1feafaed81d06" | ||||
|                 "sha256:d3393c878f575d3a9ca3b94471a3c89a6d960b35feb92f033c0de36cc9d934db", | ||||
|                 "sha256:f3607d5695be025fa405a12aca2e5df702a57db63790c73b927eb6a94aac60af" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==4.4.1" | ||||
|             "version": "==4.4.4" | ||||
|         }, | ||||
|         "drf-yasg2": { | ||||
|             "hashes": [ | ||||
| @ -396,12 +435,20 @@ | ||||
|             ], | ||||
|             "version": "==0.18.2" | ||||
|         }, | ||||
|         "geoip2": { | ||||
|             "hashes": [ | ||||
|                 "sha256:57d8d15de2527e0697bbef44fc16812bba709f03a07ef99297bd56c1df3b1efd", | ||||
|                 "sha256:707025542ef076bd8fd80e97138bebdb7812527b2a007d141a27ad98b0370fff" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==4.1.0" | ||||
|         }, | ||||
|         "google-auth": { | ||||
|             "hashes": [ | ||||
|                 "sha256:008e23ed080674f69f9d2d7d80db4c2591b9bb307d136cea7b3bc129771d211d", | ||||
|                 "sha256:514e39f4190ca972200ba33876da5a8857c5665f2b4ccc36c8b8ee21228aae80" | ||||
|                 "sha256:d3640ea61ee025d5af00e3ffd82ba0a06dd99724adaf50bdd52f49daf29f3f65", | ||||
|                 "sha256:da5218cbf33b8461d7661d6b4ad91c12c0107e2767904d5e3ae6408031d5463e" | ||||
|             ], | ||||
|             "version": "==1.25.0" | ||||
|             "version": "==1.27.0" | ||||
|         }, | ||||
|         "gunicorn": { | ||||
|             "hashes": [ | ||||
| @ -502,10 +549,10 @@ | ||||
|         }, | ||||
|         "incremental": { | ||||
|             "hashes": [ | ||||
|                 "sha256:717e12246dddf231a349175f48d74d93e2897244939173b01974ab6661406b9f", | ||||
|                 "sha256:7b751696aaf36eebfab537e458929e194460051ccad279c72b755a167eebd4b3" | ||||
|                 "sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57", | ||||
|                 "sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321" | ||||
|             ], | ||||
|             "version": "==17.5.0" | ||||
|             "version": "==21.3.0" | ||||
|         }, | ||||
|         "inflection": { | ||||
|             "hashes": [ | ||||
| @ -665,6 +712,12 @@ | ||||
|             ], | ||||
|             "version": "==1.1.1" | ||||
|         }, | ||||
|         "maxminddb": { | ||||
|             "hashes": [ | ||||
|                 "sha256:47e86a084dd814fac88c99ea34ba3278a74bc9de5a25f4b815b608798747c7dc" | ||||
|             ], | ||||
|             "version": "==2.0.3" | ||||
|         }, | ||||
|         "msgpack": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0cb94ee48675a45d3b86e61d13c1e6f1696f0183f0715544976356ff86f741d9", | ||||
| @ -698,6 +751,48 @@ | ||||
|             ], | ||||
|             "version": "==1.0.2" | ||||
|         }, | ||||
|         "multidict": { | ||||
|             "hashes": [ | ||||
|                 "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a", | ||||
|                 "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93", | ||||
|                 "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632", | ||||
|                 "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656", | ||||
|                 "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79", | ||||
|                 "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7", | ||||
|                 "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d", | ||||
|                 "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5", | ||||
|                 "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224", | ||||
|                 "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26", | ||||
|                 "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea", | ||||
|                 "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348", | ||||
|                 "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6", | ||||
|                 "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76", | ||||
|                 "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1", | ||||
|                 "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f", | ||||
|                 "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952", | ||||
|                 "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a", | ||||
|                 "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37", | ||||
|                 "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9", | ||||
|                 "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359", | ||||
|                 "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8", | ||||
|                 "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da", | ||||
|                 "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3", | ||||
|                 "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d", | ||||
|                 "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf", | ||||
|                 "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841", | ||||
|                 "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d", | ||||
|                 "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93", | ||||
|                 "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f", | ||||
|                 "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647", | ||||
|                 "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635", | ||||
|                 "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456", | ||||
|                 "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda", | ||||
|                 "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5", | ||||
|                 "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281", | ||||
|                 "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80" | ||||
|             ], | ||||
|             "version": "==5.1.0" | ||||
|         }, | ||||
|         "oauthlib": { | ||||
|             "hashes": [ | ||||
|                 "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", | ||||
| @ -722,10 +817,10 @@ | ||||
|         }, | ||||
|         "prompt-toolkit": { | ||||
|             "hashes": [ | ||||
|                 "sha256:7e966747c18ececaec785699626b771c1ba8344c8d31759a1915d6b12fad6525", | ||||
|                 "sha256:c96b30925025a7635471dc083ffb6af0cc67482a00611bd81aeaeeeb7e5a5e12" | ||||
|                 "sha256:0fa02fa80363844a4ab4b8d6891f62dd0645ba672723130423ca4037b80c1974", | ||||
|                 "sha256:62c811e46bd09130fb11ab759012a4ae385ce4fb2073442d1898867a824183bd" | ||||
|             ], | ||||
|             "version": "==3.0.14" | ||||
|             "version": "==3.0.16" | ||||
|         }, | ||||
|         "psycopg2-binary": { | ||||
|             "hashes": [ | ||||
| @ -791,84 +886,74 @@ | ||||
|         }, | ||||
|         "pycryptodome": { | ||||
|             "hashes": [ | ||||
|                 "sha256:19cb674df6c74a14b8b408aa30ba8a89bd1c01e23505100fb45f930fbf0ed0d9", | ||||
|                 "sha256:1cfdb92dca388e27e732caa72a1cc624520fe93752a665c3b6cd8f1a91b34916", | ||||
|                 "sha256:27397aee992af69d07502126561d851ba3845aa808f0e55c71ad0efa264dd7d4", | ||||
|                 "sha256:28f75e58d02019a7edc7d4135203d2501dfc47256d175c72c9798f9a129a49a7", | ||||
|                 "sha256:2a68df525b387201a43b27b879ce8c08948a430e883a756d6c9e3acdaa7d7bd8", | ||||
|                 "sha256:411745c6dce4eff918906eebcde78771d44795d747e194462abb120d2e537cd9", | ||||
|                 "sha256:46e96aeb8a9ca8b1edf9b1fd0af4bf6afcf3f1ca7fa35529f5d60b98f3e4e959", | ||||
|                 "sha256:4ed27951b0a17afd287299e2206a339b5b6d12de9321e1a1575261ef9c4a851b", | ||||
|                 "sha256:50826b49fbca348a61529693b0031cdb782c39060fb9dca5ac5dff858159dc5a", | ||||
|                 "sha256:5598dc6c9dbfe882904e54584322893eff185b98960bbe2cdaaa20e8a437b6e5", | ||||
|                 "sha256:5c3c4865730dfb0263f822b966d6d58429d8b1e560d1ddae37685fd9e7c63161", | ||||
|                 "sha256:5f19e6ef750f677d924d9c7141f54bade3cd56695bbfd8a9ef15d0378557dfe4", | ||||
|                 "sha256:60febcf5baf70c566d9d9351c47fbd8321da9a4edf2eff45c4c31c86164ca794", | ||||
|                 "sha256:62c488a21c253dadc9f731a32f0ac61e4e436d81a1ea6f7d1d9146ed4d20d6bd", | ||||
|                 "sha256:6d3baaf82681cfb1a842f1c8f77beac791ceedd99af911e4f5fabec32bae2259", | ||||
|                 "sha256:6e4227849e4231a3f5b35ea5bdedf9a82b3883500e5624f00a19156e9a9ef861", | ||||
|                 "sha256:6e89bb3826e6f84501e8e3b205c22595d0c5492c2f271cbb9ee1c48eb1866645", | ||||
|                 "sha256:70d807d11d508433daf96244ec1c64e55039e8a35931fc5ea9eee94dbe3cb6b5", | ||||
|                 "sha256:76b1a34d74bb2c91bce460cdc74d1347592045627a955e9a252554481c17c52f", | ||||
|                 "sha256:7798e73225a699651888489fbb1dbc565e03a509942a8ce6194bbe6fb582a41f", | ||||
|                 "sha256:834b790bbb6bd18956f625af4004d9c15eed12d5186d8e57851454ae76d52215", | ||||
|                 "sha256:843e5f10ecdf9d307032b8b91afe9da1d6ed5bb89d0bbec5c8dcb4ba44008e11", | ||||
|                 "sha256:8f9f84059039b672a5a705b3c5aa21747867bacc30a72e28bf0d147cc8ef85ed", | ||||
|                 "sha256:9000877383e2189dafd1b2fc68c6c726eca9a3cfb6d68148fbb72ccf651959b6", | ||||
|                 "sha256:910e202a557e1131b1c1b3f17a63914d57aac55cf9fb9b51644962841c3995c4", | ||||
|                 "sha256:946399d15eccebafc8ce0257fc4caffe383c75e6b0633509bd011e357368306c", | ||||
|                 "sha256:a199e9ca46fc6e999e5f47fce342af4b56c7de85fae893c69ab6aa17531fb1e1", | ||||
|                 "sha256:a3d8a9efa213be8232c59cdc6b65600276508e375e0a119d710826248fd18d37", | ||||
|                 "sha256:a4599c0ca0fc027c780c1c45ed996d5bef03e571470b7b1c7171ec1e1a90914c", | ||||
|                 "sha256:b4e6b269a8ddaede774e5c3adbef6bf452ee144e6db8a716d23694953348cd86", | ||||
|                 "sha256:b68794fba45bdb367eeb71249c26d23e61167510a1d0c3d6cf0f2f14636e62ee", | ||||
|                 "sha256:d7ec2bd8f57c559dd24e71891c51c25266a8deb66fc5f02cc97c7fb593d1780a", | ||||
|                 "sha256:e15bde67ccb7d4417f627dd16ffe2f5a4c2941ce5278444e884cb26d73ecbc61", | ||||
|                 "sha256:eb01f9997e4d6a8ec8a1ad1f676ba5a362781ff64e8189fe2985258ba9cb9706", | ||||
|                 "sha256:faa682c404c218e8788c3126c9a4b8fbcc54dc245b5b6e8ea5b46f3b63bd0c84" | ||||
|                 "sha256:09c1555a3fa450e7eaca41ea11cd00afe7c91fef52353488e65663777d8524e0", | ||||
|                 "sha256:12222a5edc9ca4a29de15fbd5339099c4c26c56e13c2ceddf0b920794f26165d", | ||||
|                 "sha256:1723ebee5561628ce96748501cdaa7afaa67329d753933296321f0be55358dce", | ||||
|                 "sha256:1c5e1ca507de2ad93474be5cfe2bfa76b7cf039a1a32fc196f40935944871a06", | ||||
|                 "sha256:2603c98ae04aac675fefcf71a6c87dc4bb74a75e9071ae3923bbc91a59f08d35", | ||||
|                 "sha256:2dea65df54349cdfa43d6b2e8edb83f5f8d6861e5cf7b1fbc3e34c5694c85e27", | ||||
|                 "sha256:31c1df17b3dc5f39600a4057d7db53ac372f492c955b9b75dd439f5d8b460129", | ||||
|                 "sha256:38661348ecb71476037f1e1f553159b80d256c00f6c0b00502acac891f7116d9", | ||||
|                 "sha256:3e2e3a06580c5f190df843cdb90ea28d61099cf4924334d5297a995de68e4673", | ||||
|                 "sha256:3f840c49d38986f6e17dbc0673d37947c88bc9d2d9dba1c01b979b36f8447db1", | ||||
|                 "sha256:501ab36aae360e31d0ec370cf5ce8ace6cb4112060d099b993bc02b36ac83fb6", | ||||
|                 "sha256:60386d1d4cfaad299803b45a5bc2089696eaf6cdd56f9fc17479a6f89595cfc8", | ||||
|                 "sha256:6260e24d41149268122dd39d4ebd5941e9d107f49463f7e071fd397e29923b0c", | ||||
|                 "sha256:6bbf7fee7b7948b29d7e71fcacf48bac0c57fb41332007061a933f2d996f9713", | ||||
|                 "sha256:6d2df5223b12437e644ce0a3be7809471ffa71de44ccd28b02180401982594a6", | ||||
|                 "sha256:758949ca62690b1540dfb24ad773c6da9cd0e425189e83e39c038bbd52b8e438", | ||||
|                 "sha256:77997519d8eb8a4adcd9a47b9cec18f9b323e296986528186c0e9a7a15d6a07e", | ||||
|                 "sha256:7fd519b89585abf57bf47d90166903ec7b43af4fe23c92273ea09e6336af5c07", | ||||
|                 "sha256:98213ac2b18dc1969a47bc65a79a8fca02a414249d0c8635abb081c7f38c91b6", | ||||
|                 "sha256:99b2f3fc51d308286071d0953f92055504a6ffe829a832a9fc7a04318a7683dd", | ||||
|                 "sha256:9b6f711b25e01931f1c61ce0115245a23cdc8b80bf8539ac0363bdcf27d649b6", | ||||
|                 "sha256:a3105a0eb63eacf98c2ecb0eb4aa03f77f40fbac2bdde22020bb8a536b226bb8", | ||||
|                 "sha256:a8eb8b6ea09ec1c2535bf39914377bc8abcab2c7d30fa9225eb4fe412024e427", | ||||
|                 "sha256:a92d5c414e8ee1249e850789052608f582416e82422502dc0ac8c577808a9067", | ||||
|                 "sha256:d3d6958d53ad307df5e8469cc44474a75393a434addf20ecd451f38a72fe29b8", | ||||
|                 "sha256:e0a4d5933a88a2c98bbe19c0c722f5483dc628d7a38338ac2cb64a7dbd34064b", | ||||
|                 "sha256:e3bf558c6aeb49afa9f0c06cee7fb5947ee5a1ff3bd794b653d39926b49077fa", | ||||
|                 "sha256:e61e363d9a5d7916f3a4ce984a929514c0df3daf3b1b2eb5e6edbb131ee771cf", | ||||
|                 "sha256:f977cdf725b20f6b8229b0c87acb98c7717e742ef9f46b113985303ae12a99da", | ||||
|                 "sha256:fc7489a50323a0df02378bc2fff86eb69d94cc5639914346c736be981c6a02e7" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==3.9.9" | ||||
|             "version": "==3.10.1" | ||||
|         }, | ||||
|         "pycryptodomex": { | ||||
|             "hashes": [ | ||||
|                 "sha256:15c03ffdac17731b126880622823d30d0a3cc7203cd219e6b9814140a44e7fab", | ||||
|                 "sha256:20fb7f4efc494016eab1bc2f555bc0a12dd5ca61f35c95df8061818ffb2c20a3", | ||||
|                 "sha256:28ee3bcb4d609aea3040cad995a8e2c9c6dc57c12183dadd69e53880c35333b9", | ||||
|                 "sha256:305e3c46f20d019cd57543c255e7ba49e432e275d7c0de8913b6dbe57a851bc8", | ||||
|                 "sha256:3547b87b16aad6afb28c9b3a9cd870e11b5e7b5ac649b74265258d96d8de1130", | ||||
|                 "sha256:3642252d7bfc4403a42050e18ba748bedebd5a998a8cba89665a4f42aea4c380", | ||||
|                 "sha256:404faa3e518f8bea516aae2aac47d4d960397199a15b4bd6f66cad97825469a0", | ||||
|                 "sha256:42669638e4f7937b7141044a2fbd1019caca62bd2cdd8b535f731426ab07bde1", | ||||
|                 "sha256:4632d55a140b28e20be3cd7a3057af52fb747298ff0fd3290d4e9f245b5004ba", | ||||
|                 "sha256:4a88c9383d273bdce3afc216020282c9c5c39ec0bd9462b1a206af6afa377cf0", | ||||
|                 "sha256:4ce1fc1e6d2fd2d6dc197607153327989a128c093e0e94dca63408f506622c3e", | ||||
|                 "sha256:55cf4e99b3ba0122dee570dc7661b97bf35c16aab3e2ccb5070709d282a1c7ab", | ||||
|                 "sha256:5e486cab2dfcfaec934dd4f5d5837f4a9428b690f4d92a3b020fd31d1497ca64", | ||||
|                 "sha256:65ec88c8271448d2ea109d35c1f297b09b872c57214ab7e832e413090d3469a9", | ||||
|                 "sha256:6c95a3361ce70068cf69526a58751f73ddac5ba27a3c2379b057efa2f5338c8c", | ||||
|                 "sha256:73240335f4a1baf12880ebac6df66ab4d3a9212db9f3efe809c36a27280d16f8", | ||||
|                 "sha256:7651211e15109ac0058a49159265d9f6e6423c8a81c65434d3c56d708417a05b", | ||||
|                 "sha256:7b5b7c5896f8172ea0beb283f7f9428e0ab88ec248ce0a5b8c98d73e26267d51", | ||||
|                 "sha256:836fe39282e75311ce4c38468be148f7fac0df3d461c5de58c5ff1ddb8966bac", | ||||
|                 "sha256:871852044f55295449fbf225538c2c4118525093c32f0a6c43c91bed0452d7e3", | ||||
|                 "sha256:892e93f3e7e10c751d6c17fa0dc422f7984cfd5eb6690011f9264dc73e2775fc", | ||||
|                 "sha256:934e460c5058346c6f1d62fdf3db5680fbdfbfd212722d24d8277bf47cd9ebdc", | ||||
|                 "sha256:9736f3f3e1761024200637a080a4f922f5298ad5d780e10dbb5634fe8c65b34c", | ||||
|                 "sha256:a1d38a96da57e6103423a446079ead600b450cf0f8ebf56a231895abf77e7ffc", | ||||
|                 "sha256:a385fceaa0cdb97f0098f1c1e9ec0b46cc09186ddf60ec23538e871b1dddb6dc", | ||||
|                 "sha256:a7cf1c14e47027d9fb9d26aa62e5d603994227bd635e58a8df4b1d2d1b6a8ed7", | ||||
|                 "sha256:a9aac1a30b00b5038d3d8e48248f3b58ea15c827b67325c0d18a447552e30fc8", | ||||
|                 "sha256:b696876ee583d15310be57311e90e153a84b7913ac93e6b99675c0c9867926d0", | ||||
|                 "sha256:bef9e9d39393dc7baec39ba4bac6c73826a4db02114cdeade2552a9d6afa16e2", | ||||
|                 "sha256:c885fe4d5f26ce8ca20c97d02e88f5fdd92c01e1cc771ad0951b21e1641faf6d", | ||||
|                 "sha256:d2d1388595cb5d27d9220d5cbaff4f37c6ec696a25882eb06d224d241e6e93fb", | ||||
|                 "sha256:d2e853e0f9535e693fade97768cf7293f3febabecc5feb1e9b2ffdfe1044ab96", | ||||
|                 "sha256:d62fbab185a6b01c5469eda9f0795f3d1a5bba24f5a5813f362e4b73a3c4dc70", | ||||
|                 "sha256:f20a62397e09704049ce9007bea4f6bad965ba9336a760c6f4ef1b4192e12d6d", | ||||
|                 "sha256:f81f7311250d9480e36dec819127897ae772e7e8de07abfabe931b8566770b8e" | ||||
|                 "sha256:00a584ee52bf5e27d540129ca9bf7c4a7e7447f24ff4a220faa1304ad0c09bcd", | ||||
|                 "sha256:04265a7a84ae002001249bd1de2823bcf46832bd4b58f6965567cb8a07cf4f00", | ||||
|                 "sha256:0bd35af6a18b724c689e56f2dbbdd8e409288be71952d271ba3d9614b31d188c", | ||||
|                 "sha256:20c45a30f3389148f94edb77f3b216c677a277942f62a2b81a1cc0b6b2dde7fc", | ||||
|                 "sha256:2959304d1ce31ab303d9fb5db2b294814278b35154d9b30bf7facc52d6088d0a", | ||||
|                 "sha256:36dab7f506948056ceba2d57c1ade74e898401960de697cefc02f3519bd26c1b", | ||||
|                 "sha256:37ec1b407ec032c7a0c1fdd2da12813f560bad38ae61ad9c7ce3c0573b3e5e30", | ||||
|                 "sha256:3b8eb85b3cc7f083d87978c264d10ff9de3b4bfc46f1c6fdc2792e7d7ebc87bb", | ||||
|                 "sha256:3dfce70c4e425607ae87b8eae67c9c7dbba59a33b62d70f79417aef0bc5c735b", | ||||
|                 "sha256:418f51c61eab52d9920f4ef468d22c89dab1be5ac796f71cf3802f6a6e667df0", | ||||
|                 "sha256:4195604f75cdc1db9bccdb9e44d783add3c817319c30aaff011670c9ed167690", | ||||
|                 "sha256:4344ab16faf6c2d9df2b6772995623698fb2d5f114dace4ab2ff335550cf71d5", | ||||
|                 "sha256:541cd3e3e252fb19a7b48f420b798b53483302b7fe4d9954c947605d0a263d62", | ||||
|                 "sha256:564063e3782474c92cbb333effd06e6eb718471783c6e67f28c63f0fc3ac7b23", | ||||
|                 "sha256:72f44b5be46faef2a1bf2a85902511b31f4dd7b01ce0c3978e92edb2cc812a82", | ||||
|                 "sha256:8a98e02cbf8f624add45deff444539bf26345b479fc04fa0937b23cd84078d91", | ||||
|                 "sha256:940db96449d7b2ebb2c7bf190be1514f3d67914bd37e54e8d30a182bd375a1a9", | ||||
|                 "sha256:961333e7ee896651f02d4692242aa36b787b8e8e0baa2256717b2b9d55ae0a3c", | ||||
|                 "sha256:9f713ffb4e27b5575bd917c70bbc3f7b348241a351015dbbc514c01b7061ff7e", | ||||
|                 "sha256:a6584ae58001d17bb4dc0faa8a426919c2c028ef4d90ceb4191802ca6edb8204", | ||||
|                 "sha256:c2b680987f418858e89dbb4f09c8c919ece62811780a27051ace72b2f69fb1be", | ||||
|                 "sha256:d8fae5ba3d34c868ae43614e0bd6fb61114b2687ac3255798791ce075d95aece", | ||||
|                 "sha256:dbd2c361db939a4252589baa94da4404d45e3fc70da1a31e541644cdf354336e", | ||||
|                 "sha256:e090a8609e2095aa86978559b140cf8968af99ee54b8791b29ff804838f29f10", | ||||
|                 "sha256:e4a1245e7b846e88ba63e7543483bda61b9acbaee61eadbead5a1ce479d94740", | ||||
|                 "sha256:ec9901d19cadb80d9235ee41cc58983f18660314a0eb3fc7b11b0522ac3b6c4a", | ||||
|                 "sha256:f2abeb4c4ce7584912f4d637b2c57f23720d35dd2892bfeb1b2c84b6fb7a8c88", | ||||
|                 "sha256:f3bb267df679f70a9f40f17d62d22fe12e8b75e490f41807e7560de4d3e6bf9f", | ||||
|                 "sha256:f933ecf4cb736c7af60a6a533db2bf569717f2318b265f92907acff1db43bc34", | ||||
|                 "sha256:fc9c55dc1ed57db76595f2d19a479fc1c3a1be2c9da8de798a93d286c5f65f38" | ||||
|             ], | ||||
|             "version": "==3.9.9" | ||||
|             "version": "==3.10.1" | ||||
|         }, | ||||
|         "pyhamcrest": { | ||||
|             "hashes": [ | ||||
| @ -952,14 +1037,6 @@ | ||||
|             "index": "pypi", | ||||
|             "version": "==5.4.1" | ||||
|         }, | ||||
|         "qrcode": { | ||||
|             "hashes": [ | ||||
|                 "sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5", | ||||
|                 "sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==6.1" | ||||
|         }, | ||||
|         "redis": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2", | ||||
| @ -984,11 +1061,11 @@ | ||||
|         }, | ||||
|         "rsa": { | ||||
|             "hashes": [ | ||||
|                 "sha256:69805d6b69f56eb05b62daea3a7dbd7aa44324ad1306445e05da8060232d00f4", | ||||
|                 "sha256:a8774e55b59fd9fc893b0d05e9bfc6f47081f46ff5b46f39ccf24631b7be356b" | ||||
|                 "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2", | ||||
|                 "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==4.7" | ||||
|             "version": "==4.7.2" | ||||
|         }, | ||||
|         "ruamel.yaml": { | ||||
|             "hashes": [ | ||||
| @ -1006,11 +1083,11 @@ | ||||
|         }, | ||||
|         "sentry-sdk": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0a711ec952441c2ec89b8f5d226c33bc697914f46e876b44a4edd3e7864cf4d0", | ||||
|                 "sha256:737a094e49a529dd0fdcaafa9e97cf7c3d5eb964bd229821d640bc77f3502b3f" | ||||
|                 "sha256:4ae8d1ced6c67f1c8ea51d82a16721c166c489b76876c9f2c202b8a50334b237", | ||||
|                 "sha256:e75c8c58932bda8cd293ea8e4b242527129e1caaec91433d21b8b2f20fee030b" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==0.19.5" | ||||
|             "version": "==0.20.3" | ||||
|         }, | ||||
|         "service-identity": { | ||||
|             "hashes": [ | ||||
| @ -1036,11 +1113,11 @@ | ||||
|         }, | ||||
|         "structlog": { | ||||
|             "hashes": [ | ||||
|                 "sha256:33dd6bd5f49355e52c1c61bb6a4f20d0b48ce0328cc4a45fe872d38b97a05ccd", | ||||
|                 "sha256:af79dfa547d104af8d60f86eac12fb54825f54a46bc998e4504ef66177103174" | ||||
|                 "sha256:62f06fc0ee32fb8580f0715eea66cb87271eb7efb0eaf9af6b639cba8981de47", | ||||
|                 "sha256:d9d2d890532e8db83c6977a2a676fb1889922ff0c26ad4dc0ecac26f9fafbc57" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==20.2.0" | ||||
|             "version": "==21.1.0" | ||||
|         }, | ||||
|         "swagger-spec-validator": { | ||||
|             "hashes": [ | ||||
| @ -1079,14 +1156,23 @@ | ||||
|                 "sha256:f058bd0168271de4dcdc39845b52dd0a4a2fecf5f1246335f13f5e96eaebb467", | ||||
|                 "sha256:f3c19e5bd42bbe4bf345704ad7c326c74d3fd7a1b3844987853bef180be638d4" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==20.3.0" | ||||
|         }, | ||||
|         "txaio": { | ||||
|             "hashes": [ | ||||
|                 "sha256:1488d31d564a116538cc1265ac3f7979fb6223bb5a9e9f1479436ee2c17d8549", | ||||
|                 "sha256:a8676d6c68aea1f0e2548c4afdb8e6253873af3bc2659bb5bcd9f39dff7ff90f" | ||||
|                 "sha256:7d6f89745680233f1c4db9ddb748df5e88d2a7a37962be174c0fd04c8dba1dc8", | ||||
|                 "sha256:c16b55f9a67b2419cfdf8846576e2ec9ba94fe6978a83080c352a80db31c93fb" | ||||
|             ], | ||||
|             "version": "==20.12.1" | ||||
|             "version": "==21.2.1" | ||||
|         }, | ||||
|         "typing-extensions": { | ||||
|             "hashes": [ | ||||
|                 "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", | ||||
|                 "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", | ||||
|                 "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" | ||||
|             ], | ||||
|             "version": "==3.7.4.3" | ||||
|         }, | ||||
|         "uritemplate": { | ||||
|             "hashes": [ | ||||
| @ -1111,25 +1197,26 @@ | ||||
|                 "standard" | ||||
|             ], | ||||
|             "hashes": [ | ||||
|                 "sha256:1079c50a06f6338095b4f203e7861dbff318dde5f22f3a324fc6e94c7654164c", | ||||
|                 "sha256:ef1e0bb5f7941c6fe324e06443ddac0331e1632a776175f87891c7bd02694355" | ||||
|                 "sha256:3292251b3c7978e8e4a7868f4baf7f7f7bb7e40c759ecc125c37e99cdea34202", | ||||
|                 "sha256:7587f7b08bd1efd2b9bad809a3d333e972f1d11af8a5e52a9371ee3a5de71524" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==0.13.3" | ||||
|             "version": "==0.13.4" | ||||
|         }, | ||||
|         "uvloop": { | ||||
|             "hashes": [ | ||||
|                 "sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd", | ||||
|                 "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e", | ||||
|                 "sha256:4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09", | ||||
|                 "sha256:4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726", | ||||
|                 "sha256:afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891", | ||||
|                 "sha256:b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7", | ||||
|                 "sha256:bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5", | ||||
|                 "sha256:e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95", | ||||
|                 "sha256:f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362" | ||||
|                 "sha256:114543c84e95df1b4ff546e6e3a27521580466a30127f12172a3278172ad68bc", | ||||
|                 "sha256:19fa1d56c91341318ac5d417e7b61c56e9a41183946cc70c411341173de02c69", | ||||
|                 "sha256:2bb0624a8a70834e54dde8feed62ed63b50bad7a1265c40d6403a2ac447bce01", | ||||
|                 "sha256:42eda9f525a208fbc4f7cecd00fa15c57cc57646c76632b3ba2fe005004f051d", | ||||
|                 "sha256:44cac8575bf168601424302045234d74e3561fbdbac39b2b54cc1d1d00b70760", | ||||
|                 "sha256:6de130d0cb78985a5d080e323b86c5ecaf3af82f4890492c05981707852f983c", | ||||
|                 "sha256:7ae39b11a5f4cec1432d706c21ecc62f9e04d116883178b09671aa29c46f7a47", | ||||
|                 "sha256:90e56f17755e41b425ad19a08c41dc358fa7bf1226c0f8e54d4d02d556f7af7c", | ||||
|                 "sha256:b45218c99795803fb8bdbc9435ff7f54e3a591b44cd4c121b02fa83affb61c7c", | ||||
|                 "sha256:e5e5f855c9bf483ee6cd1eb9a179b740de80cb0ae2988e3fa22309b78e2ea0e7" | ||||
|             ], | ||||
|             "version": "==0.14.0" | ||||
|             "version": "==0.15.2" | ||||
|         }, | ||||
|         "vine": { | ||||
|             "hashes": [ | ||||
| @ -1140,10 +1227,10 @@ | ||||
|         }, | ||||
|         "watchgod": { | ||||
|             "hashes": [ | ||||
|                 "sha256:59700dab7445aa8e6067a5b94f37bae90fc367554549b1ed2e9d0f4f38a90d2a", | ||||
|                 "sha256:e9cca0ab9c63f17fc85df9fd8bd18156ff00aff04ebe5976cee473f4968c6858" | ||||
|                 "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29", | ||||
|                 "sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7" | ||||
|             ], | ||||
|             "version": "==0.6" | ||||
|             "version": "==0.7" | ||||
|         }, | ||||
|         "wcwidth": { | ||||
|             "hashes": [ | ||||
| @ -1152,12 +1239,20 @@ | ||||
|             ], | ||||
|             "version": "==0.2.5" | ||||
|         }, | ||||
|         "webauthn": { | ||||
|             "hashes": [ | ||||
|                 "sha256:238391b2e2cc60fb51a2cd2d2d6be149920b9af6184651353d9f95856617a9e7", | ||||
|                 "sha256:8ad9072ff1d6169f3be30d4dc8733ea563dd266962397bc58b40f674a6af74ac" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==0.4.7" | ||||
|         }, | ||||
|         "websocket-client": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549", | ||||
|                 "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010" | ||||
|                 "sha256:44b5df8f08c74c3d82d28100fdc81f4536809ce98a17f0757557813275fbb663", | ||||
|                 "sha256:63509b41d158ae5b7f67eb4ad20fecbb4eee99434e73e140354dc3ff8e09716f" | ||||
|             ], | ||||
|             "version": "==0.57.0" | ||||
|             "version": "==0.58.0" | ||||
|         }, | ||||
|         "websockets": { | ||||
|             "hashes": [ | ||||
| @ -1205,6 +1300,48 @@ | ||||
|             "index": "pypi", | ||||
|             "version": "==1.3.9" | ||||
|         }, | ||||
|         "yarl": { | ||||
|             "hashes": [ | ||||
|                 "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e", | ||||
|                 "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434", | ||||
|                 "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366", | ||||
|                 "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3", | ||||
|                 "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec", | ||||
|                 "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959", | ||||
|                 "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e", | ||||
|                 "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c", | ||||
|                 "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6", | ||||
|                 "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a", | ||||
|                 "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6", | ||||
|                 "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424", | ||||
|                 "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e", | ||||
|                 "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f", | ||||
|                 "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50", | ||||
|                 "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2", | ||||
|                 "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc", | ||||
|                 "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4", | ||||
|                 "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970", | ||||
|                 "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10", | ||||
|                 "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0", | ||||
|                 "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406", | ||||
|                 "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896", | ||||
|                 "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643", | ||||
|                 "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721", | ||||
|                 "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478", | ||||
|                 "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724", | ||||
|                 "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e", | ||||
|                 "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8", | ||||
|                 "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96", | ||||
|                 "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25", | ||||
|                 "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76", | ||||
|                 "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2", | ||||
|                 "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2", | ||||
|                 "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c", | ||||
|                 "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a", | ||||
|                 "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71" | ||||
|             ], | ||||
|             "version": "==1.6.3" | ||||
|         }, | ||||
|         "zope.interface": { | ||||
|             "hashes": [ | ||||
|                 "sha256:05a97ba92c1c7c26f25c9f671aa1ef85ffead6cdad13770e5b689cf983adc7e1", | ||||
| @ -1271,13 +1408,6 @@ | ||||
|             ], | ||||
|             "version": "==1.4.4" | ||||
|         }, | ||||
|         "asgiref": { | ||||
|             "hashes": [ | ||||
|                 "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17", | ||||
|                 "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0" | ||||
|             ], | ||||
|             "version": "==3.3.1" | ||||
|         }, | ||||
|         "astroid": { | ||||
|             "hashes": [ | ||||
|                 "sha256:4c17cea3e592c21b6e222f673868961bad77e1f985cb1694ed077475a89229c1", | ||||
| @ -1347,74 +1477,61 @@ | ||||
|         }, | ||||
|         "coverage": { | ||||
|             "hashes": [ | ||||
|                 "sha256:03ed2a641e412e42cc35c244508cf186015c217f0e4d496bf6d7078ebe837ae7", | ||||
|                 "sha256:04b14e45d6a8e159c9767ae57ecb34563ad93440fc1b26516a89ceb5b33c1ad5", | ||||
|                 "sha256:0cdde51bfcf6b6bd862ee9be324521ec619b20590787d1655d005c3fb175005f", | ||||
|                 "sha256:0f48fc7dc82ee14aeaedb986e175a429d24129b7eada1b7e94a864e4f0644dde", | ||||
|                 "sha256:107d327071061fd4f4a2587d14c389a27e4e5c93c7cba5f1f59987181903902f", | ||||
|                 "sha256:1375bb8b88cb050a2d4e0da901001347a44302aeadb8ceb4b6e5aa373b8ea68f", | ||||
|                 "sha256:14a9f1887591684fb59fdba8feef7123a0da2424b0652e1b58dd5b9a7bb1188c", | ||||
|                 "sha256:16baa799ec09cc0dcb43a10680573269d407c159325972dd7114ee7649e56c66", | ||||
|                 "sha256:1b811662ecf72eb2d08872731636aee6559cae21862c36f74703be727b45df90", | ||||
|                 "sha256:1ccae21a076d3d5f471700f6d30eb486da1626c380b23c70ae32ab823e453337", | ||||
|                 "sha256:2f2cf7a42d4b7654c9a67b9d091ec24374f7c58794858bff632a2039cb15984d", | ||||
|                 "sha256:322549b880b2d746a7672bf6ff9ed3f895e9c9f108b714e7360292aa5c5d7cf4", | ||||
|                 "sha256:32ab83016c24c5cf3db2943286b85b0a172dae08c58d0f53875235219b676409", | ||||
|                 "sha256:3fe50f1cac369b02d34ad904dfe0771acc483f82a1b54c5e93632916ba847b37", | ||||
|                 "sha256:4a780807e80479f281d47ee4af2eb2df3e4ccf4723484f77da0bb49d027e40a1", | ||||
|                 "sha256:4a8eb7785bd23565b542b01fb39115a975fefb4a82f23d407503eee2c0106247", | ||||
|                 "sha256:5bee3970617b3d74759b2d2df2f6a327d372f9732f9ccbf03fa591b5f7581e39", | ||||
|                 "sha256:60a3307a84ec60578accd35d7f0c71a3a971430ed7eca6567399d2b50ef37b8c", | ||||
|                 "sha256:6625e52b6f346a283c3d563d1fd8bae8956daafc64bb5bbd2b8f8a07608e3994", | ||||
|                 "sha256:66a5aae8233d766a877c5ef293ec5ab9520929c2578fd2069308a98b7374ea8c", | ||||
|                 "sha256:68fb816a5dd901c6aff352ce49e2a0ffadacdf9b6fae282a69e7a16a02dad5fb", | ||||
|                 "sha256:6b588b5cf51dc0fd1c9e19f622457cc74b7d26fe295432e434525f1c0fae02bc", | ||||
|                 "sha256:6c4d7165a4e8f41eca6b990c12ee7f44fef3932fac48ca32cecb3a1b2223c21f", | ||||
|                 "sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca", | ||||
|                 "sha256:6d9c88b787638a451f41f97446a1c9fd416e669b4d9717ae4615bd29de1ac135", | ||||
|                 "sha256:755c56beeacac6a24c8e1074f89f34f4373abce8b662470d3aa719ae304931f3", | ||||
|                 "sha256:7e40d3f8eb472c1509b12ac2a7e24158ec352fc8567b77ab02c0db053927e339", | ||||
|                 "sha256:812eaf4939ef2284d29653bcfee9665f11f013724f07258928f849a2306ea9f9", | ||||
|                 "sha256:84df004223fd0550d0ea7a37882e5c889f3c6d45535c639ce9802293b39cd5c9", | ||||
|                 "sha256:859f0add98707b182b4867359e12bde806b82483fb12a9ae868a77880fc3b7af", | ||||
|                 "sha256:87c4b38288f71acd2106f5d94f575bc2136ea2887fdb5dfe18003c881fa6b370", | ||||
|                 "sha256:89fc12c6371bf963809abc46cced4a01ca4f99cba17be5e7d416ed7ef1245d19", | ||||
|                 "sha256:9564ac7eb1652c3701ac691ca72934dd3009997c81266807aef924012df2f4b3", | ||||
|                 "sha256:9754a5c265f991317de2bac0c70a746efc2b695cf4d49f5d2cddeac36544fb44", | ||||
|                 "sha256:a565f48c4aae72d1d3d3f8e8fb7218f5609c964e9c6f68604608e5958b9c60c3", | ||||
|                 "sha256:a636160680c6e526b84f85d304e2f0bb4e94f8284dd765a1911de9a40450b10a", | ||||
|                 "sha256:a839e25f07e428a87d17d857d9935dd743130e77ff46524abb992b962eb2076c", | ||||
|                 "sha256:b62046592b44263fa7570f1117d372ae3f310222af1fc1407416f037fb3af21b", | ||||
|                 "sha256:b7f7421841f8db443855d2854e25914a79a1ff48ae92f70d0a5c2f8907ab98c9", | ||||
|                 "sha256:ba7ca81b6d60a9f7a0b4b4e175dcc38e8fef4992673d9d6e6879fd6de00dd9b8", | ||||
|                 "sha256:bb32ca14b4d04e172c541c69eec5f385f9a075b38fb22d765d8b0ce3af3a0c22", | ||||
|                 "sha256:c0ff1c1b4d13e2240821ef23c1efb1f009207cb3f56e16986f713c2b0e7cd37f", | ||||
|                 "sha256:c669b440ce46ae3abe9b2d44a913b5fd86bb19eb14a8701e88e3918902ecd345", | ||||
|                 "sha256:c67734cff78383a1f23ceba3b3239c7deefc62ac2b05fa6a47bcd565771e5880", | ||||
|                 "sha256:c6809ebcbf6c1049002b9ac09c127ae43929042ec1f1dbd8bb1615f7cd9f70a0", | ||||
|                 "sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b", | ||||
|                 "sha256:ebfa374067af240d079ef97b8064478f3bf71038b78b017eb6ec93ede1b6bcec", | ||||
|                 "sha256:fbb17c0d0822684b7d6c09915677a32319f16ff1115df5ec05bdcaaee40b35f3", | ||||
|                 "sha256:fff1f3a586246110f34dc762098b5afd2de88de507559e63553d7da643053786" | ||||
|                 "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c", | ||||
|                 "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6", | ||||
|                 "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45", | ||||
|                 "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a", | ||||
|                 "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03", | ||||
|                 "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529", | ||||
|                 "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a", | ||||
|                 "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a", | ||||
|                 "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2", | ||||
|                 "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6", | ||||
|                 "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759", | ||||
|                 "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53", | ||||
|                 "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a", | ||||
|                 "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4", | ||||
|                 "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff", | ||||
|                 "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502", | ||||
|                 "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793", | ||||
|                 "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb", | ||||
|                 "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905", | ||||
|                 "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821", | ||||
|                 "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b", | ||||
|                 "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81", | ||||
|                 "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0", | ||||
|                 "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b", | ||||
|                 "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3", | ||||
|                 "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184", | ||||
|                 "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701", | ||||
|                 "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a", | ||||
|                 "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82", | ||||
|                 "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638", | ||||
|                 "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5", | ||||
|                 "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083", | ||||
|                 "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6", | ||||
|                 "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90", | ||||
|                 "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465", | ||||
|                 "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a", | ||||
|                 "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3", | ||||
|                 "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e", | ||||
|                 "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066", | ||||
|                 "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf", | ||||
|                 "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b", | ||||
|                 "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae", | ||||
|                 "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669", | ||||
|                 "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873", | ||||
|                 "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b", | ||||
|                 "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6", | ||||
|                 "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb", | ||||
|                 "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160", | ||||
|                 "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c", | ||||
|                 "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079", | ||||
|                 "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d", | ||||
|                 "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==5.4" | ||||
|         }, | ||||
|         "django": { | ||||
|             "hashes": [ | ||||
|                 "sha256:169e2e7b4839a7910b393eec127fd7cbae62e80fa55f89c6510426abf673fe5f", | ||||
|                 "sha256:c6c0462b8b361f8691171af1fb87eceb4442da28477e12200c40420176206ba7" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==3.1.6" | ||||
|         }, | ||||
|         "django-debug-toolbar": { | ||||
|             "hashes": [ | ||||
|                 "sha256:84e2607d900dbd571df0a2acf380b47c088efb787dce9805aefeb407341961d2", | ||||
|                 "sha256:9e5a25d0c965f7e686f6a8ba23613ca9ca30184daa26487706d4829f5cfb697a" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==3.2" | ||||
|             "version": "==5.5" | ||||
|         }, | ||||
|         "dodgy": { | ||||
|             "hashes": [ | ||||
| @ -1446,10 +1563,10 @@ | ||||
|         }, | ||||
|         "gitpython": { | ||||
|             "hashes": [ | ||||
|                 "sha256:42dbefd8d9e2576c496ed0059f3103dcef7125b9ce16f9d5f9c834aed44a1dac", | ||||
|                 "sha256:867ec3dfb126aac0f8296b19fb63b8c4a399f32b4b6fafe84c4b10af5fa9f7b5" | ||||
|                 "sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b", | ||||
|                 "sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61" | ||||
|             ], | ||||
|             "version": "==3.1.12" | ||||
|             "version": "==3.1.14" | ||||
|         }, | ||||
|         "iniconfig": { | ||||
|             "hashes": [ | ||||
| @ -1634,13 +1751,6 @@ | ||||
|             "index": "pypi", | ||||
|             "version": "==4.1.0" | ||||
|         }, | ||||
|         "pytz": { | ||||
|             "hashes": [ | ||||
|                 "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", | ||||
|                 "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" | ||||
|             ], | ||||
|             "version": "==2021.1" | ||||
|         }, | ||||
|         "pyyaml": { | ||||
|             "hashes": [ | ||||
|                 "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", | ||||
| @ -1755,13 +1865,6 @@ | ||||
|             ], | ||||
|             "version": "==2.1.0" | ||||
|         }, | ||||
|         "sqlparse": { | ||||
|             "hashes": [ | ||||
|                 "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", | ||||
|                 "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" | ||||
|             ], | ||||
|             "version": "==0.4.1" | ||||
|         }, | ||||
|         "stevedore": { | ||||
|             "hashes": [ | ||||
|                 "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee", | ||||
|  | ||||
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							| @ -1,7 +1,10 @@ | ||||
| <img src="https://goauthentik.io/img/icon_top_brand_colour.svg" height="250" alt="authentik logo"> | ||||
| <p align="center"> | ||||
|     <img src="https://goauthentik.io/img/icon_top_brand_colour.svg" height="150" alt="authentik logo"> | ||||
| </p> | ||||
|  | ||||
| --- | ||||
|  | ||||
| [](https://discord.gg/KPnmtNWy) | ||||
| [](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1) | ||||
| [](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=1) | ||||
| [](https://codecov.io/gh/BeryJu/authentik) | ||||
| @ -21,8 +24,10 @@ For bigger setups, there is a Helm Chart in the `helm/` directory. This is docum | ||||
|  | ||||
| ## Screenshots | ||||
|  | ||||
|  | ||||
|  | ||||
| Light | Dark | ||||
| --- | --- | ||||
|  |  | ||||
|  |  | ||||
|  | ||||
| ## Development | ||||
|  | ||||
|  | ||||
| @ -4,9 +4,9 @@ | ||||
|  | ||||
| | Version    | Supported          | | ||||
| | ---------- | ------------------ | | ||||
| | 0.13.x     | :white_check_mark: | | ||||
| | 0.14.x     | :white_check_mark: | | ||||
| | 2021.1.x   | :white_check_mark: | | ||||
| | 2021.2.x   | :white_check_mark: | | ||||
| | 2021.3.x   | :white_check_mark: | | ||||
|  | ||||
| ## Reporting a Vulnerability | ||||
|  | ||||
|  | ||||
| @ -1,2 +1,2 @@ | ||||
| """authentik""" | ||||
| __version__ = "2021.2.1-rc2" | ||||
| __version__ = "2021.3.1" | ||||
|  | ||||
| @ -2,7 +2,6 @@ | ||||
| import time | ||||
| from collections import Counter | ||||
| from datetime import timedelta | ||||
| from typing import Dict, List | ||||
|  | ||||
| from django.db.models import Count, ExpressionWrapper, F, Model | ||||
| from django.db.models.fields import DurationField | ||||
| @ -19,7 +18,7 @@ from rest_framework.viewsets import ViewSet | ||||
| 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 = ( | ||||
| @ -32,7 +31,7 @@ def get_events_per_1h(**filter_kwargs) -> List[Dict[str, int]]: | ||||
|         .annotate(count=Count("pk")) | ||||
|         .order_by("age_hours") | ||||
|     ) | ||||
|     data = Counter({d["age_hours"]: d["count"] for d in result}) | ||||
|     data = Counter({int(d["age_hours"]): d["count"] for d in result}) | ||||
|     results = [] | ||||
|     _now = now() | ||||
|     for hour in range(0, -24, -1): | ||||
|  | ||||
| @ -7,14 +7,14 @@ from django.http.response import Http404 | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from drf_yasg2.utils import swagger_auto_schema | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.fields import CharField, DateTimeField, IntegerField, ListField | ||||
| from rest_framework.fields import CharField, ChoiceField, DateTimeField, 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.events.monitored_tasks import TaskInfo | ||||
| from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus | ||||
|  | ||||
|  | ||||
| class TaskSerializer(Serializer): | ||||
| @ -24,7 +24,10 @@ class TaskSerializer(Serializer): | ||||
|     task_description = CharField() | ||||
|     task_finish_timestamp = DateTimeField(source="finish_timestamp") | ||||
|  | ||||
|     status = IntegerField(source="result.status.value") | ||||
|     status = ChoiceField( | ||||
|         source="result.status.name", | ||||
|         choices=[(x.name, x.name) for x in TaskResultStatus], | ||||
|     ) | ||||
|     messages = ListField(source="result.messages") | ||||
|  | ||||
|     def create(self, validated_data: dict) -> Model: | ||||
|  | ||||
| @ -55,7 +55,7 @@ class VersionViewSet(ListModelMixin, GenericViewSet): | ||||
|     def get_queryset(self):  # pragma: no cover | ||||
|         return None | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: VersionSerializer(many=True)}) | ||||
|     @swagger_auto_schema(responses={200: VersionSerializer(many=False)}) | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """Get running and latest version.""" | ||||
|         return Response(VersionSerializer(True).data) | ||||
|  | ||||
| @ -1,19 +0,0 @@ | ||||
| """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", | ||||
| ] | ||||
| @ -1,122 +0,0 @@ | ||||
| {% 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> | ||||
|                     <ak-modal-button href="{% url 'authentik_admin:certificatekeypair-generate' %}"> | ||||
|                         <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||
|                             {% trans 'Generate' %} | ||||
|                         </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 %} | ||||
| @ -1,135 +0,0 @@ | ||||
| {% 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 %} | ||||
| @ -1,114 +0,0 @@ | ||||
| {% 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 %} | ||||
| @ -1,149 +0,0 @@ | ||||
| {% 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 %} | ||||
| @ -1,154 +0,0 @@ | ||||
| {% 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 %} | ||||
| @ -1,148 +0,0 @@ | ||||
| {% 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 %} | ||||
| @ -1,119 +0,0 @@ | ||||
| {% 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 %} | ||||
| @ -1,139 +0,0 @@ | ||||
| {% 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 %} | ||||
| @ -1,28 +0,0 @@ | ||||
| {% extends 'generic/form.html' %} | ||||
|  | ||||
| {% load i18n %} | ||||
|  | ||||
| {% block above_form %} | ||||
| <h1>{% blocktrans with property_mapping=property_mapping %}Test {{ property_mapping }}{% endblocktrans %}</h1> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block beneath_form %} | ||||
| {% if result %} | ||||
| <div class="pf-c-form__group "> | ||||
|     <div class="pf-c-form__group-label"> | ||||
|         <label class="pf-c-form__label" for="context-1"> | ||||
|             <span class="pf-c-form__label-text">{% trans 'Result' %}</span> | ||||
|         </label> | ||||
|     </div> | ||||
|     <div class="pf-c-form__group-control"> | ||||
|         <div class="c-form__horizontal-group"> | ||||
|             <ak-codemirror mode="javascript"><textarea class="pf-c-form-control">{{ result }}</textarea></ak-codemirror> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| {% endif %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block action %} | ||||
| {% trans 'Test' %} | ||||
| {% endblock %} | ||||
| @ -1,153 +0,0 @@ | ||||
| {% 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 %} | ||||
| @ -1,148 +0,0 @@ | ||||
| {% 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 %} | ||||
| @ -1,125 +0,0 @@ | ||||
| {% 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 %} | ||||
| @ -1,109 +0,0 @@ | ||||
| {% 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 %} | ||||
| @ -1,130 +0,0 @@ | ||||
| {% 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 %} | ||||
| @ -1,84 +0,0 @@ | ||||
| {% 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"> | ||||
|                         <span>{{ task.html_name|join:"_­" }}</span> | ||||
|                     </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 %} | ||||
| @ -1,102 +0,0 @@ | ||||
| {% 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 %} | ||||
| @ -1,125 +0,0 @@ | ||||
| {% 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 %} | ||||
| @ -27,7 +27,9 @@ | ||||
|     </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 %}" /> | ||||
|     <ak-spinner-button form="main-form"> | ||||
|         {% block action %}{% endblock %} | ||||
|     </ak-spinner-button>  | ||||
|     <a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Cancel" %}</a> | ||||
| </footer> | ||||
| {% endblock %} | ||||
|  | ||||
| @ -1,62 +0,0 @@ | ||||
| """authentik admin templatetags""" | ||||
| from django import template | ||||
| from django.db.models import Model | ||||
| from django.utils.html import mark_safe | ||||
| from structlog.stdlib 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 | ||||
|  | ||||
| @ -3,8 +3,8 @@ 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 import reverse | ||||
| from django.urls.exceptions import NoReverseMatch | ||||
|  | ||||
| from authentik.admin.urls import urlpatterns | ||||
|  | ||||
| @ -20,7 +20,6 @@ from authentik.admin.views import ( | ||||
|     stages_bindings, | ||||
|     stages_invitations, | ||||
|     stages_prompts, | ||||
|     tasks, | ||||
|     tokens, | ||||
|     users, | ||||
| ) | ||||
| @ -54,14 +53,12 @@ urlpatterns = [ | ||||
|         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/", | ||||
| @ -74,7 +71,6 @@ urlpatterns = [ | ||||
|         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/", | ||||
| @ -92,11 +88,6 @@ urlpatterns = [ | ||||
|         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(), | ||||
| @ -134,7 +125,6 @@ urlpatterns = [ | ||||
|         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/", | ||||
| @ -147,11 +137,6 @@ urlpatterns = [ | ||||
|         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(), | ||||
| @ -169,31 +154,21 @@ urlpatterns = [ | ||||
|     ), | ||||
|     # Stage Prompts | ||||
|     path( | ||||
|         "stages/prompts/", | ||||
|         stages_prompts.PromptListView.as_view(), | ||||
|         name="stage-prompts", | ||||
|     ), | ||||
|     path( | ||||
|         "stages/prompts/create/", | ||||
|         "stages_prompts/create/", | ||||
|         stages_prompts.PromptCreateView.as_view(), | ||||
|         name="stage-prompt-create", | ||||
|     ), | ||||
|     path( | ||||
|         "stages/prompts/<uuid:pk>/update/", | ||||
|         "stages_prompts/<uuid:pk>/update/", | ||||
|         stages_prompts.PromptUpdateView.as_view(), | ||||
|         name="stage-prompt-update", | ||||
|     ), | ||||
|     path( | ||||
|         "stages/prompts/<uuid:pk>/delete/", | ||||
|         "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(), | ||||
| @ -205,7 +180,6 @@ urlpatterns = [ | ||||
|         name="stage-invitation-delete", | ||||
|     ), | ||||
|     # Flows | ||||
|     path("flows/", flows.FlowListView.as_view(), name="flows"), | ||||
|     path( | ||||
|         "flows/create/", | ||||
|         flows.FlowCreateView.as_view(), | ||||
| @ -258,7 +232,6 @@ urlpatterns = [ | ||||
|         name="property-mapping-test", | ||||
|     ), | ||||
|     # 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"), | ||||
| @ -272,7 +245,6 @@ urlpatterns = [ | ||||
|         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/", | ||||
| @ -285,11 +257,6 @@ urlpatterns = [ | ||||
|         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(), | ||||
| @ -311,11 +278,6 @@ urlpatterns = [ | ||||
|         name="certificatekeypair-delete", | ||||
|     ), | ||||
|     # Outposts | ||||
|     path( | ||||
|         "outposts/", | ||||
|         outposts.OutpostListView.as_view(), | ||||
|         name="outposts", | ||||
|     ), | ||||
|     path( | ||||
|         "outposts/create/", | ||||
|         outposts.OutpostCreateView.as_view(), | ||||
| @ -332,11 +294,6 @@ urlpatterns = [ | ||||
|         name="outpost-delete", | ||||
|     ), | ||||
|     # Outpost Service Connections | ||||
|     path( | ||||
|         "outpost_service_connections/", | ||||
|         outposts_service_connections.OutpostServiceConnectionListView.as_view(), | ||||
|         name="outpost-service-connections", | ||||
|     ), | ||||
|     path( | ||||
|         "outpost_service_connections/create/", | ||||
|         outposts_service_connections.OutpostServiceConnectionCreateView.as_view(), | ||||
| @ -352,12 +309,6 @@ urlpatterns = [ | ||||
|         outposts_service_connections.OutpostServiceConnectionDeleteView.as_view(), | ||||
|         name="outpost-service-connection-delete", | ||||
|     ), | ||||
|     # Tasks | ||||
|     path( | ||||
|         "tasks/", | ||||
|         tasks.TaskListView.as_view(), | ||||
|         name="tasks", | ||||
|     ), | ||||
|     # Event Notification Transpots | ||||
|     path( | ||||
|         "events/transports/create/", | ||||
|  | ||||
| @ -1,15 +1,17 @@ | ||||
| """authentik Application 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.urls import reverse_lazy | ||||
| from django.utils.translation import gettext as _ | ||||
| from django.views.generic import UpdateView | ||||
| from guardian.mixins import PermissionRequiredMixin | ||||
| from guardian.shortcuts import get_objects_for_user | ||||
|  | ||||
| from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView | ||||
| from authentik.admin.views.utils import DeleteMessageView | ||||
| from authentik.core.forms.applications import ApplicationForm | ||||
| from authentik.core.models import Application | ||||
| from authentik.lib.views import CreateAssignPermView | ||||
| @ -17,7 +19,6 @@ from authentik.lib.views import CreateAssignPermView | ||||
|  | ||||
| class ApplicationCreateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     DjangoPermissionRequiredMixin, | ||||
|     CreateAssignPermView, | ||||
| @ -28,14 +29,29 @@ class ApplicationCreateView( | ||||
|     form_class = ApplicationForm | ||||
|     permission_required = "authentik_core.add_application" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created Application") | ||||
|  | ||||
|     def get_initial(self) -> dict[str, Any]: | ||||
|         if "provider" in self.request.GET: | ||||
|             try: | ||||
|                 initial_provider_pk = int(self.request.GET["provider"]) | ||||
|             except ValueError: | ||||
|                 return super().get_initial() | ||||
|             providers = ( | ||||
|                 get_objects_for_user(self.request.user, "authentik_core.view_provider") | ||||
|                 .filter(pk=initial_provider_pk) | ||||
|                 .select_subclasses() | ||||
|             ) | ||||
|             if not providers.exists(): | ||||
|                 return {} | ||||
|             return {"provider": providers.first()} | ||||
|         return super().get_initial() | ||||
|  | ||||
|  | ||||
| class ApplicationUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     UpdateView, | ||||
| @ -46,8 +62,8 @@ class ApplicationUpdateView( | ||||
|     form_class = ApplicationForm | ||||
|     permission_required = "authentik_core.change_application" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully updated Application") | ||||
|  | ||||
|  | ||||
| @ -59,6 +75,6 @@ class ApplicationDeleteView( | ||||
|     model = Application | ||||
|     permission_required = "authentik_core.delete_application" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted Application") | ||||
|  | ||||
| @ -7,16 +7,11 @@ from django.contrib.messages.views import SuccessMessageMixin | ||||
| from django.http.response import HttpResponse | ||||
| from django.urls import reverse_lazy | ||||
| from django.utils.translation import gettext as _ | ||||
| from django.views.generic import ListView, UpdateView | ||||
| from django.views.generic import UpdateView | ||||
| from django.views.generic.edit import FormView | ||||
| from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||
| from guardian.mixins import PermissionRequiredMixin | ||||
|  | ||||
| from authentik.admin.views.utils import ( | ||||
|     BackSuccessUrlMixin, | ||||
|     DeleteMessageView, | ||||
|     SearchListMixin, | ||||
|     UserPaginateListMixin, | ||||
| ) | ||||
| from authentik.admin.views.utils import DeleteMessageView | ||||
| from authentik.crypto.builder import CertificateBuilder | ||||
| from authentik.crypto.forms import ( | ||||
|     CertificateKeyPairForm, | ||||
| @ -26,26 +21,8 @@ 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, | ||||
| @ -57,13 +34,12 @@ class CertificateKeyPairCreateView( | ||||
|     permission_required = "authentik_crypto.add_certificatekeypair" | ||||
|  | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_admin:certificate_key_pair") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created Certificate-Key Pair") | ||||
|  | ||||
|  | ||||
| class CertificateKeyPairGenerateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     DjangoPermissionRequiredMixin, | ||||
|     FormView, | ||||
| @ -75,7 +51,7 @@ class CertificateKeyPairGenerateView( | ||||
|     permission_required = "authentik_crypto.add_certificatekeypair" | ||||
|  | ||||
|     template_name = "administration/certificatekeypair/generate.html" | ||||
|     success_url = reverse_lazy("authentik_admin:certificate_key_pair") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully generated Certificate-Key Pair") | ||||
|  | ||||
|     def form_valid(self, form: CertificateKeyPairGenerateForm) -> HttpResponse: | ||||
| @ -91,7 +67,6 @@ class CertificateKeyPairGenerateView( | ||||
|  | ||||
| class CertificateKeyPairUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     UpdateView, | ||||
| @ -103,7 +78,7 @@ class CertificateKeyPairUpdateView( | ||||
|     permission_required = "authentik_crypto.change_certificatekeypair" | ||||
|  | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_admin:certificate_key_pair") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully updated Certificate-Key Pair") | ||||
|  | ||||
|  | ||||
| @ -116,5 +91,5 @@ class CertificateKeyPairDeleteView( | ||||
|     permission_required = "authentik_crypto.delete_certificatekeypair" | ||||
|  | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_admin:certificate_key_pair") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted Certificate-Key Pair") | ||||
|  | ||||
| @ -4,12 +4,11 @@ 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.admin.views.utils import DeleteMessageView | ||||
| from authentik.events.forms import NotificationRuleForm | ||||
| from authentik.events.models import NotificationRule | ||||
| from authentik.lib.views import CreateAssignPermView | ||||
| @ -17,7 +16,6 @@ from authentik.lib.views import CreateAssignPermView | ||||
|  | ||||
| class NotificationRuleCreateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     DjangoPermissionRequiredMixin, | ||||
|     CreateAssignPermView, | ||||
| @ -28,14 +26,13 @@ class NotificationRuleCreateView( | ||||
|     form_class = NotificationRuleForm | ||||
|     permission_required = "authentik_events.add_NotificationRule" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created Notification Rule") | ||||
|  | ||||
|  | ||||
| class NotificationRuleUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     UpdateView, | ||||
| @ -46,8 +43,8 @@ class NotificationRuleUpdateView( | ||||
|     form_class = NotificationRuleForm | ||||
|     permission_required = "authentik_events.change_NotificationRule" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully updated Notification Rule") | ||||
|  | ||||
|  | ||||
| @ -59,6 +56,6 @@ class NotificationRuleDeleteView( | ||||
|     model = NotificationRule | ||||
|     permission_required = "authentik_events.delete_NotificationRule" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted Notification Rule") | ||||
|  | ||||
| @ -4,12 +4,11 @@ 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.admin.views.utils import DeleteMessageView | ||||
| from authentik.events.forms import NotificationTransportForm | ||||
| from authentik.events.models import NotificationTransport | ||||
| from authentik.lib.views import CreateAssignPermView | ||||
| @ -17,7 +16,6 @@ from authentik.lib.views import CreateAssignPermView | ||||
|  | ||||
| class NotificationTransportCreateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     DjangoPermissionRequiredMixin, | ||||
|     CreateAssignPermView, | ||||
| @ -27,15 +25,13 @@ class NotificationTransportCreateView( | ||||
|     model = NotificationTransport | ||||
|     form_class = NotificationTransportForm | ||||
|     permission_required = "authentik_events.add_notificationtransport" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created Notification Transport") | ||||
|  | ||||
|  | ||||
| class NotificationTransportUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     UpdateView, | ||||
| @ -45,9 +41,8 @@ class NotificationTransportUpdateView( | ||||
|     model = NotificationTransport | ||||
|     form_class = NotificationTransportForm | ||||
|     permission_required = "authentik_events.change_notificationtransport" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully updated Notification Transport") | ||||
|  | ||||
|  | ||||
| @ -58,7 +53,6 @@ class NotificationTransportDeleteView( | ||||
|  | ||||
|     model = NotificationTransport | ||||
|     permission_required = "authentik_events.delete_notificationtransport" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted Notification Transport") | ||||
|  | ||||
| @ -8,15 +8,10 @@ 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 django.views.generic import DetailView, FormView, UpdateView | ||||
| from guardian.mixins import PermissionRequiredMixin | ||||
|  | ||||
| from authentik.admin.views.utils import ( | ||||
|     BackSuccessUrlMixin, | ||||
|     DeleteMessageView, | ||||
|     SearchListMixin, | ||||
|     UserPaginateListMixin, | ||||
| ) | ||||
| from authentik.admin.views.utils import DeleteMessageView | ||||
| from authentik.flows.exceptions import FlowNonApplicableException | ||||
| from authentik.flows.forms import FlowForm, FlowImportForm | ||||
| from authentik.flows.models import Flow | ||||
| @ -29,25 +24,8 @@ from authentik.lib.utils.urls import redirect_with_qs | ||||
| from authentik.lib.views import CreateAssignPermView, bad_request_message | ||||
|  | ||||
|  | ||||
| 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, | ||||
| @ -59,13 +37,12 @@ class FlowCreateView( | ||||
|     permission_required = "authentik_flows.add_flow" | ||||
|  | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_admin:flows") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created Flow") | ||||
|  | ||||
|  | ||||
| class FlowUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     UpdateView, | ||||
| @ -77,7 +54,7 @@ class FlowUpdateView( | ||||
|     permission_required = "authentik_flows.change_flow" | ||||
|  | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_admin:flows") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully updated Flow") | ||||
|  | ||||
|  | ||||
| @ -88,7 +65,7 @@ class FlowDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageV | ||||
|     permission_required = "authentik_flows.delete_flow" | ||||
|  | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_admin:flows") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted Flow") | ||||
|  | ||||
|  | ||||
| @ -128,7 +105,7 @@ class FlowImportView(LoginRequiredMixin, FormView): | ||||
|  | ||||
|     form_class = FlowImportForm | ||||
|     template_name = "administration/flow/import.html" | ||||
|     success_url = reverse_lazy("authentik_admin:flows") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|  | ||||
|     def dispatch(self, request, *args, **kwargs): | ||||
|         if not request.user.is_superuser: | ||||
|  | ||||
| @ -6,39 +6,17 @@ from django.contrib.auth.mixins import ( | ||||
| 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 django.views.generic import UpdateView | ||||
| from guardian.mixins import PermissionRequiredMixin | ||||
|  | ||||
| from authentik.admin.views.utils import ( | ||||
|     BackSuccessUrlMixin, | ||||
|     DeleteMessageView, | ||||
|     SearchListMixin, | ||||
|     UserPaginateListMixin, | ||||
| ) | ||||
| from authentik.admin.views.utils import DeleteMessageView | ||||
| 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, | ||||
| @ -50,13 +28,12 @@ class GroupCreateView( | ||||
|     permission_required = "authentik_core.add_group" | ||||
|  | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_admin:groups") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created Group") | ||||
|  | ||||
|  | ||||
| class GroupUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     UpdateView, | ||||
| @ -68,7 +45,7 @@ class GroupUpdateView( | ||||
|     permission_required = "authentik_core.change_group" | ||||
|  | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_admin:groups") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully updated Group") | ||||
|  | ||||
|  | ||||
| @ -79,5 +56,5 @@ class GroupDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage | ||||
|     permission_required = "authentik_flows.delete_group" | ||||
|  | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_admin:groups") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted Group") | ||||
|  | ||||
| @ -1,47 +1,24 @@ | ||||
| """authentik Outpost administration""" | ||||
| from dataclasses import asdict | ||||
| from typing import Any, Dict | ||||
| 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.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 django.views.generic import UpdateView | ||||
| from guardian.mixins import PermissionRequiredMixin | ||||
|  | ||||
| from authentik.admin.views.utils import ( | ||||
|     BackSuccessUrlMixin, | ||||
|     DeleteMessageView, | ||||
|     SearchListMixin, | ||||
|     UserPaginateListMixin, | ||||
| ) | ||||
| from authentik.admin.views.utils import DeleteMessageView | ||||
| 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, | ||||
| @ -51,12 +28,11 @@ class OutpostCreateView( | ||||
|     model = Outpost | ||||
|     form_class = OutpostForm | ||||
|     permission_required = "authentik_outposts.add_outpost" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_admin:outposts") | ||||
|     success_message = _("Successfully created Outpost") | ||||
|  | ||||
|     def get_initial(self) -> Dict[str, Any]: | ||||
|     def get_initial(self) -> dict[str, Any]: | ||||
|         return { | ||||
|             "_config": asdict( | ||||
|                 OutpostConfig(authentik_host=self.request.build_absolute_uri("/")) | ||||
| @ -66,7 +42,6 @@ class OutpostCreateView( | ||||
|  | ||||
| class OutpostUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     UpdateView, | ||||
| @ -76,9 +51,8 @@ class OutpostUpdateView( | ||||
|     model = Outpost | ||||
|     form_class = OutpostForm | ||||
|     permission_required = "authentik_outposts.change_outpost" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_admin:outposts") | ||||
|     success_message = _("Successfully updated Outpost") | ||||
|  | ||||
|  | ||||
| @ -87,7 +61,6 @@ class OutpostDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessa | ||||
|  | ||||
|     model = Outpost | ||||
|     permission_required = "authentik_outposts.delete_outpost" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_admin:outposts") | ||||
|     success_message = _("Successfully deleted Outpost") | ||||
|  | ||||
| @ -6,39 +6,18 @@ from django.contrib.auth.mixins import ( | ||||
| 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 guardian.mixins import 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, | ||||
| @ -49,13 +28,12 @@ class OutpostServiceConnectionCreateView( | ||||
|     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") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created Outpost Service Connection") | ||||
|  | ||||
|  | ||||
| class OutpostServiceConnectionUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     InheritanceUpdateView, | ||||
| @ -66,8 +44,8 @@ class OutpostServiceConnectionUpdateView( | ||||
|     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") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully updated Outpost Service Connection") | ||||
|  | ||||
|  | ||||
| class OutpostServiceConnectionDeleteView( | ||||
| @ -79,5 +57,5 @@ class OutpostServiceConnectionDeleteView( | ||||
|     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") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted Outpost Service Connection") | ||||
|  | ||||
| @ -3,7 +3,6 @@ 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.urls.base import reverse_lazy | ||||
| from django.utils.translation import gettext as _ | ||||
| from django.views.generic import FormView | ||||
| from structlog.stdlib import get_logger | ||||
| @ -19,9 +18,8 @@ class PolicyCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView): | ||||
|     """View to clear Policy cache""" | ||||
|  | ||||
|     form_class = PolicyCacheClearForm | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/form_non_model.html" | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully cleared Policy cache") | ||||
|  | ||||
|     def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: | ||||
| @ -38,9 +36,8 @@ class FlowCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView): | ||||
|     """View to clear Flow cache""" | ||||
|  | ||||
|     form_class = FlowCacheClearForm | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/form_non_model.html" | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully cleared Flow cache") | ||||
|  | ||||
|     def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| """authentik Policy administration""" | ||||
| from typing import Any, Dict | ||||
| from typing import Any | ||||
|  | ||||
| from django.contrib.auth.mixins import LoginRequiredMixin | ||||
| from django.contrib.auth.mixins import ( | ||||
| @ -11,41 +11,20 @@ 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 guardian.mixins import 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, | ||||
| @ -56,13 +35,12 @@ class PolicyCreateView( | ||||
|     permission_required = "authentik_policies.add_policy" | ||||
|  | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_admin:policies") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created Policy") | ||||
|  | ||||
|  | ||||
| class PolicyUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     InheritanceUpdateView, | ||||
| @ -73,7 +51,7 @@ class PolicyUpdateView( | ||||
|     permission_required = "authentik_policies.change_policy" | ||||
|  | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_admin:policies") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully updated Policy") | ||||
|  | ||||
|  | ||||
| @ -84,7 +62,7 @@ class PolicyDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessag | ||||
|     permission_required = "authentik_policies.delete_policy" | ||||
|  | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_admin:policies") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted Policy") | ||||
|  | ||||
|  | ||||
| @ -102,7 +80,7 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo | ||||
|             Policy.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first() | ||||
|         ) | ||||
|  | ||||
|     def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: | ||||
|     def get_context_data(self, **kwargs: Any) -> dict[str, Any]: | ||||
|         kwargs["policy"] = self.get_object() | ||||
|         return super().get_context_data(**kwargs) | ||||
|  | ||||
| @ -116,7 +94,7 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo | ||||
|  | ||||
|         p_request = PolicyRequest(user) | ||||
|         p_request.debug = True | ||||
|         p_request.http_request = self.request | ||||
|         p_request.set_http_request(self.request) | ||||
|         p_request.context = form.cleaned_data.get("context", {}) | ||||
|  | ||||
|         proc = PolicyProcess(PolicyBinding(policy=policy), p_request, None) | ||||
|  | ||||
| @ -6,55 +6,20 @@ 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.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 guardian.shortcuts import get_objects_for_user | ||||
| from django.views.generic import UpdateView | ||||
| from guardian.mixins import PermissionRequiredMixin | ||||
|  | ||||
| from authentik.admin.views.utils import ( | ||||
|     BackSuccessUrlMixin, | ||||
|     DeleteMessageView, | ||||
|     UserPaginateListMixin, | ||||
| ) | ||||
| from authentik.admin.views.utils import DeleteMessageView | ||||
| 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, | ||||
| @ -66,7 +31,7 @@ class PolicyBindingCreateView( | ||||
|     form_class = PolicyBindingForm | ||||
|  | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_admin:policies-bindings") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created PolicyBinding") | ||||
|  | ||||
|     def get_initial(self) -> dict[str, Any]: | ||||
| @ -88,7 +53,6 @@ class PolicyBindingCreateView( | ||||
|  | ||||
| class PolicyBindingUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     UpdateView, | ||||
| @ -100,7 +64,7 @@ class PolicyBindingUpdateView( | ||||
|     form_class = PolicyBindingForm | ||||
|  | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_admin:policies-bindings") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully updated PolicyBinding") | ||||
|  | ||||
|  | ||||
| @ -113,5 +77,5 @@ class PolicyBindingDeleteView( | ||||
|     permission_required = "authentik_policies.delete_policybinding" | ||||
|  | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_admin:policies-bindings") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted PolicyBinding") | ||||
|  | ||||
| @ -8,7 +8,6 @@ from django.contrib.auth.mixins import ( | ||||
| ) | ||||
| from django.contrib.messages.views import SuccessMessageMixin | ||||
| 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 | ||||
| @ -16,7 +15,6 @@ from guardian.mixins import PermissionRequiredMixin | ||||
|  | ||||
| from authentik.admin.forms.policies import PolicyTestForm | ||||
| from authentik.admin.views.utils import ( | ||||
|     BackSuccessUrlMixin, | ||||
|     DeleteMessageView, | ||||
|     InheritanceCreateView, | ||||
|     InheritanceUpdateView, | ||||
| @ -26,7 +24,6 @@ from authentik.core.models import PropertyMapping | ||||
|  | ||||
| class PropertyMappingCreateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     DjangoPermissionRequiredMixin, | ||||
|     InheritanceCreateView, | ||||
| @ -35,15 +32,13 @@ class PropertyMappingCreateView( | ||||
|  | ||||
|     model = PropertyMapping | ||||
|     permission_required = "authentik_core.add_propertymapping" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created Property Mapping") | ||||
|  | ||||
|  | ||||
| class PropertyMappingUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     InheritanceUpdateView, | ||||
| @ -52,9 +47,8 @@ class PropertyMappingUpdateView( | ||||
|  | ||||
|     model = PropertyMapping | ||||
|     permission_required = "authentik_core.change_propertymapping" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully updated Property Mapping") | ||||
|  | ||||
|  | ||||
| @ -65,9 +59,8 @@ class PropertyMappingDeleteView( | ||||
|  | ||||
|     model = PropertyMapping | ||||
|     permission_required = "authentik_core.delete_propertymapping" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted Property Mapping") | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -4,12 +4,10 @@ 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 PermissionRequiredMixin | ||||
|  | ||||
| from authentik.admin.views.utils import ( | ||||
|     BackSuccessUrlMixin, | ||||
|     DeleteMessageView, | ||||
|     InheritanceCreateView, | ||||
|     InheritanceUpdateView, | ||||
| @ -19,7 +17,6 @@ from authentik.core.models import Provider | ||||
|  | ||||
| class ProviderCreateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     DjangoPermissionRequiredMixin, | ||||
|     InheritanceCreateView, | ||||
| @ -28,15 +25,13 @@ class ProviderCreateView( | ||||
|  | ||||
|     model = Provider | ||||
|     permission_required = "authentik_core.add_provider" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_admin:providers") | ||||
|     success_message = _("Successfully created Provider") | ||||
|  | ||||
|  | ||||
| class ProviderUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     InheritanceUpdateView, | ||||
| @ -45,9 +40,8 @@ class ProviderUpdateView( | ||||
|  | ||||
|     model = Provider | ||||
|     permission_required = "authentik_core.change_provider" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_admin:providers") | ||||
|     success_message = _("Successfully updated Provider") | ||||
|  | ||||
|  | ||||
| @ -58,7 +52,6 @@ class ProviderDeleteView( | ||||
|  | ||||
|     model = Provider | ||||
|     permission_required = "authentik_core.delete_provider" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_admin:providers") | ||||
|     success_message = _("Successfully deleted Provider") | ||||
|  | ||||
| @ -4,41 +4,19 @@ 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 guardian.mixins import 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, | ||||
| @ -48,14 +26,13 @@ class SourceCreateView( | ||||
|     model = Source | ||||
|     permission_required = "authentik_core.add_source" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_admin:sources") | ||||
|     success_message = _("Successfully created Source") | ||||
|  | ||||
|  | ||||
| class SourceUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     InheritanceUpdateView, | ||||
| @ -65,8 +42,8 @@ class SourceUpdateView( | ||||
|     model = Source | ||||
|     permission_required = "authentik_core.change_source" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_admin:sources") | ||||
|     success_message = _("Successfully updated Source") | ||||
|  | ||||
|  | ||||
| @ -76,6 +53,6 @@ class SourceDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessag | ||||
|     model = Source | ||||
|     permission_required = "authentik_core.delete_source" | ||||
|  | ||||
|     success_url = "/" | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_admin:sources") | ||||
|     success_message = _("Successfully deleted Source") | ||||
|  | ||||
| @ -6,39 +6,18 @@ from django.contrib.auth.mixins import ( | ||||
| 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 guardian.mixins import 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, | ||||
| @ -49,13 +28,12 @@ class StageCreateView( | ||||
|     template_name = "generic/create.html" | ||||
|     permission_required = "authentik_flows.add_stage" | ||||
|  | ||||
|     success_url = reverse_lazy("authentik_admin:stages") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created Stage") | ||||
|  | ||||
|  | ||||
| class StageUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     InheritanceUpdateView, | ||||
| @ -65,7 +43,7 @@ class StageUpdateView( | ||||
|     model = Stage | ||||
|     permission_required = "authentik_flows.update_application" | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_admin:stages") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully updated Stage") | ||||
|  | ||||
|  | ||||
| @ -75,5 +53,5 @@ class StageDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage | ||||
|     model = Stage | ||||
|     template_name = "generic/delete.html" | ||||
|     permission_required = "authentik_flows.delete_stage" | ||||
|     success_url = reverse_lazy("authentik_admin:stages") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted Stage") | ||||
|  | ||||
| @ -9,33 +9,17 @@ 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 django.views.generic import UpdateView | ||||
| from guardian.mixins import PermissionRequiredMixin | ||||
|  | ||||
| from authentik.admin.views.utils import ( | ||||
|     BackSuccessUrlMixin, | ||||
|     DeleteMessageView, | ||||
|     UserPaginateListMixin, | ||||
| ) | ||||
| from authentik.admin.views.utils import DeleteMessageView | ||||
| 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, | ||||
| @ -47,7 +31,7 @@ class StageBindingCreateView( | ||||
|     form_class = FlowStageBindingForm | ||||
|  | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_admin:stage-bindings") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created StageBinding") | ||||
|  | ||||
|     def get_initial(self) -> dict[str, Any]: | ||||
| @ -67,7 +51,6 @@ class StageBindingCreateView( | ||||
|  | ||||
| class StageBindingUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     UpdateView, | ||||
| @ -79,7 +62,7 @@ class StageBindingUpdateView( | ||||
|     form_class = FlowStageBindingForm | ||||
|  | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_admin:stage-bindings") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully updated StageBinding") | ||||
|  | ||||
|  | ||||
| @ -92,5 +75,5 @@ class StageBindingDeleteView( | ||||
|     permission_required = "authentik_flows.delete_flowstagebinding" | ||||
|  | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_admin:stage-bindings") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted FlowStageBinding") | ||||
|  | ||||
| @ -7,39 +7,16 @@ 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 guardian.mixins import PermissionRequiredMixin | ||||
|  | ||||
| from authentik.admin.views.utils import ( | ||||
|     BackSuccessUrlMixin, | ||||
|     DeleteMessageView, | ||||
|     SearchListMixin, | ||||
|     UserPaginateListMixin, | ||||
| ) | ||||
| from authentik.admin.views.utils import DeleteMessageView | ||||
| 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, | ||||
| @ -51,7 +28,7 @@ class InvitationCreateView( | ||||
|     permission_required = "authentik_stages_invitation.add_invitation" | ||||
|  | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_admin:stage-invitations") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created Invitation") | ||||
|  | ||||
|     def form_valid(self, form): | ||||
| @ -70,5 +47,5 @@ class InvitationDeleteView( | ||||
|     permission_required = "authentik_stages_invitation.delete_invitation" | ||||
|  | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_admin:stage-invitations") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted Invitation") | ||||
|  | ||||
| @ -6,44 +6,17 @@ from django.contrib.auth.mixins import ( | ||||
| 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 django.views.generic import UpdateView | ||||
| from guardian.mixins import PermissionRequiredMixin | ||||
|  | ||||
| from authentik.admin.views.utils import ( | ||||
|     BackSuccessUrlMixin, | ||||
|     DeleteMessageView, | ||||
|     SearchListMixin, | ||||
|     UserPaginateListMixin, | ||||
| ) | ||||
| from authentik.admin.views.utils import DeleteMessageView | ||||
| 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, | ||||
| @ -55,13 +28,12 @@ class PromptCreateView( | ||||
|     permission_required = "authentik_stages_prompt.add_prompt" | ||||
|  | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_admin:stage-prompts") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created Prompt") | ||||
|  | ||||
|  | ||||
| class PromptUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     UpdateView, | ||||
| @ -73,7 +45,7 @@ class PromptUpdateView( | ||||
|     permission_required = "authentik_stages_prompt.change_prompt" | ||||
|  | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_admin:stage-prompts") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully updated Prompt") | ||||
|  | ||||
|  | ||||
| @ -84,5 +56,5 @@ class PromptDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessag | ||||
|     permission_required = "authentik_stages_prompt.delete_prompt" | ||||
|  | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_admin:stage-prompts") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted Prompt") | ||||
|  | ||||
| @ -1,23 +0,0 @@ | ||||
| """authentik Tasks List""" | ||||
| from typing import Any, Dict | ||||
|  | ||||
| from django.views.generic.base import TemplateView | ||||
|  | ||||
| from authentik.admin.mixins import AdminRequiredMixin | ||||
| from authentik.events.monitored_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 | ||||
| @ -2,38 +2,12 @@ | ||||
| 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 guardian.mixins import PermissionRequiredMixin | ||||
|  | ||||
| from authentik.admin.views.utils import ( | ||||
|     DeleteMessageView, | ||||
|     SearchListMixin, | ||||
|     UserPaginateListMixin, | ||||
| ) | ||||
| from authentik.admin.views.utils import DeleteMessageView | ||||
| 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""" | ||||
|  | ||||
| @ -41,5 +15,5 @@ class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage | ||||
|     permission_required = "authentik_core.delete_token" | ||||
|  | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_admin:tokens") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted Token") | ||||
|  | ||||
| @ -8,49 +8,20 @@ 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.urls import 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 django.views.generic import DetailView, UpdateView | ||||
| from guardian.mixins import PermissionRequiredMixin | ||||
|  | ||||
| from authentik.admin.forms.users import UserForm | ||||
| from authentik.admin.views.utils import ( | ||||
|     BackSuccessUrlMixin, | ||||
|     DeleteMessageView, | ||||
|     SearchListMixin, | ||||
|     UserPaginateListMixin, | ||||
| ) | ||||
| from authentik.admin.views.utils import DeleteMessageView | ||||
| 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, | ||||
| @ -62,13 +33,12 @@ class UserCreateView( | ||||
|     permission_required = "authentik_core.add_user" | ||||
|  | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_admin:users") | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully created User") | ||||
|  | ||||
|  | ||||
| class UserUpdateView( | ||||
|     SuccessMessageMixin, | ||||
|     BackSuccessUrlMixin, | ||||
|     LoginRequiredMixin, | ||||
|     PermissionRequiredMixin, | ||||
|     UpdateView, | ||||
| @ -82,7 +52,7 @@ class UserUpdateView( | ||||
|     # 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_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully updated User") | ||||
|  | ||||
|  | ||||
| @ -95,13 +65,11 @@ class UserDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageV | ||||
|     # 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_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully deleted User") | ||||
|  | ||||
|  | ||||
| class UserDisableView( | ||||
|     LoginRequiredMixin, PermissionRequiredMixin, BackSuccessUrlMixin, DeleteMessageView | ||||
| ): | ||||
| class UserDisableView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView): | ||||
|     """Disable user""" | ||||
|  | ||||
|     object: User | ||||
| @ -112,7 +80,7 @@ class UserDisableView( | ||||
|     # 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_url = reverse_lazy("authentik_core:shell") | ||||
|     success_message = _("Successfully disabled User") | ||||
|  | ||||
|     def delete(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: | ||||
| @ -123,9 +91,7 @@ class UserDisableView( | ||||
|         return HttpResponseRedirect(success_url) | ||||
|  | ||||
|  | ||||
| class UserEnableView( | ||||
|     LoginRequiredMixin, PermissionRequiredMixin, BackSuccessUrlMixin, DetailView | ||||
| ): | ||||
| class UserEnableView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): | ||||
|     """Enable user""" | ||||
|  | ||||
|     object: User | ||||
| @ -135,15 +101,14 @@ class UserEnableView( | ||||
|  | ||||
|     # 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_url = reverse_lazy("authentik_core:shell") | ||||
|     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) | ||||
|         return HttpResponseRedirect(self.success_url) | ||||
|  | ||||
|  | ||||
| class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): | ||||
| @ -160,9 +125,7 @@ class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailV | ||||
|         ) | ||||
|         querystring = urlencode({"token": token.key}) | ||||
|         link = request.build_absolute_uri( | ||||
|             reverse("authentik_flows:default-recovery") + f"?{querystring}" | ||||
|             reverse_lazy("authentik_flows:default-recovery") + f"?{querystring}" | ||||
|         ) | ||||
|         messages.success( | ||||
|             request, _("Password reset link: <pre>%(link)s</pre>" % {"link": link}) | ||||
|         ) | ||||
|         return redirect("authentik_admin:users") | ||||
|         messages.success(request, _("Password reset link: %(link)s" % {"link": link})) | ||||
|         return redirect("/") | ||||
|  | ||||
| @ -1,15 +1,11 @@ | ||||
| """authentik admin util views""" | ||||
| from typing import Any, Dict, List, Optional | ||||
| from urllib.parse import urlparse | ||||
| from typing import Any | ||||
|  | ||||
| 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 django.urls import reverse_lazy | ||||
| from django.views.generic import DeleteView, UpdateView | ||||
|  | ||||
| from authentik.lib.utils.reflection import all_subclasses | ||||
| from authentik.lib.views import CreateAssignPermView | ||||
| @ -18,42 +14,13 @@ from authentik.lib.views import CreateAssignPermView | ||||
| class DeleteMessageView(SuccessMessageMixin, DeleteView): | ||||
|     """DeleteView which shows `self.success_message` on successful deletion""" | ||||
|  | ||||
|     success_url = reverse_lazy("authentik_core:shell") | ||||
|  | ||||
|     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""" | ||||
|  | ||||
| @ -67,7 +34,7 @@ class InheritanceCreateView(CreateAssignPermView): | ||||
|             raise Http404 from exc | ||||
|         return model().form | ||||
|  | ||||
|     def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: | ||||
|     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"): | ||||
| @ -78,7 +45,7 @@ class InheritanceCreateView(CreateAssignPermView): | ||||
| class InheritanceUpdateView(UpdateView): | ||||
|     """UpdateView for objects using InheritanceManager""" | ||||
|  | ||||
|     def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: | ||||
|     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"): | ||||
| @ -94,31 +61,3 @@ class InheritanceUpdateView(UpdateView): | ||||
|             .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,7 +1,7 @@ | ||||
| """API Authentication""" | ||||
| from base64 import b64decode | ||||
| from binascii import Error | ||||
| from typing import Any, Optional, Tuple, Union | ||||
| from typing import Any, Optional, Union | ||||
|  | ||||
| from rest_framework.authentication import BaseAuthentication, get_authorization_header | ||||
| from rest_framework.request import Request | ||||
| @ -44,7 +44,7 @@ def token_from_header(raw_header: bytes) -> Optional[Token]: | ||||
| class AuthentikTokenAuthentication(BaseAuthentication): | ||||
|     """Token-based authentication using HTTP Basic authentication""" | ||||
|  | ||||
|     def authenticate(self, request: Request) -> Union[Tuple[User, Any], None]: | ||||
|     def authenticate(self, request: Request) -> Union[tuple[User, Any], None]: | ||||
|         """Token-based authentication using HTTP Basic authentication""" | ||||
|         auth = get_authorization_header(request) | ||||
|  | ||||
|  | ||||
| @ -6,6 +6,7 @@ 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): | ||||
|  | ||||
							
								
								
									
										97
									
								
								authentik/api/pagination_schema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								authentik/api/pagination_schema.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | ||||
| """Swagger Pagination Schema class""" | ||||
| from typing import OrderedDict | ||||
|  | ||||
| from drf_yasg2 import openapi | ||||
| from drf_yasg2.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,10 +1,11 @@ | ||||
| """core Configs API""" | ||||
| from django.db.models import Model | ||||
| from drf_yasg2.utils import swagger_auto_schema | ||||
| from rest_framework.fields import BooleanField, CharField | ||||
| 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.serializers import Serializer | ||||
| from rest_framework.viewsets import ViewSet | ||||
|  | ||||
| from authentik.lib.config import CONFIG | ||||
| @ -13,12 +14,12 @@ from authentik.lib.config import CONFIG | ||||
| class ConfigSerializer(Serializer): | ||||
|     """Serialize authentik Config into DRF Object""" | ||||
|  | ||||
|     branding_logo = ReadOnlyField() | ||||
|     branding_title = ReadOnlyField() | ||||
|     branding_logo = CharField(read_only=True) | ||||
|     branding_title = CharField(read_only=True) | ||||
|  | ||||
|     error_reporting_enabled = ReadOnlyField() | ||||
|     error_reporting_environment = ReadOnlyField() | ||||
|     error_reporting_send_pii = ReadOnlyField() | ||||
|     error_reporting_enabled = BooleanField(read_only=True) | ||||
|     error_reporting_environment = CharField(read_only=True) | ||||
|     error_reporting_send_pii = BooleanField(read_only=True) | ||||
|  | ||||
|     def create(self, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
| @ -32,7 +33,7 @@ class ConfigsViewSet(ViewSet): | ||||
|  | ||||
|     permission_classes = [AllowAny] | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: ConfigSerializer(many=True)}) | ||||
|     @swagger_auto_schema(responses={200: ConfigSerializer(many=False)}) | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """Retrive public configuration options""" | ||||
|         config = ConfigSerializer( | ||||
|  | ||||
| @ -1,37 +0,0 @@ | ||||
| """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) | ||||
| @ -10,7 +10,6 @@ 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.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 | ||||
| @ -23,22 +22,17 @@ 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 import ( | ||||
|     FlowCacheViewSet, | ||||
|     FlowStageBindingViewSet, | ||||
|     FlowViewSet, | ||||
|     StageViewSet, | ||||
| ) | ||||
| from authentik.outposts.api import ( | ||||
| 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 ( | ||||
|     DockerServiceConnectionViewSet, | ||||
|     KubernetesServiceConnectionViewSet, | ||||
|     OutpostViewSet, | ||||
| ) | ||||
| from authentik.policies.api import ( | ||||
|     PolicyBindingViewSet, | ||||
|     PolicyCacheViewSet, | ||||
|     PolicyViewSet, | ||||
|     ServiceConnectionViewSet, | ||||
| ) | ||||
| from authentik.outposts.api.outposts import OutpostViewSet | ||||
| from authentik.policies.api import PolicyBindingViewSet, PolicyViewSet | ||||
| from authentik.policies.dummy.api import DummyPolicyViewSet | ||||
| from authentik.policies.event_matcher.api import EventMatcherPolicyViewSet | ||||
| from authentik.policies.expiry.api import PasswordExpiryPolicyViewSet | ||||
| @ -46,7 +40,11 @@ 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 ReputationPolicyViewSet | ||||
| from authentik.policies.reputation.api import ( | ||||
|     IPReputationViewSet, | ||||
|     ReputationPolicyViewSet, | ||||
|     UserReputationViewSet, | ||||
| ) | ||||
| from authentik.providers.oauth2.api import OAuth2ProviderViewSet, ScopeMappingViewSet | ||||
| from authentik.providers.proxy.api import ( | ||||
|     ProxyOutpostConfigViewSet, | ||||
| @ -56,15 +54,19 @@ from authentik.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProvide | ||||
| from authentik.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet | ||||
| from authentik.sources.oauth.api import OAuthSourceViewSet | ||||
| from authentik.sources.saml.api import SAMLSourceViewSet | ||||
| from authentik.stages.authenticator_static.api import AuthenticatorStaticStageViewSet | ||||
| from authentik.stages.authenticator_totp.api import AuthenticatorTOTPStageViewSet | ||||
| from authentik.stages.authenticator_validate.api import ( | ||||
|     AuthenticatorValidateStageViewSet, | ||||
| ) | ||||
| from authentik.stages.authenticator_webauthn.api import AuthenticateWebAuthnStageViewSet | ||||
| from authentik.stages.captcha.api import CaptchaStageViewSet | ||||
| from authentik.stages.consent.api import ConsentStageViewSet | ||||
| from authentik.stages.deny.api import DenyStageViewSet | ||||
| 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 | ||||
| @ -74,7 +76,6 @@ 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") | ||||
| @ -88,6 +89,7 @@ router.register("core/users", UserViewSet) | ||||
| router.register("core/tokens", TokenViewSet) | ||||
|  | ||||
| router.register("outposts/outposts", OutpostViewSet) | ||||
| router.register("outposts/service_connections/all", ServiceConnectionViewSet) | ||||
| router.register("outposts/service_connections/docker", DockerServiceConnectionViewSet) | ||||
| router.register( | ||||
|     "outposts/service_connections/kubernetes", KubernetesServiceConnectionViewSet | ||||
| @ -95,7 +97,6 @@ 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) | ||||
| @ -111,7 +112,6 @@ 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) | ||||
| @ -119,6 +119,8 @@ 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) | ||||
| @ -132,15 +134,17 @@ router.register("propertymappings/saml", SAMLPropertyMappingViewSet) | ||||
| router.register("propertymappings/scope", ScopeMappingViewSet) | ||||
|  | ||||
| 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/otp_static", OTPStaticStageViewSet) | ||||
| router.register("stages/otp_time", OTPTimeStageViewSet) | ||||
| router.register("stages/otp_validate", OTPValidateStageViewSet) | ||||
| router.register("stages/invitation/stages", InvitationStageViewSet) | ||||
| router.register("stages/password", PasswordStageViewSet) | ||||
| router.register("stages/prompt/prompts", PromptViewSet) | ||||
| router.register("stages/prompt/stages", PromptStageViewSet) | ||||
| @ -178,4 +182,9 @@ urlpatterns = [ | ||||
|         name="schema-swagger-ui", | ||||
|     ), | ||||
|     path("redoc/", SchemaView.with_ui("redoc", cache_timeout=0), name="schema-redoc"), | ||||
|     path( | ||||
|         "flows/executor/<slug:flow_slug>/", | ||||
|         FlowExecutorView.as_view(), | ||||
|         name="flow-executor", | ||||
|     ), | ||||
| ] + router.urls | ||||
|  | ||||
| @ -19,3 +19,5 @@ class GroupViewSet(ModelViewSet): | ||||
|  | ||||
|     queryset = Group.objects.all() | ||||
|     serializer_class = GroupSerializer | ||||
|     search_fields = ["name", "is_superuser"] | ||||
|     filterset_fields = ["name", "is_superuser"] | ||||
|  | ||||
| @ -1,9 +1,16 @@ | ||||
| """PropertyMapping API Views""" | ||||
| from django.urls import reverse | ||||
| from drf_yasg2.utils import swagger_auto_schema | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ModelSerializer, SerializerMethodField | ||||
| from rest_framework.viewsets import ReadOnlyModelViewSet | ||||
|  | ||||
| from authentik.core.api.utils import MetaNameSerializer | ||||
| from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer | ||||
| from authentik.core.models import PropertyMapping | ||||
| from authentik.lib.templatetags.authentik_utils import verbose_name | ||||
| from authentik.lib.utils.reflection import all_subclasses | ||||
|  | ||||
|  | ||||
| class PropertyMappingSerializer(ModelSerializer, MetaNameSerializer): | ||||
| @ -47,3 +54,19 @@ class PropertyMappingViewSet(ReadOnlyModelViewSet): | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         return PropertyMapping.objects.select_subclasses() | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) | ||||
|     @action(detail=False) | ||||
|     def types(self, request: Request) -> Response: | ||||
|         """Get all creatable property-mapping types""" | ||||
|         data = [] | ||||
|         for subclass in all_subclasses(self.queryset.model): | ||||
|             data.append( | ||||
|                 { | ||||
|                     "name": verbose_name(subclass), | ||||
|                     "description": subclass.__doc__, | ||||
|                     "link": reverse("authentik_admin:property-mapping-create") | ||||
|                     + f"?type={subclass.__name__}", | ||||
|                 } | ||||
|             ) | ||||
|         return Response(TypeCreateSerializer(data, many=True).data) | ||||
|  | ||||
| @ -1,10 +1,18 @@ | ||||
| """Provider API Views""" | ||||
| from django.urls import reverse | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from drf_yasg2.utils import swagger_auto_schema | ||||
| 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 ModelViewSet | ||||
|  | ||||
| from authentik.core.api.utils import MetaNameSerializer | ||||
| from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer | ||||
| from authentik.core.models import Provider | ||||
| from authentik.lib.templatetags.authentik_utils import verbose_name | ||||
| from authentik.lib.utils.reflection import all_subclasses | ||||
|  | ||||
|  | ||||
| class ProviderSerializer(ModelSerializer, MetaNameSerializer): | ||||
| @ -51,3 +59,26 @@ class ProviderViewSet(ModelViewSet): | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         return Provider.objects.select_subclasses() | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) | ||||
|     @action(detail=False) | ||||
|     def types(self, request: Request) -> Response: | ||||
|         """Get all creatable provider types""" | ||||
|         data = [] | ||||
|         for subclass in all_subclasses(self.queryset.model): | ||||
|             data.append( | ||||
|                 { | ||||
|                     "name": verbose_name(subclass), | ||||
|                     "description": subclass.__doc__, | ||||
|                     "link": reverse("authentik_admin:provider-create") | ||||
|                     + f"?type={subclass.__name__}", | ||||
|                 } | ||||
|             ) | ||||
|         data.append( | ||||
|             { | ||||
|                 "name": _("SAML Provider from Metadata"), | ||||
|                 "description": _("Create a SAML Provider by importing its Metadata."), | ||||
|                 "link": reverse("authentik_admin:provider-saml-from-metadata"), | ||||
|             } | ||||
|         ) | ||||
|         return Response(TypeCreateSerializer(data, many=True).data) | ||||
|  | ||||
| @ -1,31 +1,41 @@ | ||||
| """Source API Views""" | ||||
| from django.urls import reverse | ||||
| from drf_yasg2.utils import swagger_auto_schema | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ModelSerializer, SerializerMethodField | ||||
| from rest_framework.viewsets import ReadOnlyModelViewSet | ||||
|  | ||||
| from authentik.admin.forms.source import SOURCE_SERIALIZER_FIELDS | ||||
| from authentik.core.api.utils import MetaNameSerializer | ||||
| from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer | ||||
| from authentik.core.models import Source | ||||
| from authentik.lib.templatetags.authentik_utils import verbose_name | ||||
| from authentik.lib.utils.reflection import all_subclasses | ||||
|  | ||||
|  | ||||
| class SourceSerializer(ModelSerializer, MetaNameSerializer): | ||||
|     """Source Serializer""" | ||||
|  | ||||
|     __type__ = SerializerMethodField(method_name="get_type") | ||||
|     object_type = SerializerMethodField() | ||||
|  | ||||
|     def get_type(self, obj): | ||||
|     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("source", "") | ||||
|  | ||||
|     def to_representation(self, instance: Source): | ||||
|         # pyright: reportGeneralTypeIssues=false | ||||
|         if instance.__class__ == Source: | ||||
|             return super().to_representation(instance) | ||||
|         return instance.serializer(instance=instance).data | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         model = Source | ||||
|         fields = SOURCE_SERIALIZER_FIELDS + ["__type__"] | ||||
|         fields = [ | ||||
|             "pk", | ||||
|             "name", | ||||
|             "slug", | ||||
|             "enabled", | ||||
|             "authentication_flow", | ||||
|             "enrollment_flow", | ||||
|             "object_type", | ||||
|             "verbose_name", | ||||
|             "verbose_name_plural", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class SourceViewSet(ReadOnlyModelViewSet): | ||||
| @ -37,3 +47,19 @@ class SourceViewSet(ReadOnlyModelViewSet): | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         return Source.objects.select_subclasses() | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) | ||||
|     @action(detail=False) | ||||
|     def types(self, request: Request) -> Response: | ||||
|         """Get all creatable source types""" | ||||
|         data = [] | ||||
|         for subclass in all_subclasses(self.queryset.model): | ||||
|             data.append( | ||||
|                 { | ||||
|                     "name": verbose_name(subclass), | ||||
|                     "description": subclass.__doc__, | ||||
|                     "link": reverse("authentik_admin:source-create") | ||||
|                     + f"?type={subclass.__name__}", | ||||
|                 } | ||||
|             ) | ||||
|         return Response(TypeCreateSerializer(data, many=True).data) | ||||
|  | ||||
| @ -1,11 +1,15 @@ | ||||
| """Tokens API Viewset""" | ||||
| from django.db.models.base import Model | ||||
| from django.http.response import Http404 | ||||
| from drf_yasg2.utils import swagger_auto_schema | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.fields import CharField | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ModelSerializer | ||||
| from rest_framework.serializers import ModelSerializer, Serializer | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
|  | ||||
| from authentik.core.api.users import UserSerializer | ||||
| from authentik.core.models import Token | ||||
| from authentik.events.models import Event, EventAction | ||||
|  | ||||
| @ -13,10 +17,33 @@ from authentik.events.models import Event, EventAction | ||||
| class TokenSerializer(ModelSerializer): | ||||
|     """Token Serializer""" | ||||
|  | ||||
|     user = UserSerializer() | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         model = Token | ||||
|         fields = ["pk", "identifier", "intent", "user", "description"] | ||||
|         fields = [ | ||||
|             "pk", | ||||
|             "identifier", | ||||
|             "intent", | ||||
|             "user", | ||||
|             "description", | ||||
|             "expires", | ||||
|             "expiring", | ||||
|         ] | ||||
|         depth = 2 | ||||
|  | ||||
|  | ||||
| class TokenViewSerializer(Serializer): | ||||
|     """Show token's current key""" | ||||
|  | ||||
|     key = CharField(read_only=True) | ||||
|  | ||||
|     def create(self, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def update(self, instance: Model, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|  | ||||
| class TokenViewSet(ModelViewSet): | ||||
| @ -25,13 +52,29 @@ class TokenViewSet(ModelViewSet): | ||||
|     lookup_field = "identifier" | ||||
|     queryset = Token.filter_not_expired() | ||||
|     serializer_class = TokenSerializer | ||||
|     search_fields = [ | ||||
|         "identifier", | ||||
|         "intent", | ||||
|         "user__username", | ||||
|         "description", | ||||
|     ] | ||||
|     filterset_fields = [ | ||||
|         "identifier", | ||||
|         "intent", | ||||
|         "user__username", | ||||
|         "description", | ||||
|     ] | ||||
|     ordering = ["expires"] | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: TokenViewSerializer(many=False)}) | ||||
|     @action(detail=True) | ||||
|     # pylint: disable=unused-argument | ||||
|     def view_key(self, request: Request, identifier: str) -> Response: | ||||
|         """Return token key and log access""" | ||||
|         tokens = Token.filter_not_expired(identifier=identifier) | ||||
|         if not tokens.exists(): | ||||
|         token: Token = self.get_object() | ||||
|         if token.is_expired: | ||||
|             raise Http404 | ||||
|         token = tokens.first() | ||||
|         Event.new(EventAction.TOKEN_VIEW, token=token).from_http(request) | ||||
|         return Response({"key": token.key}) | ||||
|         Event.new(EventAction.SECRET_VIEW, secret=token).from_http(  # noqa # nosec | ||||
|             request | ||||
|         ) | ||||
|         return Response(TokenViewSerializer({"key": token.key}).data) | ||||
|  | ||||
| @ -2,33 +2,35 @@ | ||||
| from drf_yasg2.utils import swagger_auto_schema | ||||
| from guardian.utils import get_anonymous_user | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.fields import CharField | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ( | ||||
|     BooleanField, | ||||
|     ModelSerializer, | ||||
|     SerializerMethodField, | ||||
| ) | ||||
| from rest_framework.serializers import BooleanField, ModelSerializer | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
|  | ||||
| from authentik.core.models import User | ||||
| from authentik.lib.templatetags.authentik_utils import avatar | ||||
|  | ||||
|  | ||||
| class UserSerializer(ModelSerializer): | ||||
|     """User Serializer""" | ||||
|  | ||||
|     is_superuser = BooleanField(read_only=True) | ||||
|     avatar = SerializerMethodField() | ||||
|  | ||||
|     def get_avatar(self, user: User) -> str: | ||||
|         """Add user's avatar as URL""" | ||||
|         return avatar(user) | ||||
|     avatar = CharField(read_only=True) | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         model = User | ||||
|         fields = ["pk", "username", "name", "is_superuser", "email", "avatar"] | ||||
|         fields = [ | ||||
|             "pk", | ||||
|             "username", | ||||
|             "name", | ||||
|             "is_active", | ||||
|             "last_login", | ||||
|             "is_superuser", | ||||
|             "email", | ||||
|             "avatar", | ||||
|             "attributes", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class UserViewSet(ModelViewSet): | ||||
| @ -36,6 +38,8 @@ class UserViewSet(ModelViewSet): | ||||
|  | ||||
|     queryset = User.objects.none() | ||||
|     serializer_class = UserSerializer | ||||
|     search_fields = ["username", "name", "is_active"] | ||||
|     filterset_fields = ["username", "name", "is_active"] | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         return User.objects.all().exclude(pk=get_anonymous_user().pk) | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| """API Utilities""" | ||||
| from django.db.models import Model | ||||
| from rest_framework.fields import CharField, IntegerField | ||||
| from rest_framework.serializers import Serializer, SerializerMethodField | ||||
|  | ||||
|  | ||||
| @ -22,3 +23,29 @@ class MetaNameSerializer(Serializer): | ||||
|     def get_verbose_name_plural(self, obj: Model) -> str: | ||||
|         """Return object's plural verbose_name""" | ||||
|         return obj._meta.verbose_name_plural | ||||
|  | ||||
|  | ||||
| class TypeCreateSerializer(Serializer): | ||||
|     """Types of an object that can be created""" | ||||
|  | ||||
|     name = CharField(read_only=True) | ||||
|     description = CharField(read_only=True) | ||||
|     link = CharField(read_only=True) | ||||
|  | ||||
|     def create(self, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def update(self, instance: Model, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|  | ||||
| class CacheSerializer(Serializer): | ||||
|     """Generic cache stats for an object""" | ||||
|  | ||||
|     count = IntegerField(read_only=True) | ||||
|  | ||||
|     def create(self, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def update(self, instance: Model, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
| @ -9,10 +9,10 @@ from authentik.lib.widgets import GroupedModelChoiceField | ||||
| class ApplicationForm(forms.ModelForm): | ||||
|     """Application Form""" | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|     def __init__(self, *args, **kwargs):  # pragma: no cover | ||||
|         super().__init__(*args, **kwargs) | ||||
|         self.fields["provider"].queryset = ( | ||||
|             Provider.objects.all().order_by("pk").select_subclasses() | ||||
|             Provider.objects.all().order_by("name").select_subclasses() | ||||
|         ) | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
| @ -9,6 +9,7 @@ from django.http import HttpRequest, HttpResponse | ||||
| SESSION_IMPERSONATE_USER = "authentik_impersonate_user" | ||||
| SESSION_IMPERSONATE_ORIGINAL_USER = "authentik_impersonate_original_user" | ||||
| LOCAL = local() | ||||
| RESPONSE_HEADER_ID = "X-authentik-id" | ||||
|  | ||||
|  | ||||
| class ImpersonateMiddleware: | ||||
| @ -43,7 +44,7 @@ class RequestIDMiddleware: | ||||
|             setattr(request, "request_id", request_id) | ||||
|             LOCAL.authentik = {"request_id": request_id} | ||||
|         response = self.get_response(request) | ||||
|         response["X-authentik-id"] = request.request_id | ||||
|         response[RESPONSE_HEADER_ID] = request.request_id | ||||
|         del LOCAL.authentik["request_id"] | ||||
|         return response | ||||
|  | ||||
|  | ||||
| @ -1,15 +1,20 @@ | ||||
| """authentik core models""" | ||||
| from datetime import timedelta | ||||
| from typing import Any, Dict, Optional, Type | ||||
| from hashlib import md5, sha256 | ||||
| from typing import Any, Optional, Type | ||||
| from urllib.parse import urlencode | ||||
| from uuid import uuid4 | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.contrib.auth.models import AbstractUser | ||||
| from django.contrib.auth.models import UserManager as DjangoUserManager | ||||
| from django.db import models | ||||
| from django.db.models import Q, QuerySet | ||||
| from django.forms import ModelForm | ||||
| from django.http import HttpRequest | ||||
| from django.templatetags.static import static | ||||
| from django.utils.functional import cached_property | ||||
| from django.utils.html import escape | ||||
| from django.utils.timezone import now | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from guardian.mixins import GuardianUserMixin | ||||
| @ -21,6 +26,7 @@ from authentik.core.exceptions import PropertyMappingExpressionException | ||||
| from authentik.core.signals import password_changed | ||||
| from authentik.core.types import UILoginButton | ||||
| from authentik.flows.models import Flow | ||||
| from authentik.lib.config import CONFIG | ||||
| from authentik.lib.models import CreatedUpdatedModel, SerializerModel | ||||
| from authentik.managed.models import ManagedModel | ||||
| from authentik.policies.models import PolicyBindingModel | ||||
| @ -29,6 +35,9 @@ LOGGER = get_logger() | ||||
| USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug" | ||||
| USER_ATTRIBUTE_SA = "goauthentik.io/user/service-account" | ||||
|  | ||||
| GRAVATAR_URL = "https://secure.gravatar.com" | ||||
| DEFAULT_AVATAR = static("dist/assets/images/user_default.png") | ||||
|  | ||||
|  | ||||
| def default_token_duration(): | ||||
|     """Default duration a Token is valid""" | ||||
| @ -81,7 +90,7 @@ class UserManager(DjangoUserManager): | ||||
|  | ||||
|  | ||||
| class User(GuardianUserMixin, AbstractUser): | ||||
|     """Custom User model to allow easier adding o f user-based settings""" | ||||
|     """Custom User model to allow easier adding of user-based settings""" | ||||
|  | ||||
|     uuid = models.UUIDField(default=uuid4, editable=False) | ||||
|     name = models.TextField(help_text=_("User's display name.")) | ||||
| @ -94,7 +103,7 @@ class User(GuardianUserMixin, AbstractUser): | ||||
|  | ||||
|     objects = UserManager() | ||||
|  | ||||
|     def group_attributes(self) -> Dict[str, Any]: | ||||
|     def group_attributes(self) -> dict[str, Any]: | ||||
|         """Get a dictionary containing the attributes from all groups the user belongs to, | ||||
|         including the users attributes""" | ||||
|         final_attributes = {} | ||||
| @ -119,6 +128,30 @@ class User(GuardianUserMixin, AbstractUser): | ||||
|         self.password_change_date = now() | ||||
|         return super().set_password(password) | ||||
|  | ||||
|     @property | ||||
|     def uid(self) -> str: | ||||
|         """Generate a globall unique UID, based on the user ID and the hashed secret key""" | ||||
|         return sha256(f"{self.id}-{settings.SECRET_KEY}".encode("ascii")).hexdigest() | ||||
|  | ||||
|     @property | ||||
|     def avatar(self) -> str: | ||||
|         """Get avatar, depending on authentik.avatar setting""" | ||||
|         mode = CONFIG.raw.get("authentik").get("avatars") | ||||
|         if mode == "none": | ||||
|             return DEFAULT_AVATAR | ||||
|         if mode == "gravatar": | ||||
|             parameters = [ | ||||
|                 ("s", "158"), | ||||
|                 ("r", "g"), | ||||
|             ] | ||||
|             # gravatar uses md5 for their URLs, so md5 can't be avoided | ||||
|             mail_hash = md5(self.email.encode("utf-8")).hexdigest()  # nosec | ||||
|             gravatar_url = ( | ||||
|                 f"{GRAVATAR_URL}/avatar/{mail_hash}?{urlencode(parameters, doseq=True)}" | ||||
|             ) | ||||
|             return escape(gravatar_url) | ||||
|         raise ValueError(f"Invalid avatar mode {mode}") | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         permissions = ( | ||||
| @ -252,11 +285,6 @@ class Source(SerializerModel, PolicyBindingModel): | ||||
|         button. If source doesn't use http-based flow, return None.""" | ||||
|         return None | ||||
|  | ||||
|     @property | ||||
|     def ui_additional_info(self) -> Optional[str]: | ||||
|         """Return additional Info, such as a callback URL. Show in the administration interface.""" | ||||
|         return None | ||||
|  | ||||
|     @property | ||||
|     def ui_user_settings(self) -> Optional[str]: | ||||
|         """Entrypoint to integrate with User settings. Can either return None if no | ||||
|  | ||||
| @ -46,8 +46,7 @@ def backup_database(self: MonitoredTask):  # pragma: no cover | ||||
|             TaskResult( | ||||
|                 TaskResultStatus.SUCCESSFUL, | ||||
|                 [ | ||||
|                     f"Successfully finished database backup {naturaltime(start)}", | ||||
|                     out.getvalue(), | ||||
|                     f"Successfully finished database backup {naturaltime(start)} {out.getvalue()}", | ||||
|                 ], | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
| @ -1,12 +0,0 @@ | ||||
| {% extends "base/skeleton.html" %} | ||||
|  | ||||
| {% load i18n %} | ||||
|  | ||||
| {% block body %} | ||||
| <ak-message-container></ak-message-container> | ||||
| <div class="pf-c-page"> | ||||
|     <a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content">{% trans 'Skip to content' %}</a> | ||||
|     {% block page_content %} | ||||
|     {% endblock %} | ||||
| </div> | ||||
| {% endblock %} | ||||
| @ -16,7 +16,6 @@ | ||||
|         <link rel="stylesheet" type="text/css" href="{% static 'dist/fontawesome.min.css' %}?v={{ ak_version }}"> | ||||
|         <link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}?v={{ ak_version }}"> | ||||
|         <script src="{% url 'javascript-catalog' %}?v={{ ak_version }}"></script> | ||||
|         <script src="{% static 'dist/main.js' %}?v={{ ak_version }}" type="module"></script> | ||||
|         {% block head %} | ||||
|         {% endblock %} | ||||
|     </head> | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| {% extends 'base/page.html' %} | ||||
| {% extends 'base/skeleton.html' %} | ||||
|  | ||||
| {% load i18n %} | ||||
| {% load authentik_utils %} | ||||
|  | ||||
| @ -1,31 +0,0 @@ | ||||
| {% extends "login/base.html" %} | ||||
|  | ||||
| {% load authentik_utils %} | ||||
| {% load i18n %} | ||||
|  | ||||
| {% block title %} | ||||
| {{ title }} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block card %} | ||||
| <form method="POST" action="{{ url }}" autosubmit> | ||||
|     {% csrf_token %} | ||||
|     {% for key, value in attrs.items %} | ||||
|     <input type="hidden" name="{{ key }}" value="{{ value }}"> | ||||
|     {% endfor %} | ||||
|     <div class="pf-c-form__group pf-u-display-flex pf-u-justify-content-center"> | ||||
|         <div class="pf-c-form__group-control"> | ||||
|             <span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading..."> | ||||
|                 <span class="pf-c-spinner__clipper"></span> | ||||
|                 <span class="pf-c-spinner__lead-ball"></span> | ||||
|                 <span class="pf-c-spinner__tail-ball"></span> | ||||
|             </span> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div class="pf-c-form__group pf-m-action"> | ||||
|         <div class="pf-c-form__actions"> | ||||
|             <button class="pf-c-button pf-m-primary pf-m-block" type="submit">{% trans 'Continue' %}</button> | ||||
|         </div> | ||||
|     </div> | ||||
| </form> | ||||
| {% endblock %} | ||||
| @ -1,53 +0,0 @@ | ||||
| {% load i18n %} | ||||
|  | ||||
| <main role="main" class="pf-c-page__main" tabindex="-1" id="main-content"> | ||||
|     <section class="pf-c-page__main-section pf-m-light"> | ||||
|         <div class="pf-c-content"> | ||||
|             <h1> | ||||
|                 <i class="pf-icon pf-icon-applications"></i> | ||||
|                 {% trans 'Applications' %} | ||||
|             </h1> | ||||
|         </div> | ||||
|     </section> | ||||
|     <section class="pf-c-page__main-section"> | ||||
|         {% if applications %} | ||||
|         <div class="pf-l-gallery pf-m-gutter"> | ||||
|             {% for app in applications %} | ||||
|             <a href="{{ app.get_launch_url }}" class="pf-c-card pf-m-hoverable pf-m-compact ak-root-link"> | ||||
|                 <div class="pf-c-card__header"> | ||||
|                     {% if app.meta_icon %} | ||||
|                     <img class="app-icon pf-c-avatar" src="{{ app.meta_icon.url }}" alt="{% trans 'Application Icon' %}"> | ||||
|                     {% else %} | ||||
|                     <i class="pf-icon pf-icon-arrow"></i> | ||||
|                     {% endif %} | ||||
|                 </div> | ||||
|                 <div class="pf-c-card__title"> | ||||
|                     <p id="card-1-check-label">{{ app.name }}</p> | ||||
|                     <div class="pf-c-content"> | ||||
|                         <small>{{ app.meta_publisher }}</small> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="pf-c-card__body"> | ||||
|                     {% trans app.meta_description|truncatewords:35 %} | ||||
|                 </div> | ||||
|             </a> | ||||
|             {% endfor %} | ||||
|         </div> | ||||
|         {% else %} | ||||
|         <div class="pf-c-empty-state pf-m-full-height"> | ||||
|             <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 Applications available.' %}</h1> | ||||
|                 <div class="pf-c-empty-state__body"> | ||||
|                     {% trans "Either no applications are defined, or you don't have access to any." %} | ||||
|                 </div> | ||||
|                 {% if perms.authentik_core.add_application %} | ||||
|                 <a href="{% url 'authentik_admin:application-create' %}" class="pf-c-button pf-m-primary" type="button"> | ||||
|                     {% trans 'Create Application' %} | ||||
|                 </a> | ||||
|                 {% endif %} | ||||
|             </div> | ||||
|         </div> | ||||
|         {% endif %} | ||||
|     </section> | ||||
| </main> | ||||
| @ -28,10 +28,8 @@ | ||||
|         {% for source in sources %} | ||||
|         <li class="pf-c-login__main-footer-links-item"> | ||||
|             <a href="{{ source.url }}" class="pf-c-login__main-footer-links-item-link"> | ||||
|                 {% if source.icon_path %} | ||||
|                 <img src="{% static source.icon_path %}" alt="{{ source.name }}"> | ||||
|                 {% elif source.icon_url %} | ||||
|                 <img src="icon_url" alt="{{ source.name }}"> | ||||
|                 {% if source.icon_url %} | ||||
|                 <img src="{{ source.icon_url }}" alt="{{ source.name }}"> | ||||
|                 {% else %} | ||||
|                 <i class="pf-icon pf-icon-arrow" title="{{ source.name }}"></i> | ||||
|                 {% endif %} | ||||
|  | ||||
| @ -1,19 +0,0 @@ | ||||
| {% extends 'login/base.html' %} | ||||
|  | ||||
| {% load static %} | ||||
| {% load i18n %} | ||||
|  | ||||
| {% block card %} | ||||
| <form method="POST" class="pf-c-form"> | ||||
|     {% block above_form %} | ||||
|     {% endblock %} | ||||
|  | ||||
|     {% include 'partials/form.html' %} | ||||
|  | ||||
|     {% block beneath_form %} | ||||
|     {% endblock %} | ||||
|     <div class="pf-c-form__group pf-m-action"> | ||||
|         <button class="pf-c-button pf-m-primary pf-m-block" type="submit">{% trans primary_action %}</button> | ||||
|     </div> | ||||
| </form> | ||||
| {% endblock %} | ||||
| @ -1,18 +0,0 @@ | ||||
| {% extends 'login/form.html' %} | ||||
|  | ||||
| {% load i18n %} | ||||
| {% load authentik_utils %} | ||||
|  | ||||
| {% block above_form %} | ||||
| <div class="pf-c-form__group"> | ||||
|     <div class="form-control-static"> | ||||
|         <div class="left"> | ||||
|             <img class="pf-c-avatar" src="{% avatar user %}" alt=""> | ||||
|             {{ user.username }} | ||||
|         </div> | ||||
|         <div class="right"> | ||||
|             <a href="{% url 'authentik_flows:cancel' %}">{% trans 'Not you?' %}</a> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| {% endblock %} | ||||
| @ -1,24 +0,0 @@ | ||||
| {% extends 'login/base.html' %} | ||||
|  | ||||
| {% load static %} | ||||
| {% load i18n %} | ||||
| {% load authentik_utils %} | ||||
|  | ||||
| {% block title %} | ||||
| {% trans title %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block head %} | ||||
| <meta http-equiv="refresh" content="0; url={{ target_url }}" /> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block card %} | ||||
| <header class="login-pf-header"> | ||||
|   <h1>{% trans title %}</h1> | ||||
| </header> | ||||
| <form> | ||||
|   <div class="form-group"> | ||||
|     <div class="spinner spinner-lg"></div> | ||||
|   </div> | ||||
| </form> | ||||
| {% endblock %} | ||||
| @ -3,7 +3,7 @@ | ||||
|  | ||||
| {% csrf_token %} | ||||
| {% if form.non_field_errors %} | ||||
| <div class="pf-c-form__group has-error"> | ||||
| <div class="pf-c-form__group"> | ||||
|     <p class="pf-c-form__helper-text pf-m-error"> | ||||
|         {{ form.non_field_errors }} | ||||
|     </p> | ||||
| @ -13,7 +13,7 @@ | ||||
| {% if field.field.widget|fieldtype == 'HiddenInput' %} | ||||
|     {{ field }} | ||||
| {% else %} | ||||
| <div class="pf-c-form__group {% if field.errors %} has-error {% endif %}"> | ||||
| <div class="pf-c-form__group"> | ||||
|     {% if field.field.widget|fieldtype == 'RadioSelect' %} | ||||
|         <label class="pf-c-form__label" {% if field.field.required %}class="required" {% endif %} | ||||
|             for="{{ field.name }}-{{ forloop.counter0 }}"> | ||||
|  | ||||
| @ -1,42 +0,0 @@ | ||||
| {% load i18n %} | ||||
| {% load authentik_utils %} | ||||
|  | ||||
| <div class="pf-c-toolbar__item pf-m-pagination "> | ||||
|     <div class="pf-c-pagination pf-m-compact pf-m-hidden pf-m-visible-on-md"> | ||||
|         <div class="pf-c-pagination pf-m-compact pf-m-compact pf-m-hidden pf-m-visible-on-md"> | ||||
|             <div class="pf-c-options-menu"> | ||||
|                 <div class="pf-c-options-menu__toggle pf-m-text pf-m-plain"> | ||||
|                     <span class="pf-c-options-menu__toggle-text"> | ||||
|                         {% blocktrans with start_index=page_obj.start_index end_index=page_obj.end_index total_items=paginator.count %} | ||||
|                             {{ start_index }} - {{ end_index }} of {{ total_items }} | ||||
|                         {% endblocktrans %} | ||||
|                     </span> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <nav class="pf-c-pagination__nav" aria-label="Pagination"> | ||||
|                 <div class="pf-c-pagination__nav-control pf-m-prev"> | ||||
|                     <a class="pf-c-button pf-m-plain" | ||||
|                         {% if page_obj.has_previous %} | ||||
|                         href="{{ request.path }}?{% query_transform page=page_obj.previous_page_number %}" | ||||
|                         {% else %} | ||||
|                         disabled | ||||
|                         {% endif %} | ||||
|                         aria-label="{% trans 'Go to previous page' %}"> | ||||
|                         <i class="fas fa-angle-left" aria-hidden="true"></i> | ||||
|                     </a> | ||||
|                 </div> | ||||
|                 <div class="pf-c-pagination__nav-control pf-m-next"> | ||||
|                     <a class="pf-c-button pf-m-plain" | ||||
|                         {% if page_obj.has_next %} | ||||
|                         href="{{ request.path }}?{% query_transform page=page_obj.next_page_number %}" | ||||
|                         {% else %} | ||||
|                         disabled | ||||
|                         {% endif %} | ||||
|                         aria-label="{% trans 'Go to next page' %}"> | ||||
|                         <i class="fas fa-angle-right" aria-hidden="true"></i> | ||||
|                     </a> | ||||
|                 </div> | ||||
|             </nav> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| @ -1,13 +0,0 @@ | ||||
|  | ||||
| <div class="pf-c-toolbar__group pf-m-filter-group"> | ||||
|     <div class="pf-c-toolbar__item pf-m-search-filter"> | ||||
|         <form class="pf-c-input-group" method="GET"> | ||||
|             {# include page data for pagination #} | ||||
|             <input type="hidden" name="page" value="{{ page_obj.number }}"> | ||||
|             <input class="pf-c-form-control" name="search" type="search" placeholder="Search..." value="{{ request.GET.search }}"> | ||||
|             <button class="pf-c-button pf-m-control" type="submit"> | ||||
|                 <i class="fas fa-search" aria-hidden="true"></i> | ||||
|             </button> | ||||
|         </form> | ||||
|     </div> | ||||
| </div> | ||||
| @ -1,5 +1,11 @@ | ||||
| {% extends "base/skeleton.html" %} | ||||
|  | ||||
| {% load static %} | ||||
|  | ||||
| {% block head %} | ||||
| <script src="{% static 'dist/main.js' %}?v={{ ak_version }}" type="module"></script> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block body %} | ||||
| <ak-interface-admin></ak-interface-admin> | ||||
| {% endblock %} | ||||
|  | ||||
| @ -13,7 +13,7 @@ | ||||
|                 <p>{% trans "Configure settings relevant to your user profile." %}</p> | ||||
|             </div> | ||||
|         </section> | ||||
|         <ak-tabs> | ||||
|         <ak-tabs vertical="true"> | ||||
|             <section slot="page-1" data-tab-title="{% trans 'User details' %}" class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||
|                 <div class="pf-u-display-flex pf-u-justify-content-center"> | ||||
|                     <div class="pf-u-w-75"> | ||||
| @ -24,9 +24,7 @@ | ||||
|                 </div> | ||||
|             </section> | ||||
|             <section slot="page-2" data-tab-title="{% trans 'Tokens' %}" class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||
|                 <ak-site-shell url="{% url 'authentik_core:user-tokens' %}"> | ||||
|                     <div slot="body"></div> | ||||
|                 </ak-site-shell> | ||||
|                 <ak-token-user-list></ak-token-user-list> | ||||
|             </section> | ||||
|             {% user_stages as user_stages_loc %} | ||||
|             {% for stage, stage_link in user_stages_loc.items %} | ||||
| @ -41,8 +39,8 @@ | ||||
|             </section> | ||||
|             {% endfor %} | ||||
|             {% user_sources as user_sources_loc %} | ||||
|             {% for source, source_link in user_sources_loc.item %} | ||||
|             <section slot="page-{{ source.pk }}" data-tab-title="{{ source|verbose_name }}" class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||
|             {% for source, source_link in user_sources_loc.items %} | ||||
|             <section slot="page-{{ source.pk }}" data-tab-title="{{ source.name }}" class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||
|                 <div class="pf-u-display-flex pf-u-justify-content-center"> | ||||
|                     <div class="pf-u-w-75"> | ||||
|                         <ak-site-shell url="{{ source_link }}"> | ||||
|  | ||||
| @ -1,100 +0,0 @@ | ||||
| {% load i18n %} | ||||
|  | ||||
| <div class="pf-c-card"> | ||||
|     <div class="pf-c-card__header pf-c-title pf-m-md"> | ||||
|         <p>{% trans "Tokens can be used to access authentik's API." %}</p> | ||||
|     </div> | ||||
|     {% 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_core:user-tokens-create' %}"> | ||||
|                     <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||
|                         {% trans 'Create' %} | ||||
|                     </ak-spinner-button> | ||||
|                     <div slot="modal"></div> | ||||
|                 </ak-modal-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 'Expires?' %}</th> | ||||
|                 <th role="columnheader" scope="col">{% trans 'Expiry Date' %}</th> | ||||
|                 <th role="columnheader" scope="col">{% trans 'Description' %}</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.expiring|yesno:"Yes,No" }} | ||||
|                     </span> | ||||
|                 </td> | ||||
|                 <td role="cell"> | ||||
|                     <span> | ||||
|                         {% if not token.expiring %} | ||||
|                         - | ||||
|                         {% else %} | ||||
|                         {{ token.expires }} | ||||
|                         {% endif %} | ||||
|                     </span> | ||||
|                 </td> | ||||
|                 <td role="cell"> | ||||
|                     <span> | ||||
|                         {{ token.description }} | ||||
|                     </span> | ||||
|                 </td> | ||||
|                 <td> | ||||
|                     <ak-modal-button href="{% url 'authentik_core:user-tokens-update' identifier=token.identifier %}"> | ||||
|                         <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_core:user-tokens-delete' identifier=token.identifier %}"> | ||||
|                         <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-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 Tokens.' %} | ||||
|             </h1> | ||||
|             <div class="pf-c-empty-state__body"> | ||||
|                 {% trans 'Currently no tokens exist. Click the button below to create one.' %} | ||||
|             </div> | ||||
|             <ak-modal-button href="{% url 'authentik_core:user-tokens-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> | ||||
| @ -1,6 +1,6 @@ | ||||
| """impersonation tests""" | ||||
| from django.shortcuts import reverse | ||||
| from django.test.testcases import TestCase | ||||
| from django.urls import reverse | ||||
|  | ||||
| from authentik.core.models import User | ||||
|  | ||||
|  | ||||
| @ -2,8 +2,8 @@ | ||||
| import string | ||||
| from random import SystemRandom | ||||
|  | ||||
| from django.shortcuts import reverse | ||||
| from django.test import TestCase | ||||
| from django.urls import reverse | ||||
|  | ||||
| from authentik.core.models import User | ||||
|  | ||||
| @ -28,9 +28,3 @@ class TestOverviewViews(TestCase): | ||||
|         self.assertEqual( | ||||
|             self.client.get(reverse("authentik_core:shell")).status_code, 200 | ||||
|         ) | ||||
|  | ||||
|     def test_overview(self): | ||||
|         """Test overview""" | ||||
|         self.assertEqual( | ||||
|             self.client.get(reverse("authentik_core:overview")).status_code, 200 | ||||
|         ) | ||||
|  | ||||
| @ -2,8 +2,8 @@ | ||||
| import string | ||||
| from random import SystemRandom | ||||
|  | ||||
| from django.shortcuts import reverse | ||||
| from django.test import TestCase | ||||
| from django.urls import reverse | ||||
|  | ||||
| from authentik.core.models import User | ||||
|  | ||||
|  | ||||
| @ -2,6 +2,10 @@ | ||||
| from dataclasses import dataclass | ||||
| from typing import Optional | ||||
|  | ||||
| from django.db.models.base import Model | ||||
| from rest_framework.fields import CharField | ||||
| from rest_framework.serializers import Serializer | ||||
|  | ||||
|  | ||||
| @dataclass | ||||
| class UILoginButton: | ||||
| @ -13,8 +17,19 @@ class UILoginButton: | ||||
|     # URL Which Button points to | ||||
|     url: str | ||||
|  | ||||
|     # Icon name, ran through django's static | ||||
|     icon_path: Optional[str] = None | ||||
|  | ||||
|     # Icon URL, used as-is | ||||
|     icon_url: Optional[str] = None | ||||
|  | ||||
|  | ||||
| class UILoginButtonSerializer(Serializer): | ||||
|     """Serializer for Login buttons of sources""" | ||||
|  | ||||
|     name = CharField() | ||||
|     url = CharField() | ||||
|     icon_url = CharField() | ||||
|  | ||||
|     def create(self, validated_data: dict) -> Model: | ||||
|         return Model() | ||||
|  | ||||
|     def update(self, instance: Model, validated_data: dict) -> Model: | ||||
|         return Model() | ||||
|  | ||||
| @ -1,14 +1,13 @@ | ||||
| """authentik URL Configuration""" | ||||
| from django.urls import path | ||||
|  | ||||
| from authentik.core.views import impersonate, library, shell, user | ||||
| from authentik.core.views import impersonate, shell, user | ||||
|  | ||||
| urlpatterns = [ | ||||
|     path("", shell.ShellView.as_view(), name="shell"), | ||||
|     # User views | ||||
|     path("-/user/", user.UserSettingsView.as_view(), name="user-settings"), | ||||
|     path("-/user/details/", user.UserDetailsView.as_view(), name="user-details"), | ||||
|     path("-/user/tokens/", user.TokenListView.as_view(), name="user-tokens"), | ||||
|     path( | ||||
|         "-/user/tokens/create/", | ||||
|         user.TokenCreateView.as_view(), | ||||
| @ -24,8 +23,6 @@ urlpatterns = [ | ||||
|         user.TokenDeleteView.as_view(), | ||||
|         name="user-tokens-delete", | ||||
|     ), | ||||
|     # Libray | ||||
|     path("library", library.LibraryView.as_view(), name="overview"), | ||||
|     # Impersonation | ||||
|     path( | ||||
|         "-/impersonation/<int:user_id>/", | ||||
|  | ||||
| @ -1,23 +0,0 @@ | ||||
| """authentik library view""" | ||||
|  | ||||
| from django.contrib.auth.mixins import LoginRequiredMixin | ||||
| from django.views.generic import TemplateView | ||||
|  | ||||
| from authentik.core.models import Application | ||||
| from authentik.policies.engine import PolicyEngine | ||||
|  | ||||
|  | ||||
| class LibraryView(LoginRequiredMixin, TemplateView): | ||||
|     """Overview for logged in user, incase user opens authentik directly | ||||
|     and is not being forwarded""" | ||||
|  | ||||
|     template_name = "library.html" | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         kwargs["applications"] = [] | ||||
|         for application in Application.objects.all().order_by("name"): | ||||
|             engine = PolicyEngine(application, self.request.user, self.request) | ||||
|             engine.build() | ||||
|             if engine.passing: | ||||
|                 kwargs["applications"].append(application) | ||||
|         return super().get_context_data(**kwargs) | ||||
| @ -1,25 +1,19 @@ | ||||
| """authentik core user views""" | ||||
| from typing import Any, Dict | ||||
| 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.query import QuerySet | ||||
| from django.http.response import HttpResponse | ||||
| from django.urls import reverse_lazy | ||||
| from django.utils.translation import gettext as _ | ||||
| from django.views.generic import ListView, UpdateView | ||||
| from django.views.generic import UpdateView | ||||
| from django.views.generic.base import TemplateView | ||||
| from guardian.mixins import PermissionListMixin, PermissionRequiredMixin | ||||
| from guardian.mixins import PermissionRequiredMixin | ||||
| from guardian.shortcuts import get_objects_for_user | ||||
|  | ||||
| from authentik.admin.views.utils import ( | ||||
|     DeleteMessageView, | ||||
|     SearchListMixin, | ||||
|     UserPaginateListMixin, | ||||
| ) | ||||
| from authentik.admin.views.utils import DeleteMessageView | ||||
| from authentik.core.forms.token import UserTokenForm | ||||
| from authentik.core.forms.users import UserDetailForm | ||||
| from authentik.core.models import Token, TokenIntents | ||||
| @ -40,12 +34,12 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView): | ||||
|     form_class = UserDetailForm | ||||
|  | ||||
|     success_message = _("Successfully updated user.") | ||||
|     success_url = reverse_lazy("authentik_core:user-details") | ||||
|     success_url = "/" | ||||
|  | ||||
|     def get_object(self): | ||||
|         return self.request.user | ||||
|  | ||||
|     def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: | ||||
|     def get_context_data(self, **kwargs: Any) -> dict[str, Any]: | ||||
|         kwargs = super().get_context_data(**kwargs) | ||||
|         unenrollment_flow = Flow.with_policy( | ||||
|             self.request, designation=FlowDesignation.UNRENOLLMENT | ||||
| @ -54,30 +48,6 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView): | ||||
|         return kwargs | ||||
|  | ||||
|  | ||||
| class TokenListView( | ||||
|     LoginRequiredMixin, | ||||
|     PermissionListMixin, | ||||
|     UserPaginateListMixin, | ||||
|     SearchListMixin, | ||||
|     ListView, | ||||
| ): | ||||
|     """Show list of all tokens""" | ||||
|  | ||||
|     model = Token | ||||
|     ordering = "expires" | ||||
|     permission_required = "authentik_core.view_token" | ||||
|  | ||||
|     template_name = "user/token_list.html" | ||||
|     search_fields = [ | ||||
|         "identifier", | ||||
|         "intent", | ||||
|         "description", | ||||
|     ] | ||||
|  | ||||
|     def get_queryset(self) -> QuerySet: | ||||
|         return super().get_queryset().filter(intent=TokenIntents.INTENT_API) | ||||
|  | ||||
|  | ||||
| class TokenCreateView( | ||||
|     SuccessMessageMixin, | ||||
|     LoginRequiredMixin, | ||||
| @ -91,7 +61,7 @@ class TokenCreateView( | ||||
|     permission_required = "authentik_core.add_token" | ||||
|  | ||||
|     template_name = "generic/create.html" | ||||
|     success_url = reverse_lazy("authentik_core:user-tokens") | ||||
|     success_url = "/" | ||||
|     success_message = _("Successfully created Token") | ||||
|  | ||||
|     def form_valid(self, form: UserTokenForm) -> HttpResponse: | ||||
| @ -109,7 +79,7 @@ class TokenUpdateView( | ||||
|     form_class = UserTokenForm | ||||
|     permission_required = "authentik_core.change_token" | ||||
|     template_name = "generic/update.html" | ||||
|     success_url = reverse_lazy("authentik_core:user-tokens") | ||||
|     success_url = "/" | ||||
|     success_message = _("Successfully updated Token") | ||||
|  | ||||
|     def get_object(self) -> Token: | ||||
| @ -129,7 +99,7 @@ class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage | ||||
|     model = Token | ||||
|     permission_required = "authentik_core.delete_token" | ||||
|     template_name = "generic/delete.html" | ||||
|     success_url = reverse_lazy("authentik_core:user-tokens") | ||||
|     success_url = "/" | ||||
|     success_message = _("Successfully deleted Token") | ||||
|  | ||||
|     def get_object(self) -> Token: | ||||
|  | ||||
| @ -2,15 +2,34 @@ | ||||
| from cryptography.hazmat.backends import default_backend | ||||
| from cryptography.hazmat.primitives.serialization import load_pem_private_key | ||||
| from cryptography.x509 import load_pem_x509_certificate | ||||
| from rest_framework.serializers import ModelSerializer, ValidationError | ||||
| from django.db.models import Model | ||||
| from drf_yasg2.utils import swagger_auto_schema | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.fields import CharField, DateTimeField, SerializerMethodField | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ModelSerializer, Serializer, ValidationError | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
|  | ||||
| from authentik.crypto.models import CertificateKeyPair | ||||
| from authentik.events.models import Event, EventAction | ||||
|  | ||||
|  | ||||
| class CertificateKeyPairSerializer(ModelSerializer): | ||||
|     """CertificateKeyPair Serializer""" | ||||
|  | ||||
|     cert_expiry = DateTimeField(source="certificate.not_valid_after", read_only=True) | ||||
|     cert_subject = SerializerMethodField() | ||||
|     private_key_available = SerializerMethodField() | ||||
|  | ||||
|     def get_cert_subject(self, instance: CertificateKeyPair) -> str: | ||||
|         """Get certificate subject as full rfc4514""" | ||||
|         return instance.certificate.subject.rfc4514_string() | ||||
|  | ||||
|     def get_private_key_available(self, instance: CertificateKeyPair) -> bool: | ||||
|         """Show if this keypair has a private key configured or not""" | ||||
|         return instance.key_data != "" and instance.key_data is not None | ||||
|  | ||||
|     def validate_certificate_data(self, value): | ||||
|         """Verify that input is a valid PEM x509 Certificate""" | ||||
|         try: | ||||
| @ -36,7 +55,32 @@ class CertificateKeyPairSerializer(ModelSerializer): | ||||
|     class Meta: | ||||
|  | ||||
|         model = CertificateKeyPair | ||||
|         fields = ["pk", "name", "certificate_data", "key_data"] | ||||
|         fields = [ | ||||
|             "pk", | ||||
|             "name", | ||||
|             "fingerprint", | ||||
|             "certificate_data", | ||||
|             "key_data", | ||||
|             "cert_expiry", | ||||
|             "cert_subject", | ||||
|             "private_key_available", | ||||
|         ] | ||||
|         extra_kwargs = { | ||||
|             "key_data": {"write_only": True}, | ||||
|             "certificate_data": {"write_only": True}, | ||||
|         } | ||||
|  | ||||
|  | ||||
| class CertificateDataSerializer(Serializer): | ||||
|     """Get CertificateKeyPair's data""" | ||||
|  | ||||
|     data = CharField(read_only=True) | ||||
|  | ||||
|     def create(self, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def update(self, instance: Model, validated_data: dict) -> Model: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|  | ||||
| class CertificateKeyPairViewSet(ModelViewSet): | ||||
| @ -44,3 +88,31 @@ class CertificateKeyPairViewSet(ModelViewSet): | ||||
|  | ||||
|     queryset = CertificateKeyPair.objects.all() | ||||
|     serializer_class = CertificateKeyPairSerializer | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: CertificateDataSerializer(many=False)}) | ||||
|     @action(detail=True) | ||||
|     # pylint: disable=invalid-name, unused-argument | ||||
|     def view_certificate(self, request: Request, pk: str) -> Response: | ||||
|         """Return certificate-key pairs certificate and log access""" | ||||
|         certificate: CertificateKeyPair = self.get_object() | ||||
|         Event.new(  # noqa # nosec | ||||
|             EventAction.SECRET_VIEW, | ||||
|             secret=certificate, | ||||
|             type="certificate", | ||||
|         ).from_http(request) | ||||
|         return Response( | ||||
|             CertificateDataSerializer({"data": certificate.certificate_data}).data | ||||
|         ) | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: CertificateDataSerializer(many=False)}) | ||||
|     @action(detail=True) | ||||
|     # pylint: disable=invalid-name, unused-argument | ||||
|     def view_private_key(self, request: Request, pk: str) -> Response: | ||||
|         """Return certificate-key pairs private key and log access""" | ||||
|         certificate: CertificateKeyPair = self.get_object() | ||||
|         Event.new(  # noqa # nosec | ||||
|             EventAction.SECRET_VIEW, | ||||
|             secret=certificate, | ||||
|             type="private_key", | ||||
|         ).from_http(request) | ||||
|         return Response(CertificateDataSerializer({"data": certificate.key_data}).data) | ||||
|  | ||||
| @ -29,7 +29,7 @@ class EventSerializer(ModelSerializer): | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class EventTopPerUserSerialier(Serializer): | ||||
| class EventTopPerUserSerializer(Serializer): | ||||
|     """Response object of Event's top_per_user""" | ||||
|  | ||||
|     application = DictField() | ||||
| @ -60,7 +60,7 @@ class EventViewSet(ReadOnlyModelViewSet): | ||||
|     filterset_fields = ["action"] | ||||
|  | ||||
|     @swagger_auto_schema( | ||||
|         method="GET", responses={200: EventTopPerUserSerialier(many=True)} | ||||
|         method="GET", responses={200: EventTopPerUserSerializer(many=True)} | ||||
|     ) | ||||
|     @action(detail=False, methods=["GET"]) | ||||
|     def top_per_user(self, request: Request): | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	