Compare commits
	
		
			96 Commits
		
	
	
		
			version/20
			...
			version/20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fe6963c428 | |||
| 19cac4bf43 | |||
| 4ca564490e | |||
| fcb795c273 | |||
| 14c70b3e4a | |||
| ac880c28d7 | |||
| f3c6b9a4f6 | |||
| cba0cf0d76 | |||
| 73b67cf0f0 | |||
| 23a8052cc8 | |||
| 57c49c3865 | |||
| cbea51ae5b | |||
| 8962081d92 | |||
| e743f13f81 | |||
| b20a8b7c17 | |||
| b53c94d76a | |||
| d4419d66c1 | |||
| 79044368d2 | |||
| 426686957d | |||
| 28cb803fd9 | |||
| 85c3a36b62 | |||
| 9ba8a715b1 | |||
| 358750f66e | |||
| b9918529b8 | |||
| a5673b4ec8 | |||
| d9287d0c0e | |||
| d9c2b64116 | |||
| 2b150d3077 | |||
| dec7a9cfb9 | |||
| e0f48a30b7 | |||
| 973f14d911 | |||
| e8978adc1b | |||
| 3ca8d9c968 | |||
| 42636142fa | |||
| 57c459348f | |||
| 493b34cf0d | |||
| f0493f418b | |||
| d45a292652 | |||
| b21ea360db | |||
| 6816f8b851 | |||
| de714f0390 | |||
| 800df332b5 | |||
| 16c194d2dc | |||
| 53100a72fe | |||
| ec4c3f44cb | |||
| f10bd432b3 | |||
| 4de927ba5b | |||
| 74e578c2bf | |||
| e584fd1344 | |||
| 0e02925a3d | |||
| 5b837c3ccc | |||
| 2580371f94 | |||
| 4e9be85353 | |||
| 79508e1965 | |||
| 3a88dde545 | |||
| 31fc4d1cb9 | |||
| 09cd8f8f63 | |||
| d824b09365 | |||
| cabbd18880 | |||
| c9dda17c68 | |||
| bb8559ee18 | |||
| 5ae32e525c | |||
| 0832145a01 | |||
| 4167276c8f | |||
| afb84c7bc5 | |||
| 82b2c7e3f0 | |||
| fc8004db2b | |||
| ddfc943bba | |||
| 8c0c12292e | |||
| 803490d98b | |||
| 16835ab478 | |||
| 572b8d87b5 | |||
| 31d2ea65fd | |||
| f4ac2f50e2 | |||
| 969a3f0ddd | |||
| 4e18f47f28 | |||
| f10286edf8 | |||
| d789dcc28f | |||
| 715a71427e | |||
| 84c21d16cf | |||
| 2e4e17adb7 | |||
| 00cbaaf672 | |||
| 74e4e8f6aa | |||
| d78fda990a | |||
| 10d949f7a9 | |||
| 6661af032d | |||
| fb5e4a3af8 | |||
| 1dfad83a34 | |||
| 70025c648c | |||
| 676b77aa7c | |||
| e35e096266 | |||
| 7af12d4fec | |||
| 8d6db0fabf | |||
| 8ddcf99bf7 | |||
| e25f6aea8c | |||
| b1a9eda1d3 | 
| @ -1,5 +1,5 @@ | ||||
| [bumpversion] | ||||
| current_version = 2021.6.1-rc3 | ||||
| current_version = 2021.6.1 | ||||
| tag = True | ||||
| commit = True | ||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*) | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| env | ||||
| helm | ||||
| static | ||||
| htmlcov | ||||
| *.env.yml | ||||
|  | ||||
							
								
								
									
										44
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										44
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @ -33,12 +33,22 @@ jobs: | ||||
|         with: | ||||
|           push: ${{ github.event_name == 'release' }} | ||||
|           tags: | | ||||
|             beryju/authentik:2021.6.1-rc3, | ||||
|             beryju/authentik:2021.6.1, | ||||
|             beryju/authentik:latest, | ||||
|             ghcr.io/goauthentik/server:2021.6.1-rc3, | ||||
|             ghcr.io/goauthentik/server:2021.6.1, | ||||
|             ghcr.io/goauthentik/server:latest | ||||
|           platforms: linux/amd64,linux/arm64 | ||||
|           context: . | ||||
|       - name: Building Docker Image (stable) | ||||
|         uses: docker/build-push-action@v2 | ||||
|         if: ${{ github.event_name == 'release' && !contains('2021.6.1', 'rc') }} | ||||
|         with: | ||||
|           push: true | ||||
|           tags: | | ||||
|             beryju/authentik:stable, | ||||
|             ghcr.io/goauthentik/server:stable | ||||
|           platforms: linux/amd64,linux/arm64 | ||||
|           context: . | ||||
|   build-proxy: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
| @ -66,12 +76,22 @@ jobs: | ||||
|         with: | ||||
|           push: ${{ github.event_name == 'release' }} | ||||
|           tags: | | ||||
|             beryju/authentik-proxy:2021.6.1-rc3, | ||||
|             beryju/authentik-proxy:2021.6.1, | ||||
|             beryju/authentik-proxy:latest, | ||||
|             ghcr.io/goauthentik/proxy:2021.6.1-rc3, | ||||
|             ghcr.io/goauthentik/proxy:2021.6.1, | ||||
|             ghcr.io/goauthentik/proxy:latest | ||||
|           file: outpost/proxy.Dockerfile | ||||
|           platforms: linux/amd64,linux/arm64 | ||||
|       - name: Building Docker Image (stable) | ||||
|         uses: docker/build-push-action@v2 | ||||
|         if: ${{ github.event_name == 'release' && !contains('2021.6.1', 'rc') }} | ||||
|         with: | ||||
|           push: true | ||||
|           tags: | | ||||
|             beryju/authentik-proxy:stable, | ||||
|             ghcr.io/goauthentik/proxy:stable | ||||
|           platforms: linux/amd64,linux/arm64 | ||||
|           context: . | ||||
|   build-ldap: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
| @ -99,12 +119,22 @@ jobs: | ||||
|         with: | ||||
|           push: ${{ github.event_name == 'release' }} | ||||
|           tags: | | ||||
|             beryju/authentik-ldap:2021.6.1-rc3, | ||||
|             beryju/authentik-ldap:2021.6.1, | ||||
|             beryju/authentik-ldap:latest, | ||||
|             ghcr.io/goauthentik/ldap:2021.6.1-rc3, | ||||
|             ghcr.io/goauthentik/ldap:2021.6.1, | ||||
|             ghcr.io/goauthentik/ldap:latest | ||||
|           file: outpost/ldap.Dockerfile | ||||
|           platforms: linux/amd64,linux/arm64 | ||||
|       - name: Building Docker Image (stable) | ||||
|         uses: docker/build-push-action@v2 | ||||
|         if: ${{ github.event_name == 'release' && !contains('2021.6.1', 'rc') }} | ||||
|         with: | ||||
|           push: true | ||||
|           tags: | | ||||
|             beryju/authentik-ldap:stable, | ||||
|             ghcr.io/goauthentik/ldap:stable | ||||
|           platforms: linux/amd64,linux/arm64 | ||||
|           context: . | ||||
|   test-release: | ||||
|     if: ${{ github.event_name == 'release' }} | ||||
|     needs: | ||||
| @ -138,5 +168,5 @@ jobs: | ||||
|           SENTRY_PROJECT: authentik | ||||
|           SENTRY_URL: https://sentry.beryju.org | ||||
|         with: | ||||
|           version: authentik@2021.6.1-rc3 | ||||
|           version: authentik@2021.6.1 | ||||
|           environment: beryjuorg-prod | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/workflows/tag.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/tag.yml
									
									
									
									
										vendored
									
									
								
							| @ -20,7 +20,7 @@ jobs: | ||||
|           docker-compose pull -q | ||||
|           docker build \ | ||||
|             --no-cache \ | ||||
|             -t beryju/authentik:latest \ | ||||
|             -t ghcr.io/goauthentik/server:latest \ | ||||
|             -f Dockerfile . | ||||
|           docker-compose up --no-start | ||||
|           docker-compose start postgresql redis | ||||
|  | ||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -193,10 +193,6 @@ pip-selfcheck.json | ||||
| local.env.yml | ||||
| .vscode/ | ||||
|  | ||||
| ### Helm ### | ||||
| # Chart dependencies | ||||
| **/charts/*.tgz | ||||
|  | ||||
| # Selenium Screenshots | ||||
| selenium_screenshots/ | ||||
| backups/ | ||||
|  | ||||
							
								
								
									
										183
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										183
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							| @ -56,6 +56,7 @@ | ||||
|                 "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a", | ||||
|                 "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==3.7.4.post0" | ||||
|         }, | ||||
|         "aioredis": { | ||||
| @ -70,6 +71,7 @@ | ||||
|                 "sha256:03e16e94f2b34c31f8bf1206d8ddd3ccaa4c315f7f6a1879b7b1210d229568c2", | ||||
|                 "sha256:493a2ac6788ce270a2f6a765b017299f60c1998f5a8617908ee9be082f7300fb" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==5.0.6" | ||||
|         }, | ||||
|         "asgiref": { | ||||
| @ -77,6 +79,7 @@ | ||||
|                 "sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee", | ||||
|                 "sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==3.3.4" | ||||
|         }, | ||||
|         "async-timeout": { | ||||
| @ -84,6 +87,7 @@ | ||||
|                 "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", | ||||
|                 "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" | ||||
|             ], | ||||
|             "markers": "python_full_version >= '3.5.3'", | ||||
|             "version": "==3.0.1" | ||||
|         }, | ||||
|         "attrs": { | ||||
| @ -91,6 +95,7 @@ | ||||
|                 "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", | ||||
|                 "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", | ||||
|             "version": "==21.2.0" | ||||
|         }, | ||||
|         "autobahn": { | ||||
| @ -98,6 +103,7 @@ | ||||
|                 "sha256:9195df8af03b0ff29ccd4b7f5abbde957ee90273465942205f9a1bad6c3f07ac", | ||||
|                 "sha256:e126c1f583e872fb59e79d36977cfa1f2d0a8a79f90ae31f406faae7664b8e03" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.7'", | ||||
|             "version": "==21.3.1" | ||||
|         }, | ||||
|         "automat": { | ||||
| @ -116,23 +122,26 @@ | ||||
|         }, | ||||
|         "boto3": { | ||||
|             "hashes": [ | ||||
|                 "sha256:4cfab400cd9ca9b27b7dffb43f5675525ea5d36c81223d64d15542fdb16cdf7e", | ||||
|                 "sha256:b0808a58c54c595b6cc6271cbc14a09bb89f0951ca9e8b105d1e94bef3ed24a0" | ||||
|                 "sha256:67a4b0578944f061fbfa05206eb5b10c5250374e9849743413739c539584b60e", | ||||
|                 "sha256:c7d6f3f09081440ca80500e679fec19f0b7597648ee380ae940ed29ad5c3768f" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==1.17.91" | ||||
|             "version": "==1.17.96" | ||||
|         }, | ||||
|         "botocore": { | ||||
|             "hashes": [ | ||||
|                 "sha256:462e75419e6537efb2709b7eb5b8c7ade007d30209416f0476bd7c51a2ddc78d" | ||||
|                 "sha256:204f7403bfe1ab837784421ddd069fd880be99d946cb59cbf31c72296ea9507a", | ||||
|                 "sha256:b18d2d016b371b769a88cb080088ce75582748b4a7efa5748e9ced4f23bdbc99" | ||||
|             ], | ||||
|             "version": "==1.20.91" | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", | ||||
|             "version": "==1.20.96" | ||||
|         }, | ||||
|         "cachetools": { | ||||
|             "hashes": [ | ||||
|                 "sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001", | ||||
|                 "sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff" | ||||
|             ], | ||||
|             "markers": "python_version ~= '3.5'", | ||||
|             "version": "==4.2.2" | ||||
|         }, | ||||
|         "cbor2": { | ||||
| @ -151,6 +160,7 @@ | ||||
|                 "sha256:ce6219986385778b1ab7f9b542f160bb4d3558f52975e914a27b774e47016fb7", | ||||
|                 "sha256:d562b2773e14ee1d65ea5b85351a83a64d4f3fd011bc2b4c70a6e813e78203ce" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==5.4.0" | ||||
|         }, | ||||
|         "celery": { | ||||
| @ -243,6 +253,7 @@ | ||||
|                 "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", | ||||
|                 "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", | ||||
|             "version": "==4.0.0" | ||||
|         }, | ||||
|         "click": { | ||||
| @ -250,6 +261,7 @@ | ||||
|                 "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", | ||||
|                 "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", | ||||
|             "version": "==7.1.2" | ||||
|         }, | ||||
|         "click-didyoumean": { | ||||
| @ -309,6 +321,7 @@ | ||||
|                 "sha256:76ffae916ba3aa66b46996c14fa713e46004788167a4873d647544e750e0e99f", | ||||
|                 "sha256:a9af943c79717bc52fe64a3c236ae5d3adccc8b5be19c881b442d2c3db233393" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==3.0.2" | ||||
|         }, | ||||
|         "defusedxml": { | ||||
| @ -413,11 +426,11 @@ | ||||
|         }, | ||||
|         "drf-spectacular": { | ||||
|             "hashes": [ | ||||
|                 "sha256:4d35e890b8139e1c056588c5529a2f2066615635482563f0840b96d3b879d7d2", | ||||
|                 "sha256:f552476dfde647963c21615249672e7f4f9ece3788036b5ee5c6cc5ad50748ab" | ||||
|                 "sha256:6ffbfde7d96a4a2febd19182cc405217e1e86a50280fc739402291c93d1a32b7", | ||||
|                 "sha256:77593024bb899f69227abedcf87def7851a11c9978f781aa4b385a10f67a38b7" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==0.17.0" | ||||
|             "version": "==0.17.2" | ||||
|         }, | ||||
|         "duo-client": { | ||||
|             "hashes": [ | ||||
| @ -439,6 +452,7 @@ | ||||
|             "hashes": [ | ||||
|                 "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||||
|             "version": "==0.18.2" | ||||
|         }, | ||||
|         "geoip2": { | ||||
| @ -451,10 +465,11 @@ | ||||
|         }, | ||||
|         "google-auth": { | ||||
|             "hashes": [ | ||||
|                 "sha256:9b235dbc876e49454cbedc52ae0abd540ef705ebccdf4fbe93553bb13f26b1a4", | ||||
|                 "sha256:eb017521276a75492282c6ca4b718f26de112ed3bcbeaeeb02c1b82de425f909" | ||||
|                 "sha256:154f7889c5d679a6f626f36adb12afbd4dbb0a9a04ec575d989d6ba79c4fd65e", | ||||
|                 "sha256:6d47c79b5d09fbc7e8355fd9594cc4cf65fdde5d401c63951eaac4baa1ba2ae1" | ||||
|             ], | ||||
|             "version": "==1.30.2" | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", | ||||
|             "version": "==1.31.0" | ||||
|         }, | ||||
|         "gunicorn": { | ||||
|             "hashes": [ | ||||
| @ -469,6 +484,7 @@ | ||||
|                 "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6", | ||||
|                 "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==0.12.0" | ||||
|         }, | ||||
|         "hiredis": { | ||||
| @ -515,6 +531,7 @@ | ||||
|                 "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0", | ||||
|                 "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==2.0.0" | ||||
|         }, | ||||
|         "httptools": { | ||||
| @ -563,6 +580,7 @@ | ||||
|                 "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", | ||||
|                 "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.5'", | ||||
|             "version": "==0.5.1" | ||||
|         }, | ||||
|         "jmespath": { | ||||
| @ -570,6 +588,7 @@ | ||||
|                 "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", | ||||
|                 "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||||
|             "version": "==0.10.0" | ||||
|         }, | ||||
|         "jsonschema": { | ||||
| @ -584,6 +603,7 @@ | ||||
|                 "sha256:01481d99f4606f6939cdc9b637264ed353ee9e3e4f62cfb582324142c41a572d", | ||||
|                 "sha256:e2dedd8a86c9077c350555153825a31e456a0dc20c15d5751f00137ec9c75f0a" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==5.1.0" | ||||
|         }, | ||||
|         "kubernetes": { | ||||
| @ -597,6 +617,9 @@ | ||||
|         "ldap3": { | ||||
|             "hashes": [ | ||||
|                 "sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91", | ||||
|                 "sha256:4139c91f0eef9782df7b77c8cbc6243086affcb6a8a249b768a9658438e5da59", | ||||
|                 "sha256:8c949edbad2be8a03e719ba48bd6779f327ec156929562814b3e84ab56889c8c", | ||||
|                 "sha256:afc6fc0d01f02af82cd7bfabd3bbfd5dc96a6ae91e97db0a2dab8a0f1b436056", | ||||
|                 "sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
| @ -658,6 +681,7 @@ | ||||
|             "hashes": [ | ||||
|                 "sha256:47e86a084dd814fac88c99ea34ba3278a74bc9de5a25f4b815b608798747c7dc" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==2.0.3" | ||||
|         }, | ||||
|         "msgpack": { | ||||
| @ -733,6 +757,7 @@ | ||||
|                 "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281", | ||||
|                 "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==5.1.0" | ||||
|         }, | ||||
|         "oauthlib": { | ||||
| @ -740,6 +765,7 @@ | ||||
|                 "sha256:42bf6354c2ed8c6acb54d971fce6f88193d97297e18602a3a886603f9d7730cc", | ||||
|                 "sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==3.1.1" | ||||
|         }, | ||||
|         "packaging": { | ||||
| @ -755,6 +781,7 @@ | ||||
|                 "sha256:3a8baade6cb80bcfe43297e33e7623f3118d660d41387593758e2fb1ea173a86", | ||||
|                 "sha256:b014bc76815eb1399da8ce5fc84b7717a3e63652b0c0f8804092c9363acab1b2" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||||
|             "version": "==0.11.0" | ||||
|         }, | ||||
|         "prompt-toolkit": { | ||||
| @ -762,60 +789,77 @@ | ||||
|                 "sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04", | ||||
|                 "sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc" | ||||
|             ], | ||||
|             "markers": "python_full_version >= '3.6.1'", | ||||
|             "version": "==3.0.18" | ||||
|         }, | ||||
|         "psycopg2-binary": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c", | ||||
|                 "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67", | ||||
|                 "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0", | ||||
|                 "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6", | ||||
|                 "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db", | ||||
|                 "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94", | ||||
|                 "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52", | ||||
|                 "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056", | ||||
|                 "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b", | ||||
|                 "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd", | ||||
|                 "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550", | ||||
|                 "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679", | ||||
|                 "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83", | ||||
|                 "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77", | ||||
|                 "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2", | ||||
|                 "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77", | ||||
|                 "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2", | ||||
|                 "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd", | ||||
|                 "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859", | ||||
|                 "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1", | ||||
|                 "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25", | ||||
|                 "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152", | ||||
|                 "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf", | ||||
|                 "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f", | ||||
|                 "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729", | ||||
|                 "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71", | ||||
|                 "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66", | ||||
|                 "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4", | ||||
|                 "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449", | ||||
|                 "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da", | ||||
|                 "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a", | ||||
|                 "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c", | ||||
|                 "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb", | ||||
|                 "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4", | ||||
|                 "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5" | ||||
|                 "sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975", | ||||
|                 "sha256:0f2e04bd2a2ab54fa44ee67fe2d002bb90cee1c0f1cc0ebc3148af7b02034cbd", | ||||
|                 "sha256:123c3fb684e9abfc47218d3784c7b4c47c8587951ea4dd5bc38b6636ac57f616", | ||||
|                 "sha256:1473c0215b0613dd938db54a653f68251a45a78b05f6fc21af4326f40e8360a2", | ||||
|                 "sha256:14db1752acdd2187d99cb2ca0a1a6dfe57fc65c3281e0f20e597aac8d2a5bd90", | ||||
|                 "sha256:1e3a362790edc0a365385b1ac4cc0acc429a0c0d662d829a50b6ce743ae61b5a", | ||||
|                 "sha256:1e85b74cbbb3056e3656f1cc4781294df03383127a8114cbc6531e8b8367bf1e", | ||||
|                 "sha256:20f1ab44d8c352074e2d7ca67dc00843067788791be373e67a0911998787ce7d", | ||||
|                 "sha256:2f62c207d1740b0bde5c4e949f857b044818f734a3d57f1d0d0edc65050532ed", | ||||
|                 "sha256:3242b9619de955ab44581a03a64bdd7d5e470cc4183e8fcadd85ab9d3756ce7a", | ||||
|                 "sha256:35c4310f8febe41f442d3c65066ca93cccefd75013df3d8c736c5b93ec288140", | ||||
|                 "sha256:4235f9d5ddcab0b8dbd723dca56ea2922b485ea00e1dafacf33b0c7e840b3d32", | ||||
|                 "sha256:5ced67f1e34e1a450cdb48eb53ca73b60aa0af21c46b9b35ac3e581cf9f00e31", | ||||
|                 "sha256:7360647ea04db2e7dff1648d1da825c8cf68dc5fbd80b8fb5b3ee9f068dcd21a", | ||||
|                 "sha256:8c13d72ed6af7fd2c8acbd95661cf9477f94e381fce0792c04981a8283b52917", | ||||
|                 "sha256:988b47ac70d204aed01589ed342303da7c4d84b56c2f4c4b8b00deda123372bf", | ||||
|                 "sha256:995fc41ebda5a7a663a254a1dcac52638c3e847f48307b5416ee373da15075d7", | ||||
|                 "sha256:a36c7eb6152ba5467fb264d73844877be8b0847874d4822b7cf2d3c0cb8cdcb0", | ||||
|                 "sha256:aed4a9a7e3221b3e252c39d0bf794c438dc5453bc2963e8befe9d4cd324dff72", | ||||
|                 "sha256:aef9aee84ec78af51107181d02fe8773b100b01c5dfde351184ad9223eab3698", | ||||
|                 "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773", | ||||
|                 "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68", | ||||
|                 "sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76", | ||||
|                 "sha256:ca86db5b561b894f9e5f115d6a159fff2a2570a652e07889d8a383b5fae66eb4", | ||||
|                 "sha256:cfc523edecddaef56f6740d7de1ce24a2fdf94fd5e704091856a201872e37f9f", | ||||
|                 "sha256:da113b70f6ec40e7d81b43d1b139b9db6a05727ab8be1ee559f3a69854a69d34", | ||||
|                 "sha256:f6fac64a38f6768e7bc7b035b9e10d8a538a9fadce06b983fb3e6fa55ac5f5ce", | ||||
|                 "sha256:f8559617b1fcf59a9aedba2c9838b5b6aa211ffedecabca412b92a1ff75aac1a", | ||||
|                 "sha256:fbb42a541b1093385a2d8c7eec94d26d30437d0e77c1d25dae1dcc46741a385e" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==2.8.6" | ||||
|             "version": "==2.9.1" | ||||
|         }, | ||||
|         "pyasn1": { | ||||
|             "hashes": [ | ||||
|                 "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", | ||||
|                 "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", | ||||
|                 "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", | ||||
|                 "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", | ||||
|                 "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", | ||||
|                 "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba" | ||||
|                 "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", | ||||
|                 "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", | ||||
|                 "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", | ||||
|                 "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", | ||||
|                 "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", | ||||
|                 "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", | ||||
|                 "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", | ||||
|                 "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" | ||||
|             ], | ||||
|             "version": "==0.4.8" | ||||
|         }, | ||||
|         "pyasn1-modules": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8", | ||||
|                 "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199", | ||||
|                 "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811", | ||||
|                 "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed", | ||||
|                 "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4", | ||||
|                 "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", | ||||
|                 "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74" | ||||
|                 "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74", | ||||
|                 "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb", | ||||
|                 "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45", | ||||
|                 "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd", | ||||
|                 "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0", | ||||
|                 "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d", | ||||
|                 "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405" | ||||
|             ], | ||||
|             "version": "==0.2.8" | ||||
|         }, | ||||
| @ -824,6 +868,7 @@ | ||||
|                 "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", | ||||
|                 "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||||
|             "version": "==2.20" | ||||
|         }, | ||||
|         "pycryptodome": { | ||||
| @ -867,6 +912,7 @@ | ||||
|                 "sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316", | ||||
|                 "sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.5'", | ||||
|             "version": "==2.0.2" | ||||
|         }, | ||||
|         "pyjwt": { | ||||
| @ -889,12 +935,14 @@ | ||||
|                 "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", | ||||
|                 "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||||
|             "version": "==2.4.7" | ||||
|         }, | ||||
|         "pyrsistent": { | ||||
|             "hashes": [ | ||||
|                 "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.5'", | ||||
|             "version": "==0.17.3" | ||||
|         }, | ||||
|         "python-dateutil": { | ||||
| @ -902,6 +950,7 @@ | ||||
|                 "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", | ||||
|                 "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||||
|             "version": "==2.8.1" | ||||
|         }, | ||||
|         "python-dotenv": { | ||||
| @ -958,6 +1007,7 @@ | ||||
|                 "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2", | ||||
|                 "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", | ||||
|             "version": "==3.5.3" | ||||
|         }, | ||||
|         "requests": { | ||||
| @ -965,12 +1015,14 @@ | ||||
|                 "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", | ||||
|                 "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", | ||||
|             "version": "==2.25.1" | ||||
|         }, | ||||
|         "requests-oauthlib": { | ||||
|             "hashes": [ | ||||
|                 "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", | ||||
|                 "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a" | ||||
|                 "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a", | ||||
|                 "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==1.3.0" | ||||
| @ -1011,6 +1063,7 @@ | ||||
|                 "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", | ||||
|                 "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||||
|             "version": "==1.16.0" | ||||
|         }, | ||||
|         "sqlparse": { | ||||
| @ -1018,6 +1071,7 @@ | ||||
|                 "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", | ||||
|                 "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.5'", | ||||
|             "version": "==0.4.1" | ||||
|         }, | ||||
|         "structlog": { | ||||
| @ -1073,6 +1127,7 @@ | ||||
|                 "sha256:7d6f89745680233f1c4db9ddb748df5e88d2a7a37962be174c0fd04c8dba1dc8", | ||||
|                 "sha256:c16b55f9a67b2419cfdf8846576e2ec9ba94fe6978a83080c352a80db31c93fb" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==21.2.1" | ||||
|         }, | ||||
|         "typing-extensions": { | ||||
| @ -1096,6 +1151,7 @@ | ||||
|                 "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f", | ||||
|                 "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||||
|             "version": "==3.0.1" | ||||
|         }, | ||||
|         "urllib3": { | ||||
| @ -1140,6 +1196,7 @@ | ||||
|                 "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30", | ||||
|                 "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==5.0.0" | ||||
|         }, | ||||
|         "watchgod": { | ||||
| @ -1169,6 +1226,7 @@ | ||||
|                 "sha256:b68e4959d704768fa20e35c9d508c8dc2bbc041fd8d267c0d7345cffe2824568", | ||||
|                 "sha256:e5c333bfa9fa739538b652b6f8c8fc2559f1d364243c8a689d7c0e1d41c2e611" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==1.1.0" | ||||
|         }, | ||||
|         "websockets": { | ||||
| @ -1266,6 +1324,7 @@ | ||||
|                 "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a", | ||||
|                 "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==1.6.3" | ||||
|         }, | ||||
|         "zope.interface": { | ||||
| @ -1322,6 +1381,7 @@ | ||||
|                 "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4", | ||||
|                 "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", | ||||
|             "version": "==5.4.0" | ||||
|         } | ||||
|     }, | ||||
| @ -1338,6 +1398,7 @@ | ||||
|                 "sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e", | ||||
|                 "sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975" | ||||
|             ], | ||||
|             "markers": "python_version ~= '3.6'", | ||||
|             "version": "==2.5.6" | ||||
|         }, | ||||
|         "attrs": { | ||||
| @ -1345,6 +1406,7 @@ | ||||
|                 "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", | ||||
|                 "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", | ||||
|             "version": "==21.2.0" | ||||
|         }, | ||||
|         "bandit": { | ||||
| @ -1383,6 +1445,7 @@ | ||||
|                 "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", | ||||
|                 "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", | ||||
|             "version": "==4.0.0" | ||||
|         }, | ||||
|         "click": { | ||||
| @ -1390,6 +1453,7 @@ | ||||
|                 "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", | ||||
|                 "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", | ||||
|             "version": "==7.1.2" | ||||
|         }, | ||||
|         "colorama": { | ||||
| @ -1463,14 +1527,16 @@ | ||||
|                 "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0", | ||||
|                 "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.4'", | ||||
|             "version": "==4.0.7" | ||||
|         }, | ||||
|         "gitpython": { | ||||
|             "hashes": [ | ||||
|                 "sha256:29fe82050709760081f588dd50ce83504feddbebdc4da6956d02351552b1c135", | ||||
|                 "sha256:ee24bdc93dce357630764db659edaf6b8d664d4ff5447ccfeedd2dc5c253f41e" | ||||
|                 "sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b", | ||||
|                 "sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61" | ||||
|             ], | ||||
|             "version": "==3.1.17" | ||||
|             "markers": "python_version >= '3.4'", | ||||
|             "version": "==3.1.14" | ||||
|         }, | ||||
|         "idna": { | ||||
|             "hashes": [ | ||||
| @ -1491,6 +1557,7 @@ | ||||
|                 "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6", | ||||
|                 "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6' and python_version < '4.0'", | ||||
|             "version": "==5.8.0" | ||||
|         }, | ||||
|         "lazy-object-proxy": { | ||||
| @ -1518,6 +1585,7 @@ | ||||
|                 "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93", | ||||
|                 "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", | ||||
|             "version": "==1.6.0" | ||||
|         }, | ||||
|         "mccabe": { | ||||
| @ -1554,6 +1622,7 @@ | ||||
|                 "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd", | ||||
|                 "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.6'", | ||||
|             "version": "==5.6.0" | ||||
|         }, | ||||
|         "pluggy": { | ||||
| @ -1561,6 +1630,7 @@ | ||||
|                 "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", | ||||
|                 "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||||
|             "version": "==0.13.1" | ||||
|         }, | ||||
|         "py": { | ||||
| @ -1568,6 +1638,7 @@ | ||||
|                 "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", | ||||
|                 "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||||
|             "version": "==1.10.0" | ||||
|         }, | ||||
|         "pylint": { | ||||
| @ -1598,6 +1669,7 @@ | ||||
|                 "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", | ||||
|                 "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||||
|             "version": "==2.4.7" | ||||
|         }, | ||||
|         "pytest": { | ||||
| @ -1702,6 +1774,7 @@ | ||||
|                 "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", | ||||
|                 "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", | ||||
|             "version": "==2.25.1" | ||||
|         }, | ||||
|         "requests-mock": { | ||||
| @ -1725,6 +1798,7 @@ | ||||
|                 "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", | ||||
|                 "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||||
|             "version": "==1.16.0" | ||||
|         }, | ||||
|         "smmap": { | ||||
| @ -1732,6 +1806,7 @@ | ||||
|                 "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182", | ||||
|                 "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.5'", | ||||
|             "version": "==4.0.0" | ||||
|         }, | ||||
|         "stevedore": { | ||||
| @ -1739,6 +1814,7 @@ | ||||
|                 "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee", | ||||
|                 "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a" | ||||
|             ], | ||||
|             "markers": "python_version >= '3.6'", | ||||
|             "version": "==3.3.0" | ||||
|         }, | ||||
|         "toml": { | ||||
| @ -1746,6 +1822,7 @@ | ||||
|                 "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", | ||||
|                 "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" | ||||
|             ], | ||||
|             "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||||
|             "version": "==0.10.2" | ||||
|         }, | ||||
|         "urllib3": { | ||||
|  | ||||
| @ -21,7 +21,7 @@ authentik is an open-source Identity Provider focused on flexibility and versati | ||||
|  | ||||
| For small/test setups it is recommended to use docker-compose, see the [documentation](https://goauthentik.io/docs/installation/docker-compose/) | ||||
|  | ||||
| For bigger setups, there is a Helm Chart in the `helm/` directory. This is documented [here](https://goauthentik.io/docs/installation/kubernetes/) | ||||
| For bigger setups, there is a Helm Chart [here])(https://github.com/goauthentik/helm). This is documented [here](https://goauthentik.io/docs/installation/kubernetes/) | ||||
|  | ||||
| ## Screenshots | ||||
|  | ||||
|  | ||||
| @ -1,3 +1,3 @@ | ||||
| """authentik""" | ||||
| __version__ = "2021.6.1-rc3" | ||||
| __version__ = "2021.6.1" | ||||
| ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ||||
|  | ||||
| @ -10,3 +10,25 @@ class AuthentikAPIConfig(AppConfig): | ||||
|     label = "authentik_api" | ||||
|     mountpoint = "api/" | ||||
|     verbose_name = "authentik API" | ||||
|  | ||||
|     def ready(self) -> None: | ||||
|         from drf_spectacular.extensions import OpenApiAuthenticationExtension | ||||
|  | ||||
|         from authentik.api.authentication import TokenAuthentication | ||||
|  | ||||
|         # Class is defined here as it needs to be created early enough that drf-spectacular will | ||||
|         # find it, but also won't cause any import issues | ||||
|         # pylint: disable=unused-variable | ||||
|         class TokenSchema(OpenApiAuthenticationExtension): | ||||
|             """Auth schema""" | ||||
|  | ||||
|             target_class = TokenAuthentication | ||||
|             name = "authentik" | ||||
|  | ||||
|             def get_security_definition(self, auto_schema): | ||||
|                 """Auth schema""" | ||||
|                 return { | ||||
|                     "type": "apiKey", | ||||
|                     "in": "header", | ||||
|                     "name": "Authorization", | ||||
|                 } | ||||
|  | ||||
| @ -3,7 +3,6 @@ from base64 import b64decode | ||||
| from binascii import Error | ||||
| from typing import Any, Optional, Union | ||||
|  | ||||
| from drf_spectacular.authentication import OpenApiAuthenticationExtension | ||||
| from rest_framework.authentication import BaseAuthentication, get_authorization_header | ||||
| from rest_framework.exceptions import AuthenticationFailed | ||||
| from rest_framework.request import Request | ||||
| @ -56,18 +55,3 @@ class TokenAuthentication(BaseAuthentication): | ||||
|             return None | ||||
|  | ||||
|         return (token.user, None)  # pragma: no cover | ||||
|  | ||||
|  | ||||
| class TokenSchema(OpenApiAuthenticationExtension): | ||||
|     """Auth schema""" | ||||
|  | ||||
|     target_class = TokenAuthentication | ||||
|     name = "authentik" | ||||
|  | ||||
|     def get_security_definition(self, auto_schema): | ||||
|         """Auth schema""" | ||||
|         return { | ||||
|             "type": "apiKey", | ||||
|             "in": "header", | ||||
|             "name": "Authorization", | ||||
|         } | ||||
|  | ||||
| @ -11,13 +11,7 @@ from drf_spectacular.utils import ( | ||||
|     inline_serializer, | ||||
| ) | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.fields import ( | ||||
|     BooleanField, | ||||
|     CharField, | ||||
|     FileField, | ||||
|     IntegerField, | ||||
|     ReadOnlyField, | ||||
| ) | ||||
| from rest_framework.fields import BooleanField, CharField, FileField, ReadOnlyField | ||||
| from rest_framework.parsers import MultiPartParser | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| @ -107,15 +101,19 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): | ||||
|         return applications | ||||
|  | ||||
|     @extend_schema( | ||||
|         request=inline_serializer( | ||||
|             "CheckAccessRequest", fields={"for_user": IntegerField(required=False)} | ||||
|         ), | ||||
|         parameters=[ | ||||
|             OpenApiParameter( | ||||
|                 name="for_user", | ||||
|                 location=OpenApiParameter.QUERY, | ||||
|                 type=OpenApiTypes.INT, | ||||
|             ) | ||||
|         ], | ||||
|         responses={ | ||||
|             200: PolicyTestResultSerializer(), | ||||
|             404: OpenApiResponse(description="for_user user not found"), | ||||
|         }, | ||||
|     ) | ||||
|     @action(detail=True, methods=["POST"]) | ||||
|     @action(detail=True, methods=["GET"]) | ||||
|     # pylint: disable=unused-argument | ||||
|     def check_access(self, request: Request, slug: str) -> Response: | ||||
|         """Check access to a single application by slug""" | ||||
| @ -204,7 +202,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): | ||||
|         """Set application icon""" | ||||
|         app: Application = self.get_object() | ||||
|         icon = request.FILES.get("file", None) | ||||
|         clear = request.data.get("clear", False) | ||||
|         clear = request.data.get("clear", "false").lower() == "true" | ||||
|         if clear: | ||||
|             # .delete() saves the model by default | ||||
|             app.meta_icon.delete() | ||||
|  | ||||
| @ -26,6 +26,8 @@ class ImpersonateMiddleware: | ||||
|  | ||||
|         if SESSION_IMPERSONATE_USER in request.session: | ||||
|             request.user = request.session[SESSION_IMPERSONATE_USER] | ||||
|             # Ensure that the user is active, otherwise nothing will work | ||||
|             request.user.is_active = True | ||||
|  | ||||
|         return self.get_response(request) | ||||
|  | ||||
|  | ||||
| @ -494,8 +494,12 @@ class AuthenticatedSession(ExpiringModel): | ||||
|     last_used = models.DateTimeField(auto_now=True) | ||||
|  | ||||
|     @staticmethod | ||||
|     def from_request(request: HttpRequest, user: User) -> "AuthenticatedSession": | ||||
|     def from_request( | ||||
|         request: HttpRequest, user: User | ||||
|     ) -> Optional["AuthenticatedSession"]: | ||||
|         """Create a new session from a http request""" | ||||
|         if not hasattr(request, "session") or not request.session.session_key: | ||||
|             return None | ||||
|         return AuthenticatedSession( | ||||
|             session_key=request.session.session_key, | ||||
|             user=user, | ||||
|  | ||||
| @ -1,11 +1,12 @@ | ||||
| """authentik core signals""" | ||||
| from typing import TYPE_CHECKING | ||||
| from typing import TYPE_CHECKING, Type | ||||
|  | ||||
| from django.contrib.auth.signals import user_logged_in, user_logged_out | ||||
| from django.contrib.sessions.backends.cache import KEY_PREFIX | ||||
| from django.core.cache import cache | ||||
| from django.core.signals import Signal | ||||
| from django.db.models import Model | ||||
| from django.db.models.signals import post_save | ||||
| from django.db.models.signals import post_save, pre_delete | ||||
| from django.dispatch import receiver | ||||
| from django.http.request import HttpRequest | ||||
| from prometheus_client import Gauge | ||||
| @ -18,7 +19,7 @@ GAUGE_MODELS = Gauge( | ||||
| ) | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from authentik.core.models import User | ||||
|     from authentik.core.models import AuthenticatedSession, User | ||||
|  | ||||
|  | ||||
| @receiver(post_save) | ||||
| @ -48,7 +49,9 @@ def user_logged_in_session(sender, request: HttpRequest, user: "User", **_): | ||||
|     """Create an AuthenticatedSession from request""" | ||||
|     from authentik.core.models import AuthenticatedSession | ||||
|  | ||||
|     AuthenticatedSession.from_request(request, user).save() | ||||
|     session = AuthenticatedSession.from_request(request, user) | ||||
|     if session: | ||||
|         session.save() | ||||
|  | ||||
|  | ||||
| @receiver(user_logged_out) | ||||
| @ -60,3 +63,17 @@ def user_logged_out_session(sender, request: HttpRequest, user: "User", **_): | ||||
|     AuthenticatedSession.objects.filter( | ||||
|         session_key=request.session.session_key | ||||
|     ).delete() | ||||
|  | ||||
|  | ||||
| @receiver(pre_delete) | ||||
| def authenticated_session_delete( | ||||
|     sender: Type[Model], instance: "AuthenticatedSession", **_ | ||||
| ): | ||||
|     """Delete session when authenticated session is deleted""" | ||||
|     from authentik.core.models import AuthenticatedSession | ||||
|  | ||||
|     if sender != AuthenticatedSession: | ||||
|         return | ||||
|  | ||||
|     cache_key = f"{KEY_PREFIX}{instance.session_key}" | ||||
|     cache.delete(cache_key) | ||||
|  | ||||
| @ -33,6 +33,7 @@ from authentik.flows.planner import ( | ||||
| from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN | ||||
| from authentik.lib.utils.urls import redirect_with_qs | ||||
| from authentik.policies.utils import delete_none_keys | ||||
| from authentik.stages.password import BACKEND_DJANGO | ||||
| from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND | ||||
| from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT | ||||
|  | ||||
| @ -182,6 +183,8 @@ class SourceFlowManager: | ||||
|     # pylint: disable=unused-argument | ||||
|     def get_stages_to_append(self, flow: Flow) -> list[Stage]: | ||||
|         """Hook to override stages which are appended to the flow""" | ||||
|         if not self.source.enrollment_flow: | ||||
|             return [] | ||||
|         if flow.slug == self.source.enrollment_flow.slug: | ||||
|             return [ | ||||
|                 in_memory_stage(PostUserEnrollmentStage), | ||||
| @ -198,7 +201,7 @@ class SourceFlowManager: | ||||
|         kwargs.update( | ||||
|             { | ||||
|                 # Since we authenticate the user by their token, they have no backend set | ||||
|                 PLAN_CONTEXT_AUTHENTICATION_BACKEND: "django.contrib.auth.backends.ModelBackend", | ||||
|                 PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_DJANGO, | ||||
|                 PLAN_CONTEXT_SSO: True, | ||||
|                 PLAN_CONTEXT_SOURCE: self.source, | ||||
|                 PLAN_CONTEXT_REDIRECT: final_redirect, | ||||
|  | ||||
| @ -3,16 +3,6 @@ | ||||
| {% load static %} | ||||
| {% load i18n %} | ||||
|  | ||||
| {% block head %} | ||||
| {{ block.super }} | ||||
| <style> | ||||
| .pf-c-background-image::before { | ||||
|     background-image: url("{% static 'dist/assets/images/flow_background.jpg' %}"); | ||||
|     background-position: center; | ||||
| } | ||||
| </style> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block title %} | ||||
| {% trans 'End session' %} - {{ tenant.branding_title }} | ||||
| {% endblock %} | ||||
|  | ||||
| @ -26,7 +26,7 @@ class TestApplicationsAPI(APITestCase): | ||||
|     def test_check_access(self): | ||||
|         """Test check_access operation""" | ||||
|         self.client.force_login(self.user) | ||||
|         response = self.client.post( | ||||
|         response = self.client.get( | ||||
|             reverse( | ||||
|                 "authentik_api:application-check-access", | ||||
|                 kwargs={"slug": self.allowed.slug}, | ||||
| @ -36,7 +36,7 @@ class TestApplicationsAPI(APITestCase): | ||||
|         self.assertJSONEqual( | ||||
|             force_str(response.content), {"messages": [], "passing": True} | ||||
|         ) | ||||
|         response = self.client.post( | ||||
|         response = self.client.get( | ||||
|             reverse( | ||||
|                 "authentik_api:application-check-access", | ||||
|                 kwargs={"slug": self.denied.slug}, | ||||
|  | ||||
| @ -17,6 +17,9 @@ class TestImpersonation(TestCase): | ||||
|  | ||||
|     def test_impersonate_simple(self): | ||||
|         """test simple impersonation and un-impersonation""" | ||||
|         # test with an inactive user to ensure that still works | ||||
|         self.other_user.is_active = False | ||||
|         self.other_user.save() | ||||
|         self.client.force_login(self.akadmin) | ||||
|  | ||||
|         self.client.get( | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
| from dataclasses import dataclass | ||||
| from typing import Optional | ||||
|  | ||||
| from rest_framework.fields import CharField, DictField | ||||
| from rest_framework.fields import CharField | ||||
|  | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
| from authentik.flows.challenge import Challenge | ||||
| @ -22,18 +22,10 @@ class UILoginButton: | ||||
|     icon_url: Optional[str] = None | ||||
|  | ||||
|  | ||||
| class UILoginButtonSerializer(PassiveSerializer): | ||||
|     """Serializer for Login buttons of sources""" | ||||
|  | ||||
|     name = CharField() | ||||
|     challenge = DictField() | ||||
|     icon_url = CharField(required=False, allow_null=True) | ||||
|  | ||||
|  | ||||
| class UserSettingSerializer(PassiveSerializer): | ||||
|     """Serializer for User settings for stages and sources""" | ||||
|  | ||||
|     object_uid = CharField() | ||||
|     component = CharField() | ||||
|     title = CharField() | ||||
|     configure_url = CharField() | ||||
|     configure_url = CharField(required=False) | ||||
|  | ||||
| @ -36,6 +36,7 @@ class EventSerializer(ModelSerializer): | ||||
|             "client_ip", | ||||
|             "created", | ||||
|             "expires", | ||||
|             "tenant", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| @ -76,6 +77,11 @@ class EventsFilter(django_filters.FilterSet): | ||||
|         field_name="action", | ||||
|         lookup_expr="icontains", | ||||
|     ) | ||||
|     tenant_name = django_filters.CharFilter( | ||||
|         field_name="tenant", | ||||
|         lookup_expr="name", | ||||
|         label="Tenant name", | ||||
|     ) | ||||
|  | ||||
|     # pylint: disable=unused-argument | ||||
|     def filter_context_model_pk(self, queryset, name, value): | ||||
|  | ||||
| @ -40,9 +40,9 @@ class GeoIPReader: | ||||
|             return | ||||
|         try: | ||||
|             reader = Reader(path) | ||||
|             LOGGER.info("Loaded GeoIP database") | ||||
|             self.__reader = reader | ||||
|             self.__last_mtime = stat(path).st_mtime | ||||
|             LOGGER.info("Loaded GeoIP database", last_write=self.__last_mtime) | ||||
|         except OSError as exc: | ||||
|             LOGGER.warning("Failed to load GeoIP database", exc=exc) | ||||
|  | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| from functools import partial | ||||
| from typing import Callable | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.db.models import Model | ||||
| from django.db.models.signals import post_save, pre_delete | ||||
| from django.http import HttpRequest, HttpResponse | ||||
| @ -12,6 +13,7 @@ from authentik.core.models import User | ||||
| from authentik.events.models import Event, EventAction, Notification | ||||
| from authentik.events.signals import EventNewThread | ||||
| from authentik.events.utils import model_to_dict | ||||
| from authentik.lib.utils.errors import exception_to_string | ||||
|  | ||||
|  | ||||
| class AuditMiddleware: | ||||
| @ -54,10 +56,19 @@ class AuditMiddleware: | ||||
|  | ||||
|     # pylint: disable=unused-argument | ||||
|     def process_exception(self, request: HttpRequest, exception: Exception): | ||||
|         """Unregister handlers in case of exception""" | ||||
|         """Disconnect handlers in case of exception""" | ||||
|         post_save.disconnect(dispatch_uid=LOCAL.authentik["request_id"]) | ||||
|         pre_delete.disconnect(dispatch_uid=LOCAL.authentik["request_id"]) | ||||
|  | ||||
|         if settings.DEBUG: | ||||
|             return | ||||
|         thread = EventNewThread( | ||||
|             EventAction.SYSTEM_EXCEPTION, | ||||
|             request, | ||||
|             message=exception_to_string(exception), | ||||
|         ) | ||||
|         thread.run() | ||||
|  | ||||
|     @staticmethod | ||||
|     # pylint: disable=unused-argument | ||||
|     def post_save_handler( | ||||
|  | ||||
							
								
								
									
										55
									
								
								authentik/events/migrations/0016_add_tenant.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								authentik/events/migrations/0016_add_tenant.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | ||||
| # Generated by Django 3.2.4 on 2021-06-14 15:33 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
| import authentik.events.models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_events", "0015_alter_event_action"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="event", | ||||
|             name="tenant", | ||||
|             field=models.JSONField( | ||||
|                 blank=True, default=authentik.events.models.default_tenant | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name="event", | ||||
|             name="action", | ||||
|             field=models.TextField( | ||||
|                 choices=[ | ||||
|                     ("login", "Login"), | ||||
|                     ("login_failed", "Login Failed"), | ||||
|                     ("logout", "Logout"), | ||||
|                     ("user_write", "User Write"), | ||||
|                     ("suspicious_request", "Suspicious Request"), | ||||
|                     ("password_set", "Password Set"), | ||||
|                     ("secret_view", "Secret View"), | ||||
|                     ("invitation_used", "Invite Used"), | ||||
|                     ("authorize_application", "Authorize Application"), | ||||
|                     ("source_linked", "Source Linked"), | ||||
|                     ("impersonation_started", "Impersonation Started"), | ||||
|                     ("impersonation_ended", "Impersonation Ended"), | ||||
|                     ("policy_execution", "Policy Execution"), | ||||
|                     ("policy_exception", "Policy Exception"), | ||||
|                     ("property_mapping_exception", "Property Mapping Exception"), | ||||
|                     ("system_task_execution", "System Task Execution"), | ||||
|                     ("system_task_exception", "System Task Exception"), | ||||
|                     ("system_exception", "System Exception"), | ||||
|                     ("configuration_error", "Configuration Error"), | ||||
|                     ("model_created", "Model Created"), | ||||
|                     ("model_updated", "Model Updated"), | ||||
|                     ("model_deleted", "Model Deleted"), | ||||
|                     ("email_sent", "Email Sent"), | ||||
|                     ("update_available", "Update Available"), | ||||
|                     ("custom_", "Custom Prefix"), | ||||
|                 ] | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @ -21,11 +21,12 @@ from authentik.core.middleware import ( | ||||
| ) | ||||
| from authentik.core.models import ExpiringModel, Group, User | ||||
| from authentik.events.geo import GEOIP_READER | ||||
| from authentik.events.utils import cleanse_dict, get_user, sanitize_dict | ||||
| from authentik.events.utils import cleanse_dict, get_user, model_to_dict, sanitize_dict | ||||
| from authentik.lib.sentry import SentryIgnoredException | ||||
| from authentik.lib.utils.http import get_client_ip | ||||
| from authentik.policies.models import PolicyBindingModel | ||||
| from authentik.stages.email.utils import TemplateEmailMessage | ||||
| from authentik.tenants.utils import DEFAULT_TENANT | ||||
|  | ||||
| LOGGER = get_logger("authentik.events") | ||||
| GAUGE_EVENTS = Gauge( | ||||
| @ -40,6 +41,11 @@ def default_event_duration(): | ||||
|     return now() + timedelta(days=365) | ||||
|  | ||||
|  | ||||
| def default_tenant(): | ||||
|     """Get a default value for tenant""" | ||||
|     return sanitize_dict(model_to_dict(DEFAULT_TENANT)) | ||||
|  | ||||
|  | ||||
| class NotificationTransportError(SentryIgnoredException): | ||||
|     """Error raised when a notification fails to be delivered""" | ||||
|  | ||||
| @ -71,6 +77,7 @@ class EventAction(models.TextChoices): | ||||
|  | ||||
|     SYSTEM_TASK_EXECUTION = "system_task_execution" | ||||
|     SYSTEM_TASK_EXCEPTION = "system_task_exception" | ||||
|     SYSTEM_EXCEPTION = "system_exception" | ||||
|  | ||||
|     CONFIGURATION_ERROR = "configuration_error" | ||||
|  | ||||
| @ -94,6 +101,7 @@ class Event(ExpiringModel): | ||||
|     context = models.JSONField(default=dict, blank=True) | ||||
|     client_ip = models.GenericIPAddressField(null=True) | ||||
|     created = models.DateTimeField(auto_now_add=True) | ||||
|     tenant = models.JSONField(default=default_tenant, blank=True) | ||||
|  | ||||
|     # Shadow the expires attribute from ExpiringModel to override the default duration | ||||
|     expires = models.DateTimeField(default=default_event_duration) | ||||
| @ -132,6 +140,13 @@ class Event(ExpiringModel): | ||||
|         """Add data from a Django-HttpRequest, allowing the creation of | ||||
|         Events independently from requests. | ||||
|         `user` arguments optionally overrides user from requests.""" | ||||
|         if request: | ||||
|             self.context["http_request"] = { | ||||
|                 "path": request.get_full_path(), | ||||
|                 "method": request.method, | ||||
|             } | ||||
|         if hasattr(request, "tenant"): | ||||
|             self.tenant = sanitize_dict(model_to_dict(request.tenant)) | ||||
|         if hasattr(request, "user"): | ||||
|             original_user = None | ||||
|             if hasattr(request, "session"): | ||||
|  | ||||
| @ -301,10 +301,14 @@ class FlowViewSet(UsedByMixin, ModelViewSet): | ||||
|         """Set Flow background""" | ||||
|         flow: Flow = self.get_object() | ||||
|         background = request.FILES.get("file", None) | ||||
|         clear = request.data.get("clear", False) | ||||
|         clear = request.data.get("clear", "false").lower() == "true" | ||||
|         if clear: | ||||
|             if flow.background_url.startswith("/media"): | ||||
|                 # .delete() saves the model by default | ||||
|                 flow.background.delete() | ||||
|             else: | ||||
|                 flow.background = None | ||||
|                 flow.save() | ||||
|             return Response({}) | ||||
|         if background: | ||||
|             flow.background = background | ||||
|  | ||||
| @ -93,7 +93,7 @@ class StageViewSet( | ||||
|             if not user_settings: | ||||
|                 continue | ||||
|             user_settings.initial_data["object_uid"] = str(stage.pk) | ||||
|             if hasattr(stage, "configure_flow"): | ||||
|             if hasattr(stage, "configure_flow") and stage.configure_flow: | ||||
|                 user_settings.initial_data["configure_url"] = reverse( | ||||
|                     "authentik_flows:configure", | ||||
|                     kwargs={"stage_uuid": stage.pk}, | ||||
|  | ||||
| @ -6,6 +6,7 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor | ||||
|  | ||||
| from authentik.flows.models import FlowDesignation | ||||
| from authentik.stages.identification.models import UserFields | ||||
| from authentik.stages.password import BACKEND_DJANGO, BACKEND_LDAP | ||||
|  | ||||
|  | ||||
| def create_default_authentication_flow( | ||||
| @ -31,7 +32,7 @@ def create_default_authentication_flow( | ||||
|  | ||||
|     password_stage, _ = PasswordStage.objects.using(db_alias).update_or_create( | ||||
|         name="default-authentication-password", | ||||
|         defaults={"backends": ["django.contrib.auth.backends.ModelBackend"]}, | ||||
|         defaults={"backends": [BACKEND_DJANGO, BACKEND_LDAP]}, | ||||
|     ) | ||||
|  | ||||
|     login_stage, _ = UserLoginStage.objects.using(db_alias).update_or_create( | ||||
|  | ||||
| @ -15,9 +15,6 @@ PREFILL_POLICY_EXPRESSION = """# This policy sets the user for the currently run | ||||
| # by injecting "pending_user" | ||||
| akadmin = ak_user_by(username="akadmin") | ||||
| context["pending_user"] = akadmin | ||||
| # We're also setting the backend for the user, so we can | ||||
| # directly login without having to identify again | ||||
| context["user_backend"] = "django.contrib.auth.backends.ModelBackend" | ||||
| return True""" | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -18,27 +18,11 @@ from authentik.flows.challenge import ( | ||||
| ) | ||||
| from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER | ||||
| from authentik.flows.views import FlowExecutorView | ||||
| from authentik.lib.sentry import SentryIgnoredException | ||||
|  | ||||
| PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier" | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| class InvalidChallengeError(SentryIgnoredException): | ||||
|     """Error raised when a challenge from a stage is not valid""" | ||||
|  | ||||
|     def __init__(self, errors, stage_view: View, challenge: Challenge) -> None: | ||||
|         super().__init__() | ||||
|         self.errors = errors | ||||
|         self.stage_view = stage_view | ||||
|         self.challenge = challenge | ||||
|  | ||||
|     def __str__(self) -> str: | ||||
|         return ( | ||||
|             f"Invalid challenge from {self.stage_view}: {self.errors}\n{self.challenge}" | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class StageView(View): | ||||
|     """Abstract Stage, inherits TemplateView but can be combined with FormView""" | ||||
|  | ||||
|  | ||||
| @ -44,6 +44,7 @@ from authentik.flows.planner import ( | ||||
|     FlowPlan, | ||||
|     FlowPlanner, | ||||
| ) | ||||
| from authentik.lib.sentry import SentryIgnoredException | ||||
| from authentik.lib.utils.reflection import all_subclasses, class_to_path | ||||
| from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs | ||||
| from authentik.tenants.models import Tenant | ||||
| @ -93,6 +94,10 @@ def challenge_response_types(): | ||||
|     return Inner() | ||||
|  | ||||
|  | ||||
| class InvalidStageError(SentryIgnoredException): | ||||
|     """Error raised when a challenge from a stage is not valid""" | ||||
|  | ||||
|  | ||||
| @method_decorator(xframe_options_sameorigin, name="dispatch") | ||||
| class FlowExecutorView(APIView): | ||||
|     """Stage 1 Flow executor, passing requests to Stage Views""" | ||||
| @ -164,17 +169,24 @@ class FlowExecutorView(APIView): | ||||
|             current_stage=self.current_stage, | ||||
|             flow_slug=self.flow.slug, | ||||
|         ) | ||||
|         try: | ||||
|             stage_cls = self.current_stage.type | ||||
|         except NotImplementedError as exc: | ||||
|             self._logger.debug("Error getting stage type", exc=exc) | ||||
|             return self.stage_invalid() | ||||
|         self.current_stage_view = stage_cls(self) | ||||
|         self.current_stage_view.args = self.args | ||||
|         self.current_stage_view.kwargs = self.kwargs | ||||
|         self.current_stage_view.request = request | ||||
|         try: | ||||
|             return super().dispatch(request) | ||||
|         except InvalidStageError as exc: | ||||
|             return self.stage_invalid(str(exc)) | ||||
|  | ||||
|     @extend_schema( | ||||
|         responses={ | ||||
|             200: PolymorphicProxySerializer( | ||||
|                 component_name="FlowChallengeRequest", | ||||
|                 component_name="ChallengeTypes", | ||||
|                 serializers=challenge_types(), | ||||
|                 resource_type_field_name="component", | ||||
|             ), | ||||
| @ -214,7 +226,7 @@ class FlowExecutorView(APIView): | ||||
|     @extend_schema( | ||||
|         responses={ | ||||
|             200: PolymorphicProxySerializer( | ||||
|                 component_name="FlowChallengeRequest", | ||||
|                 component_name="ChallengeTypes", | ||||
|                 serializers=challenge_types(), | ||||
|                 resource_type_field_name="component", | ||||
|             ), | ||||
|  | ||||
							
								
								
									
										10
									
								
								authentik/lib/utils/errors.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								authentik/lib/utils/errors.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| """error utils""" | ||||
| from traceback import format_tb | ||||
|  | ||||
| TRACEBACK_HEADER = "Traceback (most recent call last):\n" | ||||
|  | ||||
|  | ||||
| def exception_to_string(exc: Exception) -> str: | ||||
|     """Convert exception to string stackrace""" | ||||
|     # Either use passed original exception or whatever we have | ||||
|     return TRACEBACK_HEADER + "".join(format_tb(exc.__traceback__)) + str(exc) | ||||
| @ -33,6 +33,13 @@ class ServiceConnectionSerializer(ModelSerializer, MetaNameSerializer): | ||||
|  | ||||
|     component = ReadOnlyField() | ||||
|  | ||||
|     def get_component(self, obj: OutpostServiceConnection) -> str: | ||||
|         """Get object type so that we know how to edit the object""" | ||||
|         # pyright: reportGeneralTypeIssues=false | ||||
|         if obj.__class__ == OutpostServiceConnection: | ||||
|             return "" | ||||
|         return obj.component | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         model = OutpostServiceConnection | ||||
|  | ||||
| @ -63,7 +63,7 @@ class DockerController(BaseController): | ||||
|             self.client.images.pull(image_name) | ||||
|             container_args = { | ||||
|                 "image": image_name, | ||||
|                 "name": f"authentik-proxy-{self.outpost.uuid.hex}", | ||||
|                 "name": container_name, | ||||
|                 "detach": True, | ||||
|                 "ports": { | ||||
|                     f"{port.port}/{port.protocol.lower()}": port.inner_port or port.port | ||||
|  | ||||
| @ -8,7 +8,7 @@ from uuid import uuid4 | ||||
| from dacite import from_dict | ||||
| from django.contrib.auth.models import Permission | ||||
| from django.core.cache import cache | ||||
| from django.db import models, transaction | ||||
| from django.db import IntegrityError, models, transaction | ||||
| from django.db.models.base import Model | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from docker.client import DockerClient | ||||
| @ -50,6 +50,8 @@ class ServiceConnectionInvalid(SentryIgnoredException): | ||||
| class OutpostConfig: | ||||
|     """Configuration an outpost uses to configure it self""" | ||||
|  | ||||
|     # update website/docs/outposts/outposts.md | ||||
|  | ||||
|     authentik_host: str | ||||
|     authentik_host_insecure: bool = False | ||||
|  | ||||
| @ -141,7 +143,9 @@ class OutpostServiceConnection(models.Model): | ||||
|     @property | ||||
|     def component(self) -> str: | ||||
|         """Return component used to edit this object""" | ||||
|         raise NotImplementedError | ||||
|         # This is called when creating an outpost with a service connection | ||||
|         # since the response doesn't use the correct inheritance | ||||
|         return "" | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
| @ -380,13 +384,11 @@ class Outpost(models.Model): | ||||
|         tokens = Token.filter_not_expired( | ||||
|             identifier=self.token_identifier, | ||||
|             intent=TokenIntents.INTENT_API, | ||||
|             managed=managed, | ||||
|         ) | ||||
|         if tokens.exists(): | ||||
|             token = tokens.first() | ||||
|             if not token.managed: | ||||
|                 token.managed = managed | ||||
|                 token.save() | ||||
|             return token | ||||
|             return tokens.first() | ||||
|         try: | ||||
|             return Token.objects.create( | ||||
|                 user=self.user, | ||||
|                 identifier=self.token_identifier, | ||||
| @ -395,6 +397,11 @@ class Outpost(models.Model): | ||||
|                 expiring=False, | ||||
|                 managed=managed, | ||||
|             ) | ||||
|         except IntegrityError: | ||||
|             # Integrity error happens mostly when managed is re-used | ||||
|             Token.objects.filter(managed=managed).delete() | ||||
|             Token.objects.filter(identifier=self.token_identifier).delete() | ||||
|             return self.token | ||||
|  | ||||
|     def get_required_objects(self) -> Iterable[Union[models.Model, str]]: | ||||
|         """Get an iterator of all objects the user needs read access to""" | ||||
|  | ||||
| @ -0,0 +1,48 @@ | ||||
| # Generated by Django 3.2.4 on 2021-06-14 15:32 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_policies_event_matcher", "0016_alter_eventmatcherpolicy_action"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name="eventmatcherpolicy", | ||||
|             name="action", | ||||
|             field=models.TextField( | ||||
|                 blank=True, | ||||
|                 choices=[ | ||||
|                     ("login", "Login"), | ||||
|                     ("login_failed", "Login Failed"), | ||||
|                     ("logout", "Logout"), | ||||
|                     ("user_write", "User Write"), | ||||
|                     ("suspicious_request", "Suspicious Request"), | ||||
|                     ("password_set", "Password Set"), | ||||
|                     ("secret_view", "Secret View"), | ||||
|                     ("invitation_used", "Invite Used"), | ||||
|                     ("authorize_application", "Authorize Application"), | ||||
|                     ("source_linked", "Source Linked"), | ||||
|                     ("impersonation_started", "Impersonation Started"), | ||||
|                     ("impersonation_ended", "Impersonation Ended"), | ||||
|                     ("policy_execution", "Policy Execution"), | ||||
|                     ("policy_exception", "Policy Exception"), | ||||
|                     ("property_mapping_exception", "Property Mapping Exception"), | ||||
|                     ("system_task_execution", "System Task Execution"), | ||||
|                     ("system_task_exception", "System Task Exception"), | ||||
|                     ("system_exception", "System Exception"), | ||||
|                     ("configuration_error", "Configuration Error"), | ||||
|                     ("model_created", "Model Created"), | ||||
|                     ("model_updated", "Model Updated"), | ||||
|                     ("model_deleted", "Model Deleted"), | ||||
|                     ("email_sent", "Email Sent"), | ||||
|                     ("update_available", "Update Available"), | ||||
|                     ("custom_", "Custom Prefix"), | ||||
|                 ], | ||||
|                 help_text="Match created events with this action type. When left empty, all action types will be matched.", | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @ -1,7 +1,6 @@ | ||||
| """authentik policy task""" | ||||
| from multiprocessing import get_context | ||||
| from multiprocessing.connection import Connection | ||||
| from traceback import format_tb | ||||
| from typing import Optional | ||||
|  | ||||
| from django.core.cache import cache | ||||
| @ -11,12 +10,12 @@ from sentry_sdk.tracing import Span | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.lib.utils.errors import exception_to_string | ||||
| from authentik.policies.exceptions import PolicyException | ||||
| from authentik.policies.models import PolicyBinding | ||||
| from authentik.policies.types import PolicyRequest, PolicyResult | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| TRACEBACK_HEADER = "Traceback (most recent call last):\n" | ||||
|  | ||||
| FORK_CTX = get_context("fork") | ||||
| PROCESS_CLASS = FORK_CTX.Process | ||||
| @ -106,11 +105,7 @@ class PolicyProcess(PROCESS_CLASS): | ||||
|         except PolicyException as exc: | ||||
|             # Either use passed original exception or whatever we have | ||||
|             src_exc = exc.src_exc if exc.src_exc else exc | ||||
|             error_string = ( | ||||
|                 TRACEBACK_HEADER | ||||
|                 + "".join(format_tb(src_exc.__traceback__)) | ||||
|                 + str(src_exc) | ||||
|             ) | ||||
|             error_string = exception_to_string(src_exc) | ||||
|             # Create policy exception event, only when we're not debugging | ||||
|             if not self.request.debug: | ||||
|                 self.create_event(EventAction.POLICY_EXCEPTION, message=error_string) | ||||
|  | ||||
| @ -3,11 +3,13 @@ from django.core.cache import cache | ||||
| from django.db import models | ||||
| from django.utils.translation import gettext as _ | ||||
| from rest_framework.serializers import BaseSerializer | ||||
| from structlog import get_logger | ||||
|  | ||||
| from authentik.lib.utils.http import get_client_ip | ||||
| from authentik.policies.models import Policy | ||||
| from authentik.policies.types import PolicyRequest, PolicyResult | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| CACHE_KEY_IP_PREFIX = "authentik_reputation_ip_" | ||||
| CACHE_KEY_USER_PREFIX = "authentik_reputation_user_" | ||||
|  | ||||
| @ -35,9 +37,16 @@ class ReputationPolicy(Policy): | ||||
|         if self.check_ip: | ||||
|             score = cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0) | ||||
|             passing = passing and score <= self.threshold | ||||
|             LOGGER.debug("Score for IP", ip=remote_ip, score=score, passing=passing) | ||||
|         if self.check_username: | ||||
|             score = cache.get_or_set(CACHE_KEY_USER_PREFIX + request.user.username, 0) | ||||
|             passing = passing and score <= self.threshold | ||||
|             LOGGER.debug( | ||||
|                 "Score for Username", | ||||
|                 username=request.user.username, | ||||
|                 score=score, | ||||
|                 passing=passing, | ||||
|             ) | ||||
|         return PolicyResult(passing) | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
| @ -1,9 +1,10 @@ | ||||
| """test reputation signals and policy""" | ||||
| from django.contrib.auth import authenticate | ||||
| from django.core.cache import cache | ||||
| from django.test import TestCase | ||||
| from django.test import RequestFactory, TestCase | ||||
|  | ||||
| from authentik.core.models import User | ||||
| from authentik.lib.utils.http import DEFAULT_IP | ||||
| from authentik.policies.reputation.models import ( | ||||
|     CACHE_KEY_IP_PREFIX, | ||||
|     CACHE_KEY_USER_PREFIX, | ||||
| @ -19,9 +20,12 @@ class TestReputationPolicy(TestCase): | ||||
|     """test reputation signals and policy""" | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.test_ip = "255.255.255.255" | ||||
|         self.request_factory = RequestFactory() | ||||
|         self.request = self.request_factory.get("/") | ||||
|         self.test_ip = "127.0.0.1" | ||||
|         self.test_username = "test" | ||||
|         cache.delete(CACHE_KEY_IP_PREFIX + self.test_ip) | ||||
|         cache.delete(CACHE_KEY_IP_PREFIX + DEFAULT_IP) | ||||
|         cache.delete(CACHE_KEY_USER_PREFIX + self.test_username) | ||||
|         # We need a user for the one-to-one in userreputation | ||||
|         self.user = User.objects.create(username=self.test_username) | ||||
| @ -29,7 +33,9 @@ class TestReputationPolicy(TestCase): | ||||
|     def test_ip_reputation(self): | ||||
|         """test IP reputation""" | ||||
|         # Trigger negative reputation | ||||
|         authenticate(None, username=self.test_username, password=self.test_username) | ||||
|         authenticate( | ||||
|             self.request, username=self.test_username, password=self.test_username | ||||
|         ) | ||||
|         # Test value in cache | ||||
|         self.assertEqual(cache.get(CACHE_KEY_IP_PREFIX + self.test_ip), -1) | ||||
|         # Save cache and check db values | ||||
| @ -39,7 +45,9 @@ class TestReputationPolicy(TestCase): | ||||
|     def test_user_reputation(self): | ||||
|         """test User reputation""" | ||||
|         # Trigger negative reputation | ||||
|         authenticate(None, username=self.test_username, password=self.test_username) | ||||
|         authenticate( | ||||
|             self.request, username=self.test_username, password=self.test_username | ||||
|         ) | ||||
|         # Test value in cache | ||||
|         self.assertEqual(cache.get(CACHE_KEY_USER_PREFIX + self.test_username), -1) | ||||
|         # Save cache and check db values | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| """ProxyProvider API Views""" | ||||
| from typing import Any | ||||
|  | ||||
| from drf_spectacular.utils import extend_schema_field | ||||
| from drf_spectacular.utils import extend_schema_field, extend_schema_serializer | ||||
| from rest_framework.exceptions import ValidationError | ||||
| from rest_framework.fields import CharField, ListField, SerializerMethodField | ||||
| from rest_framework.serializers import ModelSerializer | ||||
| @ -85,6 +85,7 @@ class ProxyProviderViewSet(UsedByMixin, ModelViewSet): | ||||
|     ordering = ["name"] | ||||
|  | ||||
|  | ||||
| @extend_schema_serializer(deprecate_fields=["forward_auth_mode"]) | ||||
| class ProxyOutpostConfigSerializer(ModelSerializer): | ||||
|     """Proxy provider serializer for outposts""" | ||||
|  | ||||
|  | ||||
| @ -24,6 +24,7 @@ from authentik.sources.saml.processors.constants import ( | ||||
|     SAML_NAME_ID_FORMAT_EMAIL, | ||||
|     SAML_NAME_ID_FORMAT_PERSISTENT, | ||||
|     SAML_NAME_ID_FORMAT_TRANSIENT, | ||||
|     SAML_NAME_ID_FORMAT_UNSPECIFIED, | ||||
|     SAML_NAME_ID_FORMAT_WINDOWS, | ||||
|     SAML_NAME_ID_FORMAT_X509, | ||||
|     SIGN_ALGORITHM_TRANSFORM_MAP, | ||||
| @ -165,7 +166,10 @@ class AssertionProcessor: | ||||
|         if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_EMAIL: | ||||
|             name_id.text = self.http_request.user.email | ||||
|             return name_id | ||||
|         if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_PERSISTENT: | ||||
|         if name_id.attrib["Format"] in [ | ||||
|             SAML_NAME_ID_FORMAT_PERSISTENT, | ||||
|             SAML_NAME_ID_FORMAT_UNSPECIFIED, | ||||
|         ]: | ||||
|             name_id.text = persistent | ||||
|             return name_id | ||||
|         if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_X509: | ||||
| @ -180,7 +184,7 @@ class AssertionProcessor: | ||||
|             return name_id | ||||
|         if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT: | ||||
|             # Use the hash of the user's session, which changes every session | ||||
|             session_key: str = self.http_request.user.session.session_key | ||||
|             session_key: str = self.http_request.session.session_key | ||||
|             name_id.text = sha256(session_key.encode()).hexdigest() | ||||
|             return name_id | ||||
|         raise UnsupportedNameIDFormat( | ||||
|  | ||||
| @ -120,7 +120,7 @@ class ServiceProviderMetadataParser: | ||||
|                 ) | ||||
|                 ctx.key = key | ||||
|                 ctx.verify(signature_node) | ||||
|             except xmlsec.VerificationError as exc: | ||||
|             except xmlsec.Error as exc: | ||||
|                 raise ValueError("Failed to verify Metadata signature") from exc | ||||
|  | ||||
|     def parse(self, raw_xml: str) -> ServiceProviderMetadata: | ||||
|  | ||||
| @ -20,7 +20,7 @@ from authentik.sources.saml.processors.constants import ( | ||||
|     RSA_SHA256, | ||||
|     RSA_SHA384, | ||||
|     RSA_SHA512, | ||||
|     SAML_NAME_ID_FORMAT_EMAIL, | ||||
|     SAML_NAME_ID_FORMAT_UNSPECIFIED, | ||||
| ) | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| @ -42,7 +42,7 @@ class AuthNRequest: | ||||
|  | ||||
|     relay_state: Optional[str] = None | ||||
|  | ||||
|     name_id_policy: str = SAML_NAME_ID_FORMAT_EMAIL | ||||
|     name_id_policy: str = SAML_NAME_ID_FORMAT_UNSPECIFIED | ||||
|  | ||||
|  | ||||
| class AuthNRequestParser: | ||||
| @ -69,10 +69,12 @@ class AuthNRequestParser: | ||||
|         auth_n_request = AuthNRequest(id=root.attrib["ID"], relay_state=relay_state) | ||||
|  | ||||
|         # Check if AuthnRequest has a NameID Policy object | ||||
|         name_id_policies = root.findall(f"{{{NS_SAML_PROTOCOL}}}:NameIDPolicy") | ||||
|         name_id_policies = root.findall(f"{{{NS_SAML_PROTOCOL}}}NameIDPolicy") | ||||
|         if len(name_id_policies) > 0: | ||||
|             name_id_policy = name_id_policies[0] | ||||
|             auth_n_request.name_id_policy = name_id_policy.attrib["Format"] | ||||
|             auth_n_request.name_id_policy = name_id_policy.attrib.get( | ||||
|                 "Format", SAML_NAME_ID_FORMAT_UNSPECIFIED | ||||
|             ) | ||||
|  | ||||
|         return auth_n_request | ||||
|  | ||||
| @ -108,7 +110,7 @@ class AuthNRequestParser: | ||||
|                 ) | ||||
|                 ctx.key = key | ||||
|                 ctx.verify(signature_node) | ||||
|             except xmlsec.VerificationError as exc: | ||||
|             except xmlsec.Error as exc: | ||||
|                 raise CannotHandleAssertion(ERROR_FAILED_TO_VERIFY) from exc | ||||
|  | ||||
|         return self._parse_xml(decoded_xml, relay_state) | ||||
| @ -160,7 +162,7 @@ class AuthNRequestParser: | ||||
|                     sign_algorithm_transform, | ||||
|                     b64decode(signature), | ||||
|                 ) | ||||
|             except xmlsec.VerificationError as exc: | ||||
|             except xmlsec.Error as exc: | ||||
|                 raise CannotHandleAssertion(ERROR_FAILED_TO_VERIFY) from exc | ||||
|         return self._parse_xml(decoded_xml, relay_state) | ||||
|  | ||||
|  | ||||
| @ -2,18 +2,19 @@ | ||||
| from base64 import b64encode | ||||
|  | ||||
| from django.contrib.sessions.middleware import SessionMiddleware | ||||
| from django.http.request import HttpRequest, QueryDict | ||||
| from django.http.request import QueryDict | ||||
| from django.test import RequestFactory, TestCase | ||||
| from guardian.utils import get_anonymous_user | ||||
|  | ||||
| from authentik.crypto.models import CertificateKeyPair | ||||
| from authentik.flows.models import Flow | ||||
| from authentik.flows.tests.test_planner import dummy_get_response | ||||
| from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider | ||||
| from authentik.providers.saml.processors.assertion import AssertionProcessor | ||||
| from authentik.providers.saml.processors.request_parser import AuthNRequestParser | ||||
| from authentik.sources.saml.exceptions import MismatchedRequestID | ||||
| from authentik.sources.saml.models import SAMLSource | ||||
| from authentik.sources.saml.processors.constants import SAML_NAME_ID_FORMAT_EMAIL | ||||
| from authentik.sources.saml.processors.constants import SAML_NAME_ID_FORMAT_UNSPECIFIED | ||||
| from authentik.sources.saml.processors.request import ( | ||||
|     SESSION_REQUEST_ID, | ||||
|     RequestProcessor, | ||||
| @ -58,11 +59,6 @@ qNAZMq1DqpibfCBg | ||||
| -----END CERTIFICATE-----""" | ||||
|  | ||||
|  | ||||
| def dummy_get_response(request: HttpRequest):  # pragma: no cover | ||||
|     """Dummy get_response for SessionMiddleware""" | ||||
|     return None | ||||
|  | ||||
|  | ||||
| class TestAuthNRequest(TestCase): | ||||
|     """Test AuthN Request generator and parser""" | ||||
|  | ||||
| @ -210,5 +206,5 @@ class TestAuthNRequest(TestCase): | ||||
|             REDIRECT_REQUEST, REDIRECT_RELAY_STATE, REDIRECT_SIGNATURE, REDIRECT_SIG_ALG | ||||
|         ) | ||||
|         self.assertEqual(parsed_request.id, "_dcf55fcd27a887e60a7ef9ee6fd3adab") | ||||
|         self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_EMAIL) | ||||
|         self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_UNSPECIFIED) | ||||
|         self.assertEqual(parsed_request.relay_state, REDIRECT_RELAY_STATE) | ||||
|  | ||||
| @ -17,6 +17,7 @@ from authentik.providers.saml.models import SAMLBindings, SAMLProvider | ||||
| from authentik.providers.saml.processors.assertion import AssertionProcessor | ||||
| from authentik.providers.saml.processors.request_parser import AuthNRequest | ||||
| from authentik.providers.saml.utils.encoding import deflate_and_base64_encode, nice64 | ||||
| from authentik.sources.saml.exceptions import SAMLException | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| URL_VALIDATOR = URLValidator(schemes=("http", "https")) | ||||
| @ -56,22 +57,30 @@ class SAMLFlowFinalView(ChallengeStageView): | ||||
|         provider: SAMLProvider = get_object_or_404( | ||||
|             SAMLProvider, pk=application.provider_id | ||||
|         ) | ||||
|         # Log Application Authorization | ||||
|         Event.new( | ||||
|             EventAction.AUTHORIZE_APPLICATION, | ||||
|             authorized_application=application, | ||||
|             flow=self.executor.plan.flow_pk, | ||||
|         ).from_http(self.request) | ||||
|  | ||||
|         if SESSION_KEY_AUTH_N_REQUEST not in self.request.session: | ||||
|             return self.executor.stage_invalid() | ||||
|  | ||||
|         auth_n_request: AuthNRequest = self.request.session.pop( | ||||
|             SESSION_KEY_AUTH_N_REQUEST | ||||
|         ) | ||||
|         try: | ||||
|             response = AssertionProcessor( | ||||
|                 provider, request, auth_n_request | ||||
|             ).build_response() | ||||
|         except SAMLException as exc: | ||||
|             Event.new( | ||||
|                 EventAction.CONFIGURATION_ERROR, | ||||
|                 message=f"Failed to process SAML assertion: {str(exc)}", | ||||
|                 provider=provider, | ||||
|             ).from_http(self.request) | ||||
|             return self.executor.stage_invalid() | ||||
|  | ||||
|         # Log Application Authorization | ||||
|         Event.new( | ||||
|             EventAction.AUTHORIZE_APPLICATION, | ||||
|             authorized_application=application, | ||||
|             flow=self.executor.plan.flow_pk, | ||||
|         ).from_http(self.request) | ||||
|  | ||||
|         if provider.sp_binding == SAMLBindings.POST: | ||||
|             form_attrs = { | ||||
|  | ||||
| @ -44,7 +44,7 @@ class Command(BaseCommand): | ||||
|             user=user, | ||||
|             intent=TokenIntents.INTENT_RECOVERY, | ||||
|             description=f"Recovery Token generated by {getuser()} on {_now}", | ||||
|             identifier=f"ak-recovery-{user}", | ||||
|             identifier=f"ak-recovery-{user}-{_now}", | ||||
|         ) | ||||
|         self.stdout.write( | ||||
|             ( | ||||
|  | ||||
| @ -7,6 +7,7 @@ from django.utils.translation import gettext as _ | ||||
| from django.views import View | ||||
|  | ||||
| from authentik.core.models import Token, TokenIntents | ||||
| from authentik.stages.password import BACKEND_DJANGO | ||||
|  | ||||
|  | ||||
| class UseTokenView(View): | ||||
| @ -18,7 +19,7 @@ class UseTokenView(View): | ||||
|         if not tokens.exists(): | ||||
|             raise Http404 | ||||
|         token = tokens.first() | ||||
|         login(request, token.user, backend="django.contrib.auth.backends.ModelBackend") | ||||
|         login(request, token.user, backend=BACKEND_DJANGO) | ||||
|         token.delete() | ||||
|         messages.warning(request, _("Used recovery-link to authenticate.")) | ||||
|         return redirect("authentik_core:if-admin") | ||||
|  | ||||
| @ -375,7 +375,11 @@ if _ERROR_REPORTING: | ||||
|         environment=CONFIG.y("error_reporting.environment", "customer"), | ||||
|         send_default_pii=CONFIG.y_bool("error_reporting.send_pii", False), | ||||
|     ) | ||||
|     set_tag("authentik.build_hash", os.environ.get(ENV_GIT_HASH_KEY, "tagged")) | ||||
|     # Default to empty string as that is what docker has | ||||
|     build_hash = os.environ.get(ENV_GIT_HASH_KEY, "") | ||||
|     if build_hash == "": | ||||
|         build_hash = "tagged" | ||||
|     set_tag("authentik.build_hash", build_hash) | ||||
|     set_tag( | ||||
|         "authentik.env", "kubernetes" if "KUBERNETES_PORT" in os.environ else "compose" | ||||
|     ) | ||||
|  | ||||
| @ -2,17 +2,21 @@ | ||||
| from authentik.lib.sentry import SentryIgnoredException | ||||
|  | ||||
|  | ||||
| class MissingSAMLResponse(SentryIgnoredException): | ||||
| class SAMLException(SentryIgnoredException): | ||||
|     """Base SAML Exception""" | ||||
|  | ||||
|  | ||||
| class MissingSAMLResponse(SAMLException): | ||||
|     """Exception raised when request does not contain SAML Response.""" | ||||
|  | ||||
|  | ||||
| class UnsupportedNameIDFormat(SentryIgnoredException): | ||||
| class UnsupportedNameIDFormat(SAMLException): | ||||
|     """Exception raised when SAML Response contains NameID Format not supported.""" | ||||
|  | ||||
|  | ||||
| class MismatchedRequestID(SentryIgnoredException): | ||||
| class MismatchedRequestID(SAMLException): | ||||
|     """Exception raised when the returned request ID doesn't match the saved ID.""" | ||||
|  | ||||
|  | ||||
| class InvalidSignature(SentryIgnoredException): | ||||
| class InvalidSignature(SAMLException): | ||||
|     """Signature of XML Object is either missing or invalid""" | ||||
|  | ||||
| @ -15,6 +15,9 @@ NS_MAP = { | ||||
|  | ||||
| SAML_NAME_ID_FORMAT_EMAIL = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" | ||||
| SAML_NAME_ID_FORMAT_PERSISTENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" | ||||
| SAML_NAME_ID_FORMAT_UNSPECIFIED = ( | ||||
|     "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" | ||||
| ) | ||||
| SAML_NAME_ID_FORMAT_X509 = "urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName" | ||||
| SAML_NAME_ID_FORMAT_WINDOWS = ( | ||||
|     "urn:oasis:names:tc:SAML:2.0:nameid-format:WindowsDomainQualifiedName" | ||||
|  | ||||
| @ -39,7 +39,7 @@ from authentik.sources.saml.processors.constants import ( | ||||
| from authentik.sources.saml.processors.request import SESSION_REQUEST_ID | ||||
| from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND | ||||
| from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT | ||||
| from authentik.stages.user_login.stage import DEFAULT_BACKEND | ||||
| from authentik.stages.user_login.stage import BACKEND_DJANGO | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| if TYPE_CHECKING: | ||||
| @ -141,7 +141,7 @@ class ResponseProcessor: | ||||
|             self._source.authentication_flow, | ||||
|             **{ | ||||
|                 PLAN_CONTEXT_PENDING_USER: user, | ||||
|                 PLAN_CONTEXT_AUTHENTICATION_BACKEND: DEFAULT_BACKEND, | ||||
|                 PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_DJANGO, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
| @ -204,7 +204,7 @@ class ResponseProcessor: | ||||
|                 self._source.authentication_flow, | ||||
|                 **{ | ||||
|                     PLAN_CONTEXT_PENDING_USER: matching_users.first(), | ||||
|                     PLAN_CONTEXT_AUTHENTICATION_BACKEND: DEFAULT_BACKEND, | ||||
|                     PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_DJANGO, | ||||
|                     PLAN_CONTEXT_REDIRECT: final_redirect, | ||||
|                 }, | ||||
|             ) | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
| from django.utils.timezone import now | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.core.models import User | ||||
| from authentik.core.models import AuthenticatedSession, User | ||||
| from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus | ||||
| from authentik.lib.utils.time import timedelta_from_string | ||||
| from authentik.root.celery import CELERY_APP | ||||
| @ -31,11 +31,13 @@ def clean_temporary_users(self: MonitoredTask): | ||||
|             continue | ||||
|         source = sources.first() | ||||
|         source_delta = timedelta_from_string(source.temporary_user_delete_after) | ||||
|         if _now - user.last_login >= source_delta: | ||||
|         if ( | ||||
|             _now - user.last_login >= source_delta | ||||
|             and not AuthenticatedSession.objects.filter(user=user).exists() | ||||
|         ): | ||||
|             LOGGER.debug( | ||||
|                 "User is expired and will be deleted.", user=user, delta=source_delta | ||||
|             ) | ||||
|             # TODO: Check if user is signed in anywhere? | ||||
|             user.delete() | ||||
|             deleted_users += 1 | ||||
|     messages.append(f"Successfully deleted {deleted_users} users.") | ||||
|  | ||||
| @ -9,7 +9,7 @@ from rest_framework.permissions import IsAdminUser | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ModelSerializer | ||||
| from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet | ||||
| from rest_framework.viewsets import GenericViewSet, ModelViewSet | ||||
|  | ||||
| from authentik.api.authorization import OwnerFilter, OwnerPermissions | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| @ -94,7 +94,7 @@ class DuoDeviceViewSet( | ||||
|     filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter] | ||||
|  | ||||
|  | ||||
| class DuoAdminDeviceViewSet(ReadOnlyModelViewSet): | ||||
| class DuoAdminDeviceViewSet(ModelViewSet): | ||||
|     """Viewset for Duo authenticator devices (for admins)""" | ||||
|  | ||||
|     permission_classes = [IsAdminUser] | ||||
|  | ||||
| @ -1,44 +1,6 @@ | ||||
| # Generated by Django 3.1.1 on 2020-09-25 14:32 | ||||
|  | ||||
| from django.apps.registry import Apps | ||||
| from django.db import migrations | ||||
| from django.db.backends.base.schema import BaseDatabaseSchemaEditor | ||||
|  | ||||
| from authentik.flows.models import FlowDesignation | ||||
|  | ||||
|  | ||||
| def create_default_setup_flow(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): | ||||
|     Flow = apps.get_model("authentik_flows", "Flow") | ||||
|     FlowStageBinding = apps.get_model("authentik_flows", "FlowStageBinding") | ||||
|  | ||||
|     AuthenticatorDuoStage = apps.get_model( | ||||
|         "authentik_stages_authenticator_duo", "AuthenticatorDuoStage" | ||||
|     ) | ||||
|  | ||||
|     db_alias = schema_editor.connection.alias | ||||
|  | ||||
|     flow, _ = Flow.objects.using(db_alias).update_or_create( | ||||
|         slug="default-authenticator-duo-setup", | ||||
|         designation=FlowDesignation.STAGE_CONFIGURATION, | ||||
|         defaults={ | ||||
|             "name": "default-authenticator-duo-setup", | ||||
|             "title": "Setup Duo", | ||||
|         }, | ||||
|     ) | ||||
|  | ||||
|     stage, _ = AuthenticatorDuoStage.objects.using(db_alias).update_or_create( | ||||
|         name="default-authenticator-duo-setup" | ||||
|     ) | ||||
|  | ||||
|     FlowStageBinding.objects.using(db_alias).update_or_create( | ||||
|         target=flow, stage=stage, defaults={"order": 0} | ||||
|     ) | ||||
|  | ||||
|     for stage in AuthenticatorDuoStage.objects.using(db_alias).filter( | ||||
|         configure_flow=None | ||||
|     ): | ||||
|         stage.configure_flow = flow | ||||
|         stage.save() | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
| @ -50,6 +12,4 @@ class Migration(migrations.Migration): | ||||
|         ), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.RunPython(create_default_setup_flow), | ||||
|     ] | ||||
|     operations = [] | ||||
|  | ||||
| @ -3,6 +3,7 @@ from django.http import HttpRequest, HttpResponse | ||||
| from rest_framework.fields import CharField | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.flows.challenge import ( | ||||
|     Challenge, | ||||
|     ChallengeResponse, | ||||
| @ -11,6 +12,7 @@ from authentik.flows.challenge import ( | ||||
| ) | ||||
| from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER | ||||
| from authentik.flows.stage import ChallengeStageView | ||||
| from authentik.flows.views import InvalidStageError | ||||
| from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| @ -42,7 +44,15 @@ class AuthenticatorDuoStageView(ChallengeStageView): | ||||
|     def get_challenge(self, *args, **kwargs) -> Challenge: | ||||
|         user = self.get_pending_user() | ||||
|         stage: AuthenticatorDuoStage = self.executor.current_stage | ||||
|         try: | ||||
|             enroll = stage.client.enroll(user.username) | ||||
|         except RuntimeError as exc: | ||||
|             Event.new( | ||||
|                 EventAction.CONFIGURATION_ERROR, | ||||
|                 message=f"Failed to enroll user: {str(exc)}", | ||||
|                 user=user, | ||||
|             ).from_http(self.request, user) | ||||
|             raise InvalidStageError(str(exc)) from exc | ||||
|         user_id = enroll["user_id"] | ||||
|         self.request.session[SESSION_KEY_DUO_USER_ID] = user_id | ||||
|         self.request.session[SESSION_KEY_DUO_ACTIVATION_CODE] = enroll[ | ||||
|  | ||||
| @ -3,4 +3,4 @@ | ||||
| INSTALLED_APPS = [ | ||||
|     "django_otp.plugins.otp_totp", | ||||
| ] | ||||
| OTP_TOTP_ISSUER = "authentik" | ||||
| OTP_TOTP_ISSUER = "__to_replace__" | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| """TOTP Setup stage""" | ||||
| from django.http import HttpRequest, HttpResponse | ||||
| from django.http.request import QueryDict | ||||
| from django.utils.text import slugify | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django_otp.plugins.otp_totp.models import TOTPDevice | ||||
| from rest_framework.fields import CharField, IntegerField | ||||
| @ -16,6 +17,7 @@ from authentik.flows.challenge import ( | ||||
| from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER | ||||
| from authentik.flows.stage import ChallengeStageView | ||||
| from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage | ||||
| from authentik.stages.authenticator_totp.settings import OTP_TOTP_ISSUER | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| SESSION_TOTP_DEVICE = "totp_device" | ||||
| @ -54,7 +56,9 @@ class AuthenticatorTOTPStageView(ChallengeStageView): | ||||
|         return AuthenticatorTOTPChallenge( | ||||
|             data={ | ||||
|                 "type": ChallengeTypes.NATIVE.value, | ||||
|                 "config_url": device.config_url, | ||||
|                 "config_url": device.config_url.replace( | ||||
|                     OTP_TOTP_ISSUER, slugify(self.request.tenant.branding_title) | ||||
|                 ), | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|  | ||||
							
								
								
									
										127
									
								
								authentik/stages/authenticator_validate/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								authentik/stages/authenticator_validate/tests.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,127 @@ | ||||
| """Test validator stage""" | ||||
| from unittest.mock import MagicMock, patch | ||||
|  | ||||
| from django.contrib.sessions.middleware import SessionMiddleware | ||||
| from django.test import TestCase | ||||
| from django.test.client import RequestFactory | ||||
| from django_otp.plugins.otp_totp.models import TOTPDevice | ||||
| from rest_framework.exceptions import ValidationError | ||||
|  | ||||
| from authentik.core.models import User | ||||
| from authentik.flows.models import NotConfiguredAction | ||||
| from authentik.flows.tests.test_planner import dummy_get_response | ||||
| from authentik.providers.oauth2.generators import ( | ||||
|     generate_client_id, | ||||
|     generate_client_secret, | ||||
| ) | ||||
| from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice | ||||
| from authentik.stages.authenticator_validate.api import ( | ||||
|     AuthenticatorValidateStageSerializer, | ||||
| ) | ||||
| from authentik.stages.authenticator_validate.challenge import ( | ||||
|     get_challenge_for_device, | ||||
|     validate_challenge_code, | ||||
|     validate_challenge_duo, | ||||
|     validate_challenge_webauthn, | ||||
| ) | ||||
| from authentik.stages.authenticator_webauthn.models import WebAuthnDevice | ||||
|  | ||||
|  | ||||
| class AuthenticatorValidateStageTests(TestCase): | ||||
|     """Test validator stage""" | ||||
|  | ||||
|     def setUp(self) -> None: | ||||
|         self.user = User.objects.get(username="akadmin") | ||||
|         self.request_factory = RequestFactory() | ||||
|  | ||||
|     def test_stage_validation(self): | ||||
|         """Test serializer validation""" | ||||
|         self.client.force_login(self.user) | ||||
|         serializer = AuthenticatorValidateStageSerializer( | ||||
|             data={"name": "foo", "not_configured_action": NotConfiguredAction.CONFIGURE} | ||||
|         ) | ||||
|         self.assertFalse(serializer.is_valid()) | ||||
|         self.assertIn("not_configured_action", serializer.errors) | ||||
|  | ||||
|     def test_device_challenge_totp(self): | ||||
|         """Test device challenge""" | ||||
|         request = self.request_factory.get("/") | ||||
|         totp_device = TOTPDevice.objects.create( | ||||
|             user=self.user, confirmed=True, digits=6 | ||||
|         ) | ||||
|         self.assertEqual(get_challenge_for_device(request, totp_device), {}) | ||||
|         with self.assertRaises(ValidationError): | ||||
|             validate_challenge_code("1234", request, self.user) | ||||
|  | ||||
|     def test_device_challenge_webauthn(self): | ||||
|         """Test webauthn""" | ||||
|         request = self.request_factory.get("/") | ||||
|         request.user = self.user | ||||
|         middleware = SessionMiddleware(dummy_get_response) | ||||
|         middleware.process_request(request) | ||||
|         request.session.save() | ||||
|  | ||||
|         webauthn_device = WebAuthnDevice.objects.create( | ||||
|             user=self.user, | ||||
|             public_key="qwerqwerqre", | ||||
|             credential_id="foobarbaz", | ||||
|             sign_count=0, | ||||
|             rp_id="foo", | ||||
|         ) | ||||
|         challenge = get_challenge_for_device(request, webauthn_device) | ||||
|         del challenge["challenge"] | ||||
|         self.assertEqual( | ||||
|             challenge, | ||||
|             { | ||||
|                 "allowCredentials": [ | ||||
|                     { | ||||
|                         "id": "foobarbaz", | ||||
|                         "transports": ["usb", "nfc", "ble", "internal"], | ||||
|                         "type": "public-key", | ||||
|                     } | ||||
|                 ], | ||||
|                 "rpId": "foo", | ||||
|                 "timeout": 60000, | ||||
|                 "userVerification": "discouraged", | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|         with self.assertRaises(ValidationError): | ||||
|             validate_challenge_webauthn({}, request, self.user) | ||||
|  | ||||
|     def test_device_challenge_duo(self): | ||||
|         """Test duo""" | ||||
|         request = self.request_factory.get("/") | ||||
|         stage = AuthenticatorDuoStage.objects.create( | ||||
|             name="test", | ||||
|             client_id=generate_client_id(), | ||||
|             client_secret=generate_client_secret(), | ||||
|             api_hostname="", | ||||
|         ) | ||||
|         duo_device = DuoDevice.objects.create( | ||||
|             user=self.user, | ||||
|             stage=stage, | ||||
|         ) | ||||
|         duo_mock = MagicMock( | ||||
|             auth=MagicMock( | ||||
|                 return_value={ | ||||
|                     "result": "allow", | ||||
|                     "status": "allow", | ||||
|                     "status_msg": "Success. Logging you in...", | ||||
|                 } | ||||
|             ) | ||||
|         ) | ||||
|         failed_duo_mock = MagicMock(auth=MagicMock(return_value={"result": "deny"})) | ||||
|         with patch( | ||||
|             "authentik.stages.authenticator_duo.models.AuthenticatorDuoStage.client", | ||||
|             duo_mock, | ||||
|         ): | ||||
|             self.assertEqual( | ||||
|                 duo_device.pk, validate_challenge_duo(duo_device.pk, request, self.user) | ||||
|             ) | ||||
|         with patch( | ||||
|             "authentik.stages.authenticator_duo.models.AuthenticatorDuoStage.client", | ||||
|             failed_duo_mock, | ||||
|         ): | ||||
|             with self.assertRaises(ValidationError): | ||||
|                 validate_challenge_duo(duo_device.pk, request, self.user) | ||||
| @ -28,8 +28,6 @@ from authentik.stages.authenticator_webauthn.utils import ( | ||||
|     get_rp_id, | ||||
| ) | ||||
|  | ||||
| RP_NAME = "authentik" | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
| SESSION_KEY_WEBAUTHN_AUTHENTICATED = ( | ||||
| @ -119,7 +117,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView): | ||||
|         user = self.get_pending_user() | ||||
|         make_credential_options = WebAuthnMakeCredentialOptions( | ||||
|             challenge, | ||||
|             RP_NAME, | ||||
|             self.request.tenant.branding_title, | ||||
|             get_rp_id(self.request), | ||||
|             user.uid, | ||||
|             user.username, | ||||
|  | ||||
| @ -0,0 +1,31 @@ | ||||
| # Generated by Django 3.2.4 on 2021-06-14 15:32 | ||||
|  | ||||
| import django.contrib.postgres.fields | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_stages_identification", "0010_identificationstage_password_stage"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name="identificationstage", | ||||
|             name="user_fields", | ||||
|             field=django.contrib.postgres.fields.ArrayField( | ||||
|                 base_field=models.CharField( | ||||
|                     choices=[ | ||||
|                         ("email", "E Mail"), | ||||
|                         ("username", "Username"), | ||||
|                         ("upn", "Upn"), | ||||
|                     ], | ||||
|                     max_length=100, | ||||
|                 ), | ||||
|                 blank=True, | ||||
|                 help_text="Fields of the user object to match against. (Hold shift to select multiple options)", | ||||
|                 size=None, | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @ -17,6 +17,7 @@ class UserFields(models.TextChoices): | ||||
|  | ||||
|     E_MAIL = "email" | ||||
|     USERNAME = "username" | ||||
|     UPN = "upn" | ||||
|  | ||||
|  | ||||
| class IdentificationStage(Stage): | ||||
|  | ||||
| @ -8,19 +8,20 @@ from django.db.models import Q | ||||
| from django.http import HttpResponse | ||||
| from django.urls import reverse | ||||
| from django.utils.translation import gettext as _ | ||||
| from rest_framework.fields import BooleanField, CharField, ListField | ||||
| from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field | ||||
| from rest_framework.fields import BooleanField, CharField, DictField, ListField | ||||
| from rest_framework.serializers import ValidationError | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
| from authentik.core.models import Application, Source, User | ||||
| from authentik.core.types import UILoginButtonSerializer | ||||
| from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes | ||||
| from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER | ||||
| from authentik.flows.stage import ( | ||||
|     PLAN_CONTEXT_PENDING_USER_IDENTIFIER, | ||||
|     ChallengeStageView, | ||||
| ) | ||||
| from authentik.flows.views import SESSION_KEY_APPLICATION_PRE | ||||
| from authentik.flows.views import SESSION_KEY_APPLICATION_PRE, challenge_types | ||||
| from authentik.stages.identification.models import IdentificationStage | ||||
| from authentik.stages.identification.signals import identification_failed | ||||
| from authentik.stages.password.stage import authenticate | ||||
| @ -28,6 +29,26 @@ from authentik.stages.password.stage import authenticate | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| @extend_schema_field( | ||||
|     PolymorphicProxySerializer( | ||||
|         component_name="ChallengeTypes", | ||||
|         serializers=challenge_types(), | ||||
|         resource_type_field_name="component", | ||||
|     ) | ||||
| ) | ||||
| class ChallengeDictWrapper(DictField): | ||||
|     """Wrapper around DictField that annotates itself as challenge proxy""" | ||||
|  | ||||
|  | ||||
| class LoginSourceSerializer(PassiveSerializer): | ||||
|     """Serializer for Login buttons of sources""" | ||||
|  | ||||
|     name = CharField() | ||||
|     icon_url = CharField(required=False, allow_null=True) | ||||
|  | ||||
|     challenge = ChallengeDictWrapper() | ||||
|  | ||||
|  | ||||
| class IdentificationChallenge(Challenge): | ||||
|     """Identification challenges with all UI elements""" | ||||
|  | ||||
| @ -38,7 +59,7 @@ class IdentificationChallenge(Challenge): | ||||
|     enroll_url = CharField(required=False) | ||||
|     recovery_url = CharField(required=False) | ||||
|     primary_action = CharField() | ||||
|     sources = UILoginButtonSerializer(many=True, required=False) | ||||
|     sources = LoginSourceSerializer(many=True, required=False) | ||||
|  | ||||
|     component = CharField(default="ak-stage-identification") | ||||
|  | ||||
| @ -96,7 +117,11 @@ class IdentificationStageView(ChallengeStageView): | ||||
|         current_stage: IdentificationStage = self.executor.current_stage | ||||
|         query = Q() | ||||
|         for search_field in current_stage.user_fields: | ||||
|             model_field = search_field | ||||
|             model_field = { | ||||
|                 "email": "email", | ||||
|                 "username": "username", | ||||
|                 "upn": "attributes__upn", | ||||
|             }[search_field] | ||||
|             if current_stage.case_insensitive_matching: | ||||
|                 model_field += "__iexact" | ||||
|             else: | ||||
|  | ||||
| @ -9,6 +9,7 @@ from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding | ||||
| from authentik.providers.oauth2.generators import generate_client_secret | ||||
| from authentik.sources.oauth.models import OAuthSource | ||||
| from authentik.stages.identification.models import IdentificationStage, UserFields | ||||
| from authentik.stages.password import BACKEND_DJANGO | ||||
| from authentik.stages.password.models import PasswordStage | ||||
|  | ||||
|  | ||||
| @ -70,7 +71,7 @@ class TestIdentificationStage(TestCase): | ||||
|     def test_valid_with_password(self): | ||||
|         """Test with valid email and password in single step""" | ||||
|         pw_stage = PasswordStage.objects.create( | ||||
|             name="password", backends=["django.contrib.auth.backends.ModelBackend"] | ||||
|             name="password", backends=[BACKEND_DJANGO] | ||||
|         ) | ||||
|         self.stage.password_stage = pw_stage | ||||
|         self.stage.save() | ||||
| @ -92,7 +93,7 @@ class TestIdentificationStage(TestCase): | ||||
|     def test_invalid_with_password(self): | ||||
|         """Test with valid email and invalid password in single step""" | ||||
|         pw_stage = PasswordStage.objects.create( | ||||
|             name="password", backends=["django.contrib.auth.backends.ModelBackend"] | ||||
|             name="password", backends=[BACKEND_DJANGO] | ||||
|         ) | ||||
|         self.stage.password_stage = pw_stage | ||||
|         self.stage.save() | ||||
|  | ||||
| @ -17,6 +17,7 @@ from authentik.flows.tests.test_views import TO_STAGE_RESPONSE_MOCK | ||||
| from authentik.flows.views import SESSION_KEY_PLAN | ||||
| from authentik.stages.invitation.models import Invitation, InvitationStage | ||||
| from authentik.stages.invitation.stage import INVITATION_TOKEN_KEY, PLAN_CONTEXT_PROMPT | ||||
| from authentik.stages.password import BACKEND_DJANGO | ||||
| from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND | ||||
|  | ||||
|  | ||||
| @ -46,9 +47,7 @@ class TestUserLoginStage(TestCase): | ||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] | ||||
|         ) | ||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||
|         plan.context[ | ||||
|             PLAN_CONTEXT_AUTHENTICATION_BACKEND | ||||
|         ] = "django.contrib.auth.backends.ModelBackend" | ||||
|         plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO | ||||
|         session = self.client.session | ||||
|         session[SESSION_KEY_PLAN] = plan | ||||
|         session.save() | ||||
| @ -79,9 +78,7 @@ class TestUserLoginStage(TestCase): | ||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] | ||||
|         ) | ||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||
|         plan.context[ | ||||
|             PLAN_CONTEXT_AUTHENTICATION_BACKEND | ||||
|         ] = "django.contrib.auth.backends.ModelBackend" | ||||
|         plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO | ||||
|         session = self.client.session | ||||
|         session[SESSION_KEY_PLAN] = plan | ||||
|         session.save() | ||||
|  | ||||
| @ -0,0 +1,3 @@ | ||||
| """Backend paths""" | ||||
| BACKEND_DJANGO = "django.contrib.auth.backends.ModelBackend" | ||||
| BACKEND_LDAP = "authentik.sources.ldap.auth.LDAPBackend" | ||||
|  | ||||
| @ -9,17 +9,18 @@ from rest_framework.serializers import BaseSerializer | ||||
|  | ||||
| from authentik.core.types import UserSettingSerializer | ||||
| from authentik.flows.models import ConfigurableStage, Stage | ||||
| from authentik.stages.password import BACKEND_DJANGO, BACKEND_LDAP | ||||
|  | ||||
|  | ||||
| def get_authentication_backends(): | ||||
|     """Return all available authentication backends as tuple set""" | ||||
|     return [ | ||||
|         ( | ||||
|             "django.contrib.auth.backends.ModelBackend", | ||||
|             BACKEND_DJANGO, | ||||
|             _("authentik-internal Userdatabase"), | ||||
|         ), | ||||
|         ( | ||||
|             "authentik.sources.ldap.auth.LDAPBackend", | ||||
|             BACKEND_LDAP, | ||||
|             _("authentik LDAP"), | ||||
|         ), | ||||
|     ] | ||||
|  | ||||
| @ -14,6 +14,7 @@ from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan | ||||
| from authentik.flows.tests.test_views import TO_STAGE_RESPONSE_MOCK | ||||
| from authentik.flows.views import SESSION_KEY_PLAN | ||||
| from authentik.providers.oauth2.generators import generate_client_secret | ||||
| from authentik.stages.password import BACKEND_DJANGO | ||||
| from authentik.stages.password.models import PasswordStage | ||||
|  | ||||
| MOCK_BACKEND_AUTHENTICATE = MagicMock(side_effect=PermissionDenied("test")) | ||||
| @ -36,7 +37,7 @@ class TestPasswordStage(TestCase): | ||||
|             designation=FlowDesignation.AUTHENTICATION, | ||||
|         ) | ||||
|         self.stage = PasswordStage.objects.create( | ||||
|             name="password", backends=["django.contrib.auth.backends.ModelBackend"] | ||||
|             name="password", backends=[BACKEND_DJANGO] | ||||
|         ) | ||||
|         FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) | ||||
|  | ||||
|  | ||||
| @ -8,10 +8,10 @@ from structlog.stdlib import get_logger | ||||
| from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER | ||||
| from authentik.flows.stage import StageView | ||||
| from authentik.lib.utils.time import timedelta_from_string | ||||
| from authentik.stages.password import BACKEND_DJANGO | ||||
| from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| DEFAULT_BACKEND = "django.contrib.auth.backends.ModelBackend" | ||||
| USER_LOGIN_AUTHENTICATED = "user_login_authenticated" | ||||
|  | ||||
|  | ||||
| @ -26,7 +26,7 @@ class UserLoginStageView(StageView): | ||||
|             LOGGER.debug(message) | ||||
|             return self.executor.stage_invalid() | ||||
|         backend = self.executor.plan.context.get( | ||||
|             PLAN_CONTEXT_AUTHENTICATION_BACKEND, DEFAULT_BACKEND | ||||
|             PLAN_CONTEXT_AUTHENTICATION_BACKEND, BACKEND_DJANGO | ||||
|         ) | ||||
|         login( | ||||
|             self.request, | ||||
|  | ||||
| @ -9,6 +9,7 @@ from authentik.flows.markers import StageMarker | ||||
| from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding | ||||
| from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan | ||||
| from authentik.flows.views import SESSION_KEY_PLAN | ||||
| from authentik.stages.password import BACKEND_DJANGO | ||||
| from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND | ||||
| from authentik.stages.user_logout.models import UserLogoutStage | ||||
|  | ||||
| @ -35,9 +36,7 @@ class TestUserLogoutStage(TestCase): | ||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] | ||||
|         ) | ||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||
|         plan.context[ | ||||
|             PLAN_CONTEXT_AUTHENTICATION_BACKEND | ||||
|         ] = "django.contrib.auth.backends.ModelBackend" | ||||
|         plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO | ||||
|         session = self.client.session | ||||
|         session[SESSION_KEY_PLAN] = plan | ||||
|         session.save() | ||||
|  | ||||
| @ -9,6 +9,7 @@ from authentik.lib.config import CONFIG | ||||
| from authentik.tenants.models import Tenant | ||||
|  | ||||
| _q_default = Q(default=True) | ||||
| DEFAULT_TENANT = Tenant(domain="fallback") | ||||
|  | ||||
|  | ||||
| def get_tenant_for_request(request: HttpRequest) -> Tenant: | ||||
| @ -17,13 +18,13 @@ def get_tenant_for_request(request: HttpRequest) -> Tenant: | ||||
|         Q(domain__iendswith=request.get_host()) | _q_default | ||||
|     ) | ||||
|     if not db_tenants.exists(): | ||||
|         return Tenant(domain="fallback") | ||||
|         return DEFAULT_TENANT | ||||
|     return db_tenants.first() | ||||
|  | ||||
|  | ||||
| def context_processor(request: HttpRequest) -> dict[str, Any]: | ||||
|     """Context Processor that injects tenant object into every template""" | ||||
|     tenant = getattr(request, "tenant", Tenant(domain="fallback")) | ||||
|     tenant = getattr(request, "tenant", DEFAULT_TENANT) | ||||
|     return { | ||||
|         "tenant": tenant, | ||||
|         "ak_version": __version__, | ||||
|  | ||||
| @ -148,7 +148,7 @@ stages: | ||||
|             inputs: | ||||
|               script: | | ||||
|                 pipenv run python -m scripts.generate_ci_config | ||||
|                 pipenv run ./manage.py migrate | ||||
|                 pipenv run python -m lifecycle.migrate | ||||
|       - job: migrations_from_previous_release | ||||
|         pool: | ||||
|           vmImage: 'ubuntu-latest' | ||||
|  | ||||
| @ -21,7 +21,7 @@ services: | ||||
|     networks: | ||||
|       - internal | ||||
|   server: | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.1-rc3} | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.1} | ||||
|     restart: unless-stopped | ||||
|     command: server | ||||
|     environment: | ||||
| @ -52,7 +52,7 @@ services: | ||||
|       - "0.0.0.0:9000:9000" | ||||
|       - "0.0.0.0:9443:9443" | ||||
|   worker: | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.1-rc3} | ||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.1} | ||||
|     restart: unless-stopped | ||||
|     command: worker | ||||
|     networks: | ||||
|  | ||||
| @ -1,3 +1,3 @@ | ||||
| package constants | ||||
|  | ||||
| const VERSION = "2021.6.1-rc3" | ||||
| const VERSION = "2021.6.1" | ||||
|  | ||||
| @ -3,7 +3,6 @@ package ldap | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/http/cookiejar" | ||||
| @ -66,15 +65,15 @@ func (pi *ProviderInstance) Bind(username string, bindDN, bindPW string, conn ne | ||||
|  | ||||
| 	params := url.Values{} | ||||
| 	params.Add("goauthentik.io/outpost/ldap", "true") | ||||
| 	passed, err := pi.solveFlowChallenge(username, bindPW, apiClient, params.Encode(), 1) | ||||
| 	if err != nil { | ||||
| 	passed, rerr := pi.solveFlowChallenge(username, bindPW, apiClient, params.Encode(), 1) | ||||
| 	if rerr != ldap.LDAPResultSuccess { | ||||
| 		pi.log.WithField("bindDN", bindDN).WithError(err).Warning("failed to solve challenge") | ||||
| 		return ldap.LDAPResultOperationsError, nil | ||||
| 		return rerr, nil | ||||
| 	} | ||||
| 	if !passed { | ||||
| 		return ldap.LDAPResultInvalidCredentials, nil | ||||
| 	} | ||||
| 	p, _, err := apiClient.CoreApi.CoreApplicationsCheckAccessCreate(context.Background(), pi.appSlug).Execute() | ||||
| 	p, _, err := apiClient.CoreApi.CoreApplicationsCheckAccessRetrieve(context.Background(), pi.appSlug).Execute() | ||||
| 	if !p.Passing { | ||||
| 		pi.log.WithField("bindDN", bindDN).Info("Access denied for user") | ||||
| 		return ldap.LDAPResultInsufficientAccessRights, nil | ||||
| @ -138,12 +137,12 @@ type ChallengeInt interface { | ||||
| 	GetResponseErrors() map[string][]api.ErrorDetail | ||||
| } | ||||
|  | ||||
| func (pi *ProviderInstance) solveFlowChallenge(bindDN string, password string, client *api.APIClient, urlParams string, depth int) (bool, error) { | ||||
| func (pi *ProviderInstance) solveFlowChallenge(bindDN string, password string, client *api.APIClient, urlParams string, depth int) (bool, ldap.LDAPResultCode) { | ||||
| 	req := client.FlowsApi.FlowsExecutorGet(context.Background(), pi.flowSlug).Query(urlParams) | ||||
| 	challenge, _, err := req.Execute() | ||||
| 	if err != nil { | ||||
| 		pi.log.WithError(err).Warning("Failed to get challenge") | ||||
| 		return false, err | ||||
| 		return false, ldap.LDAPResultOperationsError | ||||
| 	} | ||||
| 	ch := challenge.GetActualInstance().(ChallengeInt) | ||||
| 	pi.log.WithField("component", ch.GetComponent()).WithField("type", ch.GetType()).Debug("Got challenge") | ||||
| @ -162,45 +161,51 @@ func (pi *ProviderInstance) solveFlowChallenge(bindDN string, password string, c | ||||
| 			} | ||||
| 		} | ||||
| 		if deviceChallenge == nil { | ||||
| 			return false, errors.New("got ak-stage-authenticator-validate without duo") | ||||
| 			pi.log.Warning("got ak-stage-authenticator-validate without duo") | ||||
| 			return false, ldap.LDAPResultOperationsError | ||||
| 		} | ||||
| 		devId, err := strconv.Atoi(deviceChallenge.DeviceUid) | ||||
| 		if err != nil { | ||||
| 			return false, errors.New("failed to convert duo device id to int") | ||||
| 			pi.log.Warning("failed to convert duo device id to int") | ||||
| 			return false, ldap.LDAPResultOperationsError | ||||
| 		} | ||||
| 		devId32 := int32(devId) | ||||
| 		inner := api.NewAuthenticatorValidationChallengeResponseRequest() | ||||
| 		inner.Duo = &devId32 | ||||
| 		responseReq = responseReq.FlowChallengeResponseRequest(api.AuthenticatorValidationChallengeResponseRequestAsFlowChallengeResponseRequest(inner)) | ||||
| 	case "ak-stage-access-denied": | ||||
| 		return false, errors.New("got ak-stage-access-denied") | ||||
| 		pi.log.Info("got ak-stage-access-denied") | ||||
| 		return false, ldap.LDAPResultInsufficientAccessRights | ||||
| 	default: | ||||
| 		return false, fmt.Errorf("unsupported challenge type: %s", ch.GetComponent()) | ||||
| 		pi.log.WithField("component", ch.GetComponent()).Warning("unsupported challenge type") | ||||
| 		return false, ldap.LDAPResultOperationsError | ||||
| 	} | ||||
| 	response, _, err := responseReq.Execute() | ||||
| 	ch = response.GetActualInstance().(ChallengeInt) | ||||
| 	pi.log.WithField("component", ch.GetComponent()).WithField("type", ch.GetType()).Debug("Got response") | ||||
| 	switch ch.GetComponent() { | ||||
| 	case "ak-stage-access-denied": | ||||
| 		return false, errors.New("got ak-stage-access-denied") | ||||
| 		pi.log.Info("got ak-stage-access-denied") | ||||
| 		return false, ldap.LDAPResultInsufficientAccessRights | ||||
| 	} | ||||
| 	if ch.GetType() == "redirect" { | ||||
| 		return true, nil | ||||
| 		return true, ldap.LDAPResultSuccess | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		pi.log.WithError(err).Warning("Failed to submit challenge") | ||||
| 		return false, err | ||||
| 		return false, ldap.LDAPResultOperationsError | ||||
| 	} | ||||
| 	if len(ch.GetResponseErrors()) > 0 { | ||||
| 		for key, errs := range ch.GetResponseErrors() { | ||||
| 			for _, err := range errs { | ||||
| 				pi.log.WithField("key", key).WithField("code", err.Code).WithField("msg", err.String).Warning("Flow error") | ||||
| 				return false, nil | ||||
| 				return false, ldap.LDAPResultInsufficientAccessRights | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if depth >= 10 { | ||||
| 		return false, errors.New("exceeded stage recursion depth") | ||||
| 		pi.log.Warning("exceeded stage recursion depth") | ||||
| 		return false, ldap.LDAPResultOperationsError | ||||
| 	} | ||||
| 	return pi.solveFlowChallenge(bindDN, password, client, urlParams, depth+1) | ||||
| } | ||||
|  | ||||
| @ -5,7 +5,7 @@ import ( | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| const VERSION = "2021.6.1-rc3" | ||||
| const VERSION = "2021.6.1" | ||||
|  | ||||
| func BUILD() string { | ||||
| 	build := os.Getenv("GIT_BUILD_HASH") | ||||
|  | ||||
							
								
								
									
										295
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										295
									
								
								schema.yml
									
									
									
									
									
								
							| @ -1,7 +1,7 @@ | ||||
| openapi: 3.0.3 | ||||
| info: | ||||
|   title: authentik | ||||
|   version: 2021.6.1-rc1 | ||||
|   version: 2021.6.1-rc6 | ||||
|   description: Making authentication simple. | ||||
|   contact: | ||||
|     email: hello@beryju.org | ||||
| @ -236,6 +236,37 @@ paths: | ||||
|           $ref: '#/components/schemas/ValidationError' | ||||
|         '403': | ||||
|           $ref: '#/components/schemas/GenericError' | ||||
|     post: | ||||
|       operationId: authenticators_admin_duo_create | ||||
|       description: Viewset for Duo authenticator devices (for admins) | ||||
|       tags: | ||||
|       - authenticators | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/DuoDeviceRequest' | ||||
|           application/x-www-form-urlencoded: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/DuoDeviceRequest' | ||||
|           multipart/form-data: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/DuoDeviceRequest' | ||||
|         required: true | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       - cookieAuth: [] | ||||
|       responses: | ||||
|         '201': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/DuoDevice' | ||||
|           description: '' | ||||
|         '400': | ||||
|           $ref: '#/components/schemas/ValidationError' | ||||
|         '403': | ||||
|           $ref: '#/components/schemas/GenericError' | ||||
|   /api/v2beta/authenticators/admin/duo/{id}/: | ||||
|     get: | ||||
|       operationId: authenticators_admin_duo_retrieve | ||||
| @ -263,6 +294,103 @@ paths: | ||||
|           $ref: '#/components/schemas/ValidationError' | ||||
|         '403': | ||||
|           $ref: '#/components/schemas/GenericError' | ||||
|     put: | ||||
|       operationId: authenticators_admin_duo_update | ||||
|       description: Viewset for Duo authenticator devices (for admins) | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: id | ||||
|         schema: | ||||
|           type: integer | ||||
|         description: A unique integer value identifying this Duo Device. | ||||
|         required: true | ||||
|       tags: | ||||
|       - authenticators | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/DuoDeviceRequest' | ||||
|           application/x-www-form-urlencoded: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/DuoDeviceRequest' | ||||
|           multipart/form-data: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/DuoDeviceRequest' | ||||
|         required: true | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       - cookieAuth: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/DuoDevice' | ||||
|           description: '' | ||||
|         '400': | ||||
|           $ref: '#/components/schemas/ValidationError' | ||||
|         '403': | ||||
|           $ref: '#/components/schemas/GenericError' | ||||
|     patch: | ||||
|       operationId: authenticators_admin_duo_partial_update | ||||
|       description: Viewset for Duo authenticator devices (for admins) | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: id | ||||
|         schema: | ||||
|           type: integer | ||||
|         description: A unique integer value identifying this Duo Device. | ||||
|         required: true | ||||
|       tags: | ||||
|       - authenticators | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/PatchedDuoDeviceRequest' | ||||
|           application/x-www-form-urlencoded: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/PatchedDuoDeviceRequest' | ||||
|           multipart/form-data: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/PatchedDuoDeviceRequest' | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       - cookieAuth: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/DuoDevice' | ||||
|           description: '' | ||||
|         '400': | ||||
|           $ref: '#/components/schemas/ValidationError' | ||||
|         '403': | ||||
|           $ref: '#/components/schemas/GenericError' | ||||
|     delete: | ||||
|       operationId: authenticators_admin_duo_destroy | ||||
|       description: Viewset for Duo authenticator devices (for admins) | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: id | ||||
|         schema: | ||||
|           type: integer | ||||
|         description: A unique integer value identifying this Duo Device. | ||||
|         required: true | ||||
|       tags: | ||||
|       - authenticators | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       - cookieAuth: [] | ||||
|       responses: | ||||
|         '204': | ||||
|           description: No response body | ||||
|         '400': | ||||
|           $ref: '#/components/schemas/ValidationError' | ||||
|         '403': | ||||
|           $ref: '#/components/schemas/GenericError' | ||||
|   /api/v2beta/authenticators/admin/static/: | ||||
|     get: | ||||
|       operationId: authenticators_admin_static_list | ||||
| @ -1504,10 +1632,14 @@ paths: | ||||
|         '403': | ||||
|           $ref: '#/components/schemas/GenericError' | ||||
|   /api/v2beta/core/applications/{slug}/check_access/: | ||||
|     post: | ||||
|       operationId: core_applications_check_access_create | ||||
|     get: | ||||
|       operationId: core_applications_check_access_retrieve | ||||
|       description: Check access to a single application by slug | ||||
|       parameters: | ||||
|       - in: query | ||||
|         name: for_user | ||||
|         schema: | ||||
|           type: integer | ||||
|       - in: path | ||||
|         name: slug | ||||
|         schema: | ||||
| @ -1516,17 +1648,6 @@ paths: | ||||
|         required: true | ||||
|       tags: | ||||
|       - core | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/CheckAccessRequestRequest' | ||||
|           application/x-www-form-urlencoded: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/CheckAccessRequestRequest' | ||||
|           multipart/form-data: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/CheckAccessRequestRequest' | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       - cookieAuth: [] | ||||
| @ -2717,14 +2838,10 @@ paths: | ||||
|         name: is_active | ||||
|         schema: | ||||
|           type: boolean | ||||
|           title: Active | ||||
|         description: Designates whether this user should be treated as active. Unselect | ||||
|           this instead of deleting accounts. | ||||
|       - in: query | ||||
|         name: is_superuser | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|           type: boolean | ||||
|       - in: query | ||||
|         name: name | ||||
|         schema: | ||||
| @ -3041,7 +3158,7 @@ paths: | ||||
|       - in: query | ||||
|         name: has_key | ||||
|         schema: | ||||
|           type: string | ||||
|           type: boolean | ||||
|         description: Only return certificate-key pairs with keys | ||||
|       - in: query | ||||
|         name: name | ||||
| @ -3429,6 +3546,11 @@ paths: | ||||
|         description: A search term. | ||||
|         schema: | ||||
|           type: string | ||||
|       - in: query | ||||
|         name: tenant_name | ||||
|         schema: | ||||
|           type: string | ||||
|         description: Tenant name | ||||
|       - in: query | ||||
|         name: username | ||||
|         schema: | ||||
| @ -3545,6 +3667,11 @@ paths: | ||||
|         description: A search term. | ||||
|         schema: | ||||
|           type: string | ||||
|       - in: query | ||||
|         name: tenant_name | ||||
|         schema: | ||||
|           type: string | ||||
|         description: Tenant name | ||||
|       - in: query | ||||
|         name: top_n | ||||
|         schema: | ||||
| @ -4609,7 +4736,7 @@ paths: | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/FlowChallengeRequest' | ||||
|                 $ref: '#/components/schemas/ChallengeTypes' | ||||
|           description: '' | ||||
|         '404': | ||||
|           description: No Token found | ||||
| @ -4655,7 +4782,7 @@ paths: | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/FlowChallengeRequest' | ||||
|                 $ref: '#/components/schemas/ChallengeTypes' | ||||
|           description: '' | ||||
|         '400': | ||||
|           $ref: '#/components/schemas/ValidationError' | ||||
| @ -18419,11 +18546,45 @@ components: | ||||
|       - shell | ||||
|       - redirect | ||||
|       type: string | ||||
|     CheckAccessRequestRequest: | ||||
|       type: object | ||||
|       properties: | ||||
|         for_user: | ||||
|           type: integer | ||||
|     ChallengeTypes: | ||||
|       oneOf: | ||||
|       - $ref: '#/components/schemas/AccessDeniedChallenge' | ||||
|       - $ref: '#/components/schemas/AuthenticatorDuoChallenge' | ||||
|       - $ref: '#/components/schemas/AuthenticatorStaticChallenge' | ||||
|       - $ref: '#/components/schemas/AuthenticatorTOTPChallenge' | ||||
|       - $ref: '#/components/schemas/AuthenticatorValidationChallenge' | ||||
|       - $ref: '#/components/schemas/AuthenticatorWebAuthnChallenge' | ||||
|       - $ref: '#/components/schemas/AutosubmitChallenge' | ||||
|       - $ref: '#/components/schemas/CaptchaChallenge' | ||||
|       - $ref: '#/components/schemas/ConsentChallenge' | ||||
|       - $ref: '#/components/schemas/DummyChallenge' | ||||
|       - $ref: '#/components/schemas/EmailChallenge' | ||||
|       - $ref: '#/components/schemas/IdentificationChallenge' | ||||
|       - $ref: '#/components/schemas/PasswordChallenge' | ||||
|       - $ref: '#/components/schemas/PlexAuthenticationChallenge' | ||||
|       - $ref: '#/components/schemas/PromptChallenge' | ||||
|       - $ref: '#/components/schemas/RedirectChallenge' | ||||
|       - $ref: '#/components/schemas/ShellChallenge' | ||||
|       discriminator: | ||||
|         propertyName: component | ||||
|         mapping: | ||||
|           ak-stage-access-denied: '#/components/schemas/AccessDeniedChallenge' | ||||
|           ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallenge' | ||||
|           ak-stage-authenticator-static: '#/components/schemas/AuthenticatorStaticChallenge' | ||||
|           ak-stage-authenticator-totp: '#/components/schemas/AuthenticatorTOTPChallenge' | ||||
|           ak-stage-authenticator-validate: '#/components/schemas/AuthenticatorValidationChallenge' | ||||
|           ak-stage-authenticator-webauthn: '#/components/schemas/AuthenticatorWebAuthnChallenge' | ||||
|           ak-stage-autosubmit: '#/components/schemas/AutosubmitChallenge' | ||||
|           ak-stage-captcha: '#/components/schemas/CaptchaChallenge' | ||||
|           ak-stage-consent: '#/components/schemas/ConsentChallenge' | ||||
|           ak-stage-dummy: '#/components/schemas/DummyChallenge' | ||||
|           ak-stage-email: '#/components/schemas/EmailChallenge' | ||||
|           ak-stage-identification: '#/components/schemas/IdentificationChallenge' | ||||
|           ak-stage-password: '#/components/schemas/PasswordChallenge' | ||||
|           ak-flow-sources-plex: '#/components/schemas/PlexAuthenticationChallenge' | ||||
|           ak-stage-prompt: '#/components/schemas/PromptChallenge' | ||||
|           xak-flow-redirect: '#/components/schemas/RedirectChallenge' | ||||
|           xak-flow-shell: '#/components/schemas/ShellChallenge' | ||||
|     ClientTypeEnum: | ||||
|       enum: | ||||
|       - confidential | ||||
| @ -19097,6 +19258,9 @@ components: | ||||
|         expires: | ||||
|           type: string | ||||
|           format: date-time | ||||
|         tenant: | ||||
|           type: object | ||||
|           additionalProperties: {} | ||||
|       required: | ||||
|       - action | ||||
|       - app | ||||
| @ -19169,6 +19333,7 @@ components: | ||||
|       - property_mapping_exception | ||||
|       - system_task_execution | ||||
|       - system_task_exception | ||||
|       - system_exception | ||||
|       - configuration_error | ||||
|       - model_created | ||||
|       - model_updated | ||||
| @ -19222,6 +19387,9 @@ components: | ||||
|         expires: | ||||
|           type: string | ||||
|           format: date-time | ||||
|         tenant: | ||||
|           type: object | ||||
|           additionalProperties: {} | ||||
|       required: | ||||
|       - action | ||||
|       - app | ||||
| @ -19387,45 +19555,6 @@ components: | ||||
|       - slug | ||||
|       - stages | ||||
|       - title | ||||
|     FlowChallengeRequest: | ||||
|       oneOf: | ||||
|       - $ref: '#/components/schemas/AccessDeniedChallenge' | ||||
|       - $ref: '#/components/schemas/AuthenticatorDuoChallenge' | ||||
|       - $ref: '#/components/schemas/AuthenticatorStaticChallenge' | ||||
|       - $ref: '#/components/schemas/AuthenticatorTOTPChallenge' | ||||
|       - $ref: '#/components/schemas/AuthenticatorValidationChallenge' | ||||
|       - $ref: '#/components/schemas/AuthenticatorWebAuthnChallenge' | ||||
|       - $ref: '#/components/schemas/AutosubmitChallenge' | ||||
|       - $ref: '#/components/schemas/CaptchaChallenge' | ||||
|       - $ref: '#/components/schemas/ConsentChallenge' | ||||
|       - $ref: '#/components/schemas/DummyChallenge' | ||||
|       - $ref: '#/components/schemas/EmailChallenge' | ||||
|       - $ref: '#/components/schemas/IdentificationChallenge' | ||||
|       - $ref: '#/components/schemas/PasswordChallenge' | ||||
|       - $ref: '#/components/schemas/PlexAuthenticationChallenge' | ||||
|       - $ref: '#/components/schemas/PromptChallenge' | ||||
|       - $ref: '#/components/schemas/RedirectChallenge' | ||||
|       - $ref: '#/components/schemas/ShellChallenge' | ||||
|       discriminator: | ||||
|         propertyName: component | ||||
|         mapping: | ||||
|           ak-stage-access-denied: '#/components/schemas/AccessDeniedChallenge' | ||||
|           ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallenge' | ||||
|           ak-stage-authenticator-static: '#/components/schemas/AuthenticatorStaticChallenge' | ||||
|           ak-stage-authenticator-totp: '#/components/schemas/AuthenticatorTOTPChallenge' | ||||
|           ak-stage-authenticator-validate: '#/components/schemas/AuthenticatorValidationChallenge' | ||||
|           ak-stage-authenticator-webauthn: '#/components/schemas/AuthenticatorWebAuthnChallenge' | ||||
|           ak-stage-autosubmit: '#/components/schemas/AutosubmitChallenge' | ||||
|           ak-stage-captcha: '#/components/schemas/CaptchaChallenge' | ||||
|           ak-stage-consent: '#/components/schemas/ConsentChallenge' | ||||
|           ak-stage-dummy: '#/components/schemas/DummyChallenge' | ||||
|           ak-stage-email: '#/components/schemas/EmailChallenge' | ||||
|           ak-stage-identification: '#/components/schemas/IdentificationChallenge' | ||||
|           ak-stage-password: '#/components/schemas/PasswordChallenge' | ||||
|           ak-flow-sources-plex: '#/components/schemas/PlexAuthenticationChallenge' | ||||
|           ak-stage-prompt: '#/components/schemas/PromptChallenge' | ||||
|           xak-flow-redirect: '#/components/schemas/RedirectChallenge' | ||||
|           xak-flow-shell: '#/components/schemas/ShellChallenge' | ||||
|     FlowChallengeResponseRequest: | ||||
|       oneOf: | ||||
|       - $ref: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest' | ||||
| @ -19775,7 +19904,7 @@ components: | ||||
|         sources: | ||||
|           type: array | ||||
|           items: | ||||
|             $ref: '#/components/schemas/UILoginButton' | ||||
|             $ref: '#/components/schemas/LoginSource' | ||||
|       required: | ||||
|       - password_fields | ||||
|       - primary_action | ||||
| @ -20471,6 +20600,20 @@ components: | ||||
|       required: | ||||
|       - logins_failed_per_1h | ||||
|       - logins_per_1h | ||||
|     LoginSource: | ||||
|       type: object | ||||
|       description: Serializer for Login buttons of sources | ||||
|       properties: | ||||
|         name: | ||||
|           type: string | ||||
|         icon_url: | ||||
|           type: string | ||||
|           nullable: true | ||||
|         challenge: | ||||
|           $ref: '#/components/schemas/ChallengeTypes' | ||||
|       required: | ||||
|       - challenge | ||||
|       - name | ||||
|     NameIdPolicyEnum: | ||||
|       enum: | ||||
|       - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress | ||||
| @ -26137,6 +26280,7 @@ components: | ||||
|         forward_auth_mode: | ||||
|           type: boolean | ||||
|           readOnly: true | ||||
|           deprecated: true | ||||
|       required: | ||||
|       - external_host | ||||
|       - forward_auth_mode | ||||
| @ -27524,21 +27668,6 @@ components: | ||||
|       - description | ||||
|       - model_name | ||||
|       - name | ||||
|     UILoginButton: | ||||
|       type: object | ||||
|       description: Serializer for Login buttons of sources | ||||
|       properties: | ||||
|         name: | ||||
|           type: string | ||||
|         challenge: | ||||
|           type: object | ||||
|           additionalProperties: {} | ||||
|         icon_url: | ||||
|           type: string | ||||
|           nullable: true | ||||
|       required: | ||||
|       - challenge | ||||
|       - name | ||||
|     UsedBy: | ||||
|       type: object | ||||
|       description: A list of all objects referencing the queried object | ||||
| @ -27687,6 +27816,7 @@ components: | ||||
|       enum: | ||||
|       - email | ||||
|       - username | ||||
|       - upn | ||||
|       type: string | ||||
|     UserLoginStage: | ||||
|       type: object | ||||
| @ -27917,7 +28047,6 @@ components: | ||||
|           type: string | ||||
|       required: | ||||
|       - component | ||||
|       - configure_url | ||||
|       - object_uid | ||||
|       - title | ||||
|     UserWriteStage: | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| env | ||||
| helm | ||||
| static | ||||
| htmlcov | ||||
| *.env.yml | ||||
|  | ||||
							
								
								
									
										398
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										398
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -9,14 +9,14 @@ | ||||
|             "version": "0.0.0", | ||||
|             "license": "GNU GPLv3", | ||||
|             "dependencies": { | ||||
|                 "@babel/core": "^7.14.5", | ||||
|                 "@babel/core": "^7.14.6", | ||||
|                 "@babel/plugin-proposal-decorators": "^7.14.5", | ||||
|                 "@babel/plugin-transform-runtime": "^7.14.5", | ||||
|                 "@babel/preset-env": "^7.14.5", | ||||
|                 "@babel/preset-typescript": "^7.14.5", | ||||
|                 "@fortawesome/fontawesome-free": "^5.15.3", | ||||
|                 "@lingui/cli": "^3.10.2", | ||||
|                 "@lingui/core": "^3.10.2", | ||||
|                 "@lingui/core": "^3.10.4", | ||||
|                 "@lingui/macro": "^3.10.2", | ||||
|                 "@patternfly/patternfly": "^4.108.2", | ||||
|                 "@polymer/iron-form": "^3.0.1", | ||||
| @ -24,13 +24,13 @@ | ||||
|                 "@rollup/plugin-babel": "^5.3.0", | ||||
|                 "@rollup/plugin-replace": "^2.4.2", | ||||
|                 "@rollup/plugin-typescript": "^8.2.1", | ||||
|                 "@sentry/browser": "^6.5.1", | ||||
|                 "@sentry/tracing": "^6.5.1", | ||||
|                 "@sentry/browser": "^6.7.1", | ||||
|                 "@sentry/tracing": "^6.7.1", | ||||
|                 "@types/chart.js": "^2.9.32", | ||||
|                 "@types/codemirror": "5.60.0", | ||||
|                 "@types/grecaptcha": "^3.0.2", | ||||
|                 "@typescript-eslint/eslint-plugin": "^4.26.1", | ||||
|                 "@typescript-eslint/parser": "^4.26.1", | ||||
|                 "@typescript-eslint/eslint-plugin": "^4.27.0", | ||||
|                 "@typescript-eslint/parser": "^4.27.0", | ||||
|                 "@webcomponents/webcomponentsjs": "^2.5.0", | ||||
|                 "authentik-api": "file:api", | ||||
|                 "babel-plugin-macros": "^3.1.0", | ||||
| @ -48,7 +48,7 @@ | ||||
|                 "lit-html": "^1.4.1", | ||||
|                 "moment": "^2.29.1", | ||||
|                 "rapidoc": "^9.0.0", | ||||
|                 "rollup": "^2.51.1", | ||||
|                 "rollup": "^2.52.1", | ||||
|                 "rollup-plugin-commonjs": "^10.1.0", | ||||
|                 "rollup-plugin-copy": "^3.4.0", | ||||
|                 "rollup-plugin-cssimport": "^1.0.2", | ||||
| @ -57,8 +57,8 @@ | ||||
|                 "rollup-plugin-sourcemaps": "^0.6.3", | ||||
|                 "rollup-plugin-terser": "^7.0.2", | ||||
|                 "ts-lit-plugin": "^1.2.1", | ||||
|                 "tslib": "^2.2.0", | ||||
|                 "typescript": "^4.3.2", | ||||
|                 "tslib": "^2.3.0", | ||||
|                 "typescript": "^4.3.3", | ||||
|                 "webcomponent-qr-code": "^1.0.5", | ||||
|                 "yaml": "^1.10.2" | ||||
|             } | ||||
| @ -110,16 +110,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@babel/core": { | ||||
|             "version": "7.14.5", | ||||
|             "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.5.tgz", | ||||
|             "integrity": "sha512-RN/AwP2DJmQTZSfiDaD+JQQ/J99KsIpOCfBE5pL+5jJSt7nI3nYGoAXZu+ffYSQ029NLs2DstZb+eR81uuARgg==", | ||||
|             "version": "7.14.6", | ||||
|             "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.6.tgz", | ||||
|             "integrity": "sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA==", | ||||
|             "dependencies": { | ||||
|                 "@babel/code-frame": "^7.14.5", | ||||
|                 "@babel/generator": "^7.14.5", | ||||
|                 "@babel/helper-compilation-targets": "^7.14.5", | ||||
|                 "@babel/helper-module-transforms": "^7.14.5", | ||||
|                 "@babel/helpers": "^7.14.5", | ||||
|                 "@babel/parser": "^7.14.5", | ||||
|                 "@babel/helpers": "^7.14.6", | ||||
|                 "@babel/parser": "^7.14.6", | ||||
|                 "@babel/template": "^7.14.5", | ||||
|                 "@babel/traverse": "^7.14.5", | ||||
|                 "@babel/types": "^7.14.5", | ||||
| @ -479,9 +479,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@babel/helpers": { | ||||
|             "version": "7.14.5", | ||||
|             "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.5.tgz", | ||||
|             "integrity": "sha512-xtcWOuN9VL6nApgVHtq3PPcQv5qFBJzoSZzJ/2c0QK/IP/gxVcoWSNQwFEGvmbQsuS9rhYqjILDGGXcTkA705Q==", | ||||
|             "version": "7.14.6", | ||||
|             "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.6.tgz", | ||||
|             "integrity": "sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA==", | ||||
|             "dependencies": { | ||||
|                 "@babel/template": "^7.14.5", | ||||
|                 "@babel/traverse": "^7.14.5", | ||||
| @ -505,9 +505,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@babel/parser": { | ||||
|             "version": "7.14.5", | ||||
|             "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", | ||||
|             "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==", | ||||
|             "version": "7.14.6", | ||||
|             "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", | ||||
|             "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==", | ||||
|             "bin": { | ||||
|                 "parser": "bin/babel-parser.js" | ||||
|             }, | ||||
| @ -2047,9 +2047,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@lingui/core": { | ||||
|             "version": "3.10.2", | ||||
|             "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.10.2.tgz", | ||||
|             "integrity": "sha512-RL4Bn1s4Ukd0GBlEFO/+GoxnK48xuZR2GwFslg/eQxcQmeOA4c1Jw/tbvMKDfi40XwqMBWcS0G2njpWjuHC3SA==", | ||||
|             "version": "3.10.4", | ||||
|             "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.10.4.tgz", | ||||
|             "integrity": "sha512-V9QKQ9PFMTPrGGz2PaeKHZcxFikQZzJbptyQbVFJdXaKhdE2RH6HhdK1PIziDHqp6ZWPthVIfVLURT3ku8eu5w==", | ||||
|             "dependencies": { | ||||
|                 "@babel/runtime": "^7.11.2", | ||||
|                 "make-plural": "^6.2.2", | ||||
| @ -2314,13 +2314,13 @@ | ||||
|             "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" | ||||
|         }, | ||||
|         "node_modules/@sentry/browser": { | ||||
|             "version": "6.5.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.5.1.tgz", | ||||
|             "integrity": "sha512-iVLCdEFwsoWAzE/hNknexPQjjDpMQV7mmaq9Z1P63bD6MfhwVTx4hG4pHn8HEvC38VvCVf1wv0v/LxtoODAYXg==", | ||||
|             "version": "6.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.1.tgz", | ||||
|             "integrity": "sha512-R5PYx4TTvifcU790XkK6JVGwavKaXwycDU0MaAwfc4Vf7BLm5KCNJCsDySu1RPAap/017MVYf54p6dWvKiRviA==", | ||||
|             "dependencies": { | ||||
|                 "@sentry/core": "6.5.1", | ||||
|                 "@sentry/types": "6.5.1", | ||||
|                 "@sentry/utils": "6.5.1", | ||||
|                 "@sentry/core": "6.7.1", | ||||
|                 "@sentry/types": "6.7.1", | ||||
|                 "@sentry/utils": "6.7.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -2333,14 +2333,14 @@ | ||||
|             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" | ||||
|         }, | ||||
|         "node_modules/@sentry/core": { | ||||
|             "version": "6.5.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.5.1.tgz", | ||||
|             "integrity": "sha512-Mh3sl/iUOT1myHmM6RlDy2ARzkUClx/g4DAt1rJ/IpQBOlDYQraplXSIW80i/hzRgQDfwhwgf4wUa5DicKBjKw==", | ||||
|             "version": "6.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.1.tgz", | ||||
|             "integrity": "sha512-VAv8OR/7INn2JfiLcuop4hfDcyC7mfL9fdPndQEhlacjmw8gRrgXjR7qyhnCTgzFLkHI7V5bcdIzA83TRPYQpA==", | ||||
|             "dependencies": { | ||||
|                 "@sentry/hub": "6.5.1", | ||||
|                 "@sentry/minimal": "6.5.1", | ||||
|                 "@sentry/types": "6.5.1", | ||||
|                 "@sentry/utils": "6.5.1", | ||||
|                 "@sentry/hub": "6.7.1", | ||||
|                 "@sentry/minimal": "6.7.1", | ||||
|                 "@sentry/types": "6.7.1", | ||||
|                 "@sentry/utils": "6.7.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -2353,12 +2353,12 @@ | ||||
|             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" | ||||
|         }, | ||||
|         "node_modules/@sentry/hub": { | ||||
|             "version": "6.5.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.5.1.tgz", | ||||
|             "integrity": "sha512-lBRMBVMYP8B4PfRiM70murbtJAXiIAao/asDEMIRNGMP6pI2ArqXfJCBYDkStukhikYD0Kqb4trXq+JYF07Hbg==", | ||||
|             "version": "6.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.1.tgz", | ||||
|             "integrity": "sha512-eVCTWvvcp6xa0A5GGNHMQEWslmKPlisE5rGmsV/kjvSUv3zSrI0eIDfb51ikdnCiBjHpK2NBWP8Vy8cZOEJegg==", | ||||
|             "dependencies": { | ||||
|                 "@sentry/types": "6.5.1", | ||||
|                 "@sentry/utils": "6.5.1", | ||||
|                 "@sentry/types": "6.7.1", | ||||
|                 "@sentry/utils": "6.7.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -2371,12 +2371,12 @@ | ||||
|             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" | ||||
|         }, | ||||
|         "node_modules/@sentry/minimal": { | ||||
|             "version": "6.5.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.5.1.tgz", | ||||
|             "integrity": "sha512-q9Do/oreu1RP695CXCLowVDuQyk7ilE6FGdz2QLpTXAfx8247qOwk6+zy9Kea/Djk93+BoSDVQUSneNiVwl0nQ==", | ||||
|             "version": "6.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.1.tgz", | ||||
|             "integrity": "sha512-HDDPEnQRD6hC0qaHdqqKDStcdE1KhkFh0RCtJNMCDn0zpav8Dj9AteF70x6kLSlliAJ/JFwi6AmQrLz+FxPexw==", | ||||
|             "dependencies": { | ||||
|                 "@sentry/hub": "6.5.1", | ||||
|                 "@sentry/types": "6.5.1", | ||||
|                 "@sentry/hub": "6.7.1", | ||||
|                 "@sentry/types": "6.7.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -2389,14 +2389,14 @@ | ||||
|             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" | ||||
|         }, | ||||
|         "node_modules/@sentry/tracing": { | ||||
|             "version": "6.5.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.5.1.tgz", | ||||
|             "integrity": "sha512-y1W/xFC2hAuKqSuuaovkElHY4pbli3XoXrreesg8PtO7ilX6ZbatOQbHsEsHQyoUv0F6aVA+MABOxWH2jt7tfw==", | ||||
|             "version": "6.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.1.tgz", | ||||
|             "integrity": "sha512-wyS3nWNl5mzaC1qZ2AIp1hjXnfO9EERjMIJjCihs2LWBz1r3efxrHxJHs8wXlNWvrT3KLhq/7vvF5CdU82uPeQ==", | ||||
|             "dependencies": { | ||||
|                 "@sentry/hub": "6.5.1", | ||||
|                 "@sentry/minimal": "6.5.1", | ||||
|                 "@sentry/types": "6.5.1", | ||||
|                 "@sentry/utils": "6.5.1", | ||||
|                 "@sentry/hub": "6.7.1", | ||||
|                 "@sentry/minimal": "6.7.1", | ||||
|                 "@sentry/types": "6.7.1", | ||||
|                 "@sentry/utils": "6.7.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -2409,19 +2409,19 @@ | ||||
|             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" | ||||
|         }, | ||||
|         "node_modules/@sentry/types": { | ||||
|             "version": "6.5.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.1.tgz", | ||||
|             "integrity": "sha512-b/7a6CMoytaeFPx4IBjfxPw3nPvsQh7ui1C8Vw0LxNNDgBwVhPLzUOWeLWbo5YZCVbGEMIWwtCUQYWxneceZSA==", | ||||
|             "version": "6.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.1.tgz", | ||||
|             "integrity": "sha512-9AO7HKoip2MBMNQJEd6+AKtjj2+q9Ze4ooWUdEvdOVSt5drg7BGpK221/p9JEOyJAZwEPEXdcMd3VAIMiOb4MA==", | ||||
|             "engines": { | ||||
|                 "node": ">=6" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@sentry/utils": { | ||||
|             "version": "6.5.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.5.1.tgz", | ||||
|             "integrity": "sha512-Wv86JYGQH+ZJ5XGFQX7h6ijl32667ikenoL9EyXMn8UoOYX/MLwZoQZin1P60wmKkYR9ifTNVmpaI9OoTaH+UQ==", | ||||
|             "version": "6.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.1.tgz", | ||||
|             "integrity": "sha512-Tq2otdbWlHAkctD+EWTYKkEx6BL1Qn3Z/imkO06/PvzpWvVhJWQ5qHAzz5XnwwqNHyV03KVzYB6znq1Bea9HuA==", | ||||
|             "dependencies": { | ||||
|                 "@sentry/types": "6.5.1", | ||||
|                 "@sentry/types": "6.7.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -2579,12 +2579,12 @@ | ||||
|             "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/eslint-plugin": { | ||||
|             "version": "4.26.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.26.1.tgz", | ||||
|             "integrity": "sha512-aoIusj/8CR+xDWmZxARivZjbMBQTT9dImUtdZ8tVCVRXgBUuuZyM5Of5A9D9arQPxbi/0rlJLcuArclz/rCMJw==", | ||||
|             "version": "4.27.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.27.0.tgz", | ||||
|             "integrity": "sha512-DsLqxeUfLVNp3AO7PC3JyaddmEHTtI9qTSAs+RB6ja27QvIM0TA8Cizn1qcS6vOu+WDLFJzkwkgweiyFhssDdQ==", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/experimental-utils": "4.26.1", | ||||
|                 "@typescript-eslint/scope-manager": "4.26.1", | ||||
|                 "@typescript-eslint/experimental-utils": "4.27.0", | ||||
|                 "@typescript-eslint/scope-manager": "4.27.0", | ||||
|                 "debug": "^4.3.1", | ||||
|                 "functional-red-black-tree": "^1.0.1", | ||||
|                 "lodash": "^4.17.21", | ||||
| @ -2610,14 +2610,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/experimental-utils": { | ||||
|             "version": "4.26.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.26.1.tgz", | ||||
|             "integrity": "sha512-sQHBugRhrXzRCs9PaGg6rowie4i8s/iD/DpTB+EXte8OMDfdCG5TvO73XlO9Wc/zi0uyN4qOmX9hIjQEyhnbmQ==", | ||||
|             "version": "4.27.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.27.0.tgz", | ||||
|             "integrity": "sha512-n5NlbnmzT2MXlyT+Y0Jf0gsmAQzCnQSWXKy4RGSXVStjDvS5we9IWbh7qRVKdGcxT0WYlgcCYUK/HRg7xFhvjQ==", | ||||
|             "dependencies": { | ||||
|                 "@types/json-schema": "^7.0.7", | ||||
|                 "@typescript-eslint/scope-manager": "4.26.1", | ||||
|                 "@typescript-eslint/types": "4.26.1", | ||||
|                 "@typescript-eslint/typescript-estree": "4.26.1", | ||||
|                 "@typescript-eslint/scope-manager": "4.27.0", | ||||
|                 "@typescript-eslint/types": "4.27.0", | ||||
|                 "@typescript-eslint/typescript-estree": "4.27.0", | ||||
|                 "eslint-scope": "^5.1.1", | ||||
|                 "eslint-utils": "^3.0.0" | ||||
|             }, | ||||
| @ -2650,13 +2650,13 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/parser": { | ||||
|             "version": "4.26.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.26.1.tgz", | ||||
|             "integrity": "sha512-q7F3zSo/nU6YJpPJvQveVlIIzx9/wu75lr6oDbDzoeIRWxpoc/HQ43G4rmMoCc5my/3uSj2VEpg/D83LYZF5HQ==", | ||||
|             "version": "4.27.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.27.0.tgz", | ||||
|             "integrity": "sha512-XpbxL+M+gClmJcJ5kHnUpBGmlGdgNvy6cehgR6ufyxkEJMGP25tZKCaKyC0W/JVpuhU3VU1RBn7SYUPKSMqQvQ==", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/scope-manager": "4.26.1", | ||||
|                 "@typescript-eslint/types": "4.26.1", | ||||
|                 "@typescript-eslint/typescript-estree": "4.26.1", | ||||
|                 "@typescript-eslint/scope-manager": "4.27.0", | ||||
|                 "@typescript-eslint/types": "4.27.0", | ||||
|                 "@typescript-eslint/typescript-estree": "4.27.0", | ||||
|                 "debug": "^4.3.1" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -2676,12 +2676,12 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/scope-manager": { | ||||
|             "version": "4.26.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.26.1.tgz", | ||||
|             "integrity": "sha512-TW1X2p62FQ8Rlne+WEShyd7ac2LA6o27S9i131W4NwDSfyeVlQWhw8ylldNNS8JG6oJB9Ha9Xyc+IUcqipvheQ==", | ||||
|             "version": "4.27.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.27.0.tgz", | ||||
|             "integrity": "sha512-DY73jK6SEH6UDdzc6maF19AHQJBFVRf6fgAXHPXCGEmpqD4vYgPEzqpFz1lf/daSbOcMpPPj9tyXXDPW2XReAw==", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "4.26.1", | ||||
|                 "@typescript-eslint/visitor-keys": "4.26.1" | ||||
|                 "@typescript-eslint/types": "4.27.0", | ||||
|                 "@typescript-eslint/visitor-keys": "4.27.0" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": "^8.10.0 || ^10.13.0 || >=11.10.1" | ||||
| @ -2692,9 +2692,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/types": { | ||||
|             "version": "4.26.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.26.1.tgz", | ||||
|             "integrity": "sha512-STyMPxR3cS+LaNvS8yK15rb8Y0iL0tFXq0uyl6gY45glyI7w0CsyqyEXl/Fa0JlQy+pVANeK3sbwPneCbWE7yg==", | ||||
|             "version": "4.27.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.27.0.tgz", | ||||
|             "integrity": "sha512-I4ps3SCPFCKclRcvnsVA/7sWzh7naaM/b4pBO2hVxnM3wrU51Lveybdw5WoIktU/V4KfXrTt94V9b065b/0+wA==", | ||||
|             "engines": { | ||||
|                 "node": "^8.10.0 || ^10.13.0 || >=11.10.1" | ||||
|             }, | ||||
| @ -2704,12 +2704,12 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/typescript-estree": { | ||||
|             "version": "4.26.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.26.1.tgz", | ||||
|             "integrity": "sha512-l3ZXob+h0NQzz80lBGaykdScYaiEbFqznEs99uwzm8fPHhDjwaBFfQkjUC/slw6Sm7npFL8qrGEAMxcfBsBJUg==", | ||||
|             "version": "4.27.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.27.0.tgz", | ||||
|             "integrity": "sha512-KH03GUsUj41sRLLEy2JHstnezgpS5VNhrJouRdmh6yNdQ+yl8w5LrSwBkExM+jWwCJa7Ct2c8yl8NdtNRyQO6g==", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "4.26.1", | ||||
|                 "@typescript-eslint/visitor-keys": "4.26.1", | ||||
|                 "@typescript-eslint/types": "4.27.0", | ||||
|                 "@typescript-eslint/visitor-keys": "4.27.0", | ||||
|                 "debug": "^4.3.1", | ||||
|                 "globby": "^11.0.3", | ||||
|                 "is-glob": "^4.0.1", | ||||
| @ -2749,11 +2749,11 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/@typescript-eslint/visitor-keys": { | ||||
|             "version": "4.26.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.26.1.tgz", | ||||
|             "integrity": "sha512-IGouNSSd+6x/fHtYRyLOM6/C+QxMDzWlDtN41ea+flWuSF9g02iqcIlX8wM53JkfljoIjP0U+yp7SiTS1onEkw==", | ||||
|             "version": "4.27.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.27.0.tgz", | ||||
|             "integrity": "sha512-es0GRYNZp0ieckZ938cEANfEhsfHrzuLrePukLKtY3/KPXcq1Xd555Mno9/GOgXhKzn0QfkDLVgqWO3dGY80bg==", | ||||
|             "dependencies": { | ||||
|                 "@typescript-eslint/types": "4.26.1", | ||||
|                 "@typescript-eslint/types": "4.27.0", | ||||
|                 "eslint-visitor-keys": "^2.0.0" | ||||
|             }, | ||||
|             "engines": { | ||||
| @ -6771,9 +6771,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/rollup": { | ||||
|             "version": "2.51.1", | ||||
|             "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.51.1.tgz", | ||||
|             "integrity": "sha512-8xfDbAtBleXotb6qKEHWuo/jkn94a9dVqGc7Rwl3sqspCVlnCfbRek7ldhCARSi7h32H0xR4QThm1t9zHN+3uw==", | ||||
|             "version": "2.52.1", | ||||
|             "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.1.tgz", | ||||
|             "integrity": "sha512-/SPqz8UGnp4P1hq6wc9gdTqA2bXQXGx13TtoL03GBm6qGRI6Hm3p4Io7GeiHNLl0BsQAne1JNYY+q/apcY933w==", | ||||
|             "bin": { | ||||
|                 "rollup": "dist/bin/rollup" | ||||
|             }, | ||||
| @ -6781,7 +6781,7 @@ | ||||
|                 "node": ">=10.0.0" | ||||
|             }, | ||||
|             "optionalDependencies": { | ||||
|                 "fsevents": "~2.3.1" | ||||
|                 "fsevents": "~2.3.2" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/rollup-plugin-commonjs": { | ||||
| @ -7570,9 +7570,9 @@ | ||||
|             "integrity": "sha512-zKmsCQs4dZaeSKjEA7pLFDv7FHHqAFLPd0Mr//OIJvu8M+4p4bgSFJwZSEBEg3ec9W7RzRz1vi8giiX0+mheBQ==" | ||||
|         }, | ||||
|         "node_modules/tslib": { | ||||
|             "version": "2.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", | ||||
|             "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" | ||||
|             "version": "2.3.0", | ||||
|             "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", | ||||
|             "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" | ||||
|         }, | ||||
|         "node_modules/tsutils": { | ||||
|             "version": "3.21.0", | ||||
| @ -7605,9 +7605,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/typescript": { | ||||
|             "version": "4.3.2", | ||||
|             "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", | ||||
|             "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", | ||||
|             "version": "4.3.3", | ||||
|             "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.3.tgz", | ||||
|             "integrity": "sha512-rUvLW0WtF7PF2b9yenwWUi9Da9euvDRhmH7BLyBG4DCFfOJ850LGNknmRpp8Z8kXNUPObdZQEfKOiHtXuQHHKA==", | ||||
|             "bin": { | ||||
|                 "tsc": "bin/tsc", | ||||
|                 "tsserver": "bin/tsserver" | ||||
| @ -8071,16 +8071,16 @@ | ||||
|             "integrity": "sha512-kixrYn4JwfAVPa0f2yfzc2AWti6WRRyO3XjWW5PJAvtE11qhSayrrcrEnee05KAtNaPC+EwehE8Qt1UedEVB8w==" | ||||
|         }, | ||||
|         "@babel/core": { | ||||
|             "version": "7.14.5", | ||||
|             "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.5.tgz", | ||||
|             "integrity": "sha512-RN/AwP2DJmQTZSfiDaD+JQQ/J99KsIpOCfBE5pL+5jJSt7nI3nYGoAXZu+ffYSQ029NLs2DstZb+eR81uuARgg==", | ||||
|             "version": "7.14.6", | ||||
|             "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.6.tgz", | ||||
|             "integrity": "sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA==", | ||||
|             "requires": { | ||||
|                 "@babel/code-frame": "^7.14.5", | ||||
|                 "@babel/generator": "^7.14.5", | ||||
|                 "@babel/helper-compilation-targets": "^7.14.5", | ||||
|                 "@babel/helper-module-transforms": "^7.14.5", | ||||
|                 "@babel/helpers": "^7.14.5", | ||||
|                 "@babel/parser": "^7.14.5", | ||||
|                 "@babel/helpers": "^7.14.6", | ||||
|                 "@babel/parser": "^7.14.6", | ||||
|                 "@babel/template": "^7.14.5", | ||||
|                 "@babel/traverse": "^7.14.5", | ||||
|                 "@babel/types": "^7.14.5", | ||||
| @ -8345,9 +8345,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@babel/helpers": { | ||||
|             "version": "7.14.5", | ||||
|             "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.5.tgz", | ||||
|             "integrity": "sha512-xtcWOuN9VL6nApgVHtq3PPcQv5qFBJzoSZzJ/2c0QK/IP/gxVcoWSNQwFEGvmbQsuS9rhYqjILDGGXcTkA705Q==", | ||||
|             "version": "7.14.6", | ||||
|             "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.6.tgz", | ||||
|             "integrity": "sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA==", | ||||
|             "requires": { | ||||
|                 "@babel/template": "^7.14.5", | ||||
|                 "@babel/traverse": "^7.14.5", | ||||
| @ -8365,9 +8365,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@babel/parser": { | ||||
|             "version": "7.14.5", | ||||
|             "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.5.tgz", | ||||
|             "integrity": "sha512-TM8C+xtH/9n1qzX+JNHi7AN2zHMTiPUtspO0ZdHflW8KaskkALhMmuMHb4bCmNdv9VAPzJX3/bXqkVLnAvsPfg==" | ||||
|             "version": "7.14.6", | ||||
|             "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.6.tgz", | ||||
|             "integrity": "sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ==" | ||||
|         }, | ||||
|         "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { | ||||
|             "version": "7.14.5", | ||||
| @ -9431,9 +9431,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@lingui/core": { | ||||
|             "version": "3.10.2", | ||||
|             "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.10.2.tgz", | ||||
|             "integrity": "sha512-RL4Bn1s4Ukd0GBlEFO/+GoxnK48xuZR2GwFslg/eQxcQmeOA4c1Jw/tbvMKDfi40XwqMBWcS0G2njpWjuHC3SA==", | ||||
|             "version": "3.10.4", | ||||
|             "resolved": "https://registry.npmjs.org/@lingui/core/-/core-3.10.4.tgz", | ||||
|             "integrity": "sha512-V9QKQ9PFMTPrGGz2PaeKHZcxFikQZzJbptyQbVFJdXaKhdE2RH6HhdK1PIziDHqp6ZWPthVIfVLURT3ku8eu5w==", | ||||
|             "requires": { | ||||
|                 "@babel/runtime": "^7.11.2", | ||||
|                 "make-plural": "^6.2.2", | ||||
| @ -9670,13 +9670,13 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@sentry/browser": { | ||||
|             "version": "6.5.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.5.1.tgz", | ||||
|             "integrity": "sha512-iVLCdEFwsoWAzE/hNknexPQjjDpMQV7mmaq9Z1P63bD6MfhwVTx4hG4pHn8HEvC38VvCVf1wv0v/LxtoODAYXg==", | ||||
|             "version": "6.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.1.tgz", | ||||
|             "integrity": "sha512-R5PYx4TTvifcU790XkK6JVGwavKaXwycDU0MaAwfc4Vf7BLm5KCNJCsDySu1RPAap/017MVYf54p6dWvKiRviA==", | ||||
|             "requires": { | ||||
|                 "@sentry/core": "6.5.1", | ||||
|                 "@sentry/types": "6.5.1", | ||||
|                 "@sentry/utils": "6.5.1", | ||||
|                 "@sentry/core": "6.7.1", | ||||
|                 "@sentry/types": "6.7.1", | ||||
|                 "@sentry/utils": "6.7.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "dependencies": { | ||||
| @ -9688,14 +9688,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@sentry/core": { | ||||
|             "version": "6.5.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.5.1.tgz", | ||||
|             "integrity": "sha512-Mh3sl/iUOT1myHmM6RlDy2ARzkUClx/g4DAt1rJ/IpQBOlDYQraplXSIW80i/hzRgQDfwhwgf4wUa5DicKBjKw==", | ||||
|             "version": "6.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.1.tgz", | ||||
|             "integrity": "sha512-VAv8OR/7INn2JfiLcuop4hfDcyC7mfL9fdPndQEhlacjmw8gRrgXjR7qyhnCTgzFLkHI7V5bcdIzA83TRPYQpA==", | ||||
|             "requires": { | ||||
|                 "@sentry/hub": "6.5.1", | ||||
|                 "@sentry/minimal": "6.5.1", | ||||
|                 "@sentry/types": "6.5.1", | ||||
|                 "@sentry/utils": "6.5.1", | ||||
|                 "@sentry/hub": "6.7.1", | ||||
|                 "@sentry/minimal": "6.7.1", | ||||
|                 "@sentry/types": "6.7.1", | ||||
|                 "@sentry/utils": "6.7.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "dependencies": { | ||||
| @ -9707,12 +9707,12 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@sentry/hub": { | ||||
|             "version": "6.5.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.5.1.tgz", | ||||
|             "integrity": "sha512-lBRMBVMYP8B4PfRiM70murbtJAXiIAao/asDEMIRNGMP6pI2ArqXfJCBYDkStukhikYD0Kqb4trXq+JYF07Hbg==", | ||||
|             "version": "6.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.1.tgz", | ||||
|             "integrity": "sha512-eVCTWvvcp6xa0A5GGNHMQEWslmKPlisE5rGmsV/kjvSUv3zSrI0eIDfb51ikdnCiBjHpK2NBWP8Vy8cZOEJegg==", | ||||
|             "requires": { | ||||
|                 "@sentry/types": "6.5.1", | ||||
|                 "@sentry/utils": "6.5.1", | ||||
|                 "@sentry/types": "6.7.1", | ||||
|                 "@sentry/utils": "6.7.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "dependencies": { | ||||
| @ -9724,12 +9724,12 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@sentry/minimal": { | ||||
|             "version": "6.5.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.5.1.tgz", | ||||
|             "integrity": "sha512-q9Do/oreu1RP695CXCLowVDuQyk7ilE6FGdz2QLpTXAfx8247qOwk6+zy9Kea/Djk93+BoSDVQUSneNiVwl0nQ==", | ||||
|             "version": "6.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.1.tgz", | ||||
|             "integrity": "sha512-HDDPEnQRD6hC0qaHdqqKDStcdE1KhkFh0RCtJNMCDn0zpav8Dj9AteF70x6kLSlliAJ/JFwi6AmQrLz+FxPexw==", | ||||
|             "requires": { | ||||
|                 "@sentry/hub": "6.5.1", | ||||
|                 "@sentry/types": "6.5.1", | ||||
|                 "@sentry/hub": "6.7.1", | ||||
|                 "@sentry/types": "6.7.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "dependencies": { | ||||
| @ -9741,14 +9741,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@sentry/tracing": { | ||||
|             "version": "6.5.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.5.1.tgz", | ||||
|             "integrity": "sha512-y1W/xFC2hAuKqSuuaovkElHY4pbli3XoXrreesg8PtO7ilX6ZbatOQbHsEsHQyoUv0F6aVA+MABOxWH2jt7tfw==", | ||||
|             "version": "6.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.1.tgz", | ||||
|             "integrity": "sha512-wyS3nWNl5mzaC1qZ2AIp1hjXnfO9EERjMIJjCihs2LWBz1r3efxrHxJHs8wXlNWvrT3KLhq/7vvF5CdU82uPeQ==", | ||||
|             "requires": { | ||||
|                 "@sentry/hub": "6.5.1", | ||||
|                 "@sentry/minimal": "6.5.1", | ||||
|                 "@sentry/types": "6.5.1", | ||||
|                 "@sentry/utils": "6.5.1", | ||||
|                 "@sentry/hub": "6.7.1", | ||||
|                 "@sentry/minimal": "6.7.1", | ||||
|                 "@sentry/types": "6.7.1", | ||||
|                 "@sentry/utils": "6.7.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "dependencies": { | ||||
| @ -9760,16 +9760,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@sentry/types": { | ||||
|             "version": "6.5.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.1.tgz", | ||||
|             "integrity": "sha512-b/7a6CMoytaeFPx4IBjfxPw3nPvsQh7ui1C8Vw0LxNNDgBwVhPLzUOWeLWbo5YZCVbGEMIWwtCUQYWxneceZSA==" | ||||
|             "version": "6.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.1.tgz", | ||||
|             "integrity": "sha512-9AO7HKoip2MBMNQJEd6+AKtjj2+q9Ze4ooWUdEvdOVSt5drg7BGpK221/p9JEOyJAZwEPEXdcMd3VAIMiOb4MA==" | ||||
|         }, | ||||
|         "@sentry/utils": { | ||||
|             "version": "6.5.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.5.1.tgz", | ||||
|             "integrity": "sha512-Wv86JYGQH+ZJ5XGFQX7h6ijl32667ikenoL9EyXMn8UoOYX/MLwZoQZin1P60wmKkYR9ifTNVmpaI9OoTaH+UQ==", | ||||
|             "version": "6.7.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.1.tgz", | ||||
|             "integrity": "sha512-Tq2otdbWlHAkctD+EWTYKkEx6BL1Qn3Z/imkO06/PvzpWvVhJWQ5qHAzz5XnwwqNHyV03KVzYB6znq1Bea9HuA==", | ||||
|             "requires": { | ||||
|                 "@sentry/types": "6.5.1", | ||||
|                 "@sentry/types": "6.7.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "dependencies": { | ||||
| @ -9926,12 +9926,12 @@ | ||||
|             "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" | ||||
|         }, | ||||
|         "@typescript-eslint/eslint-plugin": { | ||||
|             "version": "4.26.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.26.1.tgz", | ||||
|             "integrity": "sha512-aoIusj/8CR+xDWmZxARivZjbMBQTT9dImUtdZ8tVCVRXgBUuuZyM5Of5A9D9arQPxbi/0rlJLcuArclz/rCMJw==", | ||||
|             "version": "4.27.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.27.0.tgz", | ||||
|             "integrity": "sha512-DsLqxeUfLVNp3AO7PC3JyaddmEHTtI9qTSAs+RB6ja27QvIM0TA8Cizn1qcS6vOu+WDLFJzkwkgweiyFhssDdQ==", | ||||
|             "requires": { | ||||
|                 "@typescript-eslint/experimental-utils": "4.26.1", | ||||
|                 "@typescript-eslint/scope-manager": "4.26.1", | ||||
|                 "@typescript-eslint/experimental-utils": "4.27.0", | ||||
|                 "@typescript-eslint/scope-manager": "4.27.0", | ||||
|                 "debug": "^4.3.1", | ||||
|                 "functional-red-black-tree": "^1.0.1", | ||||
|                 "lodash": "^4.17.21", | ||||
| @ -9941,14 +9941,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@typescript-eslint/experimental-utils": { | ||||
|             "version": "4.26.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.26.1.tgz", | ||||
|             "integrity": "sha512-sQHBugRhrXzRCs9PaGg6rowie4i8s/iD/DpTB+EXte8OMDfdCG5TvO73XlO9Wc/zi0uyN4qOmX9hIjQEyhnbmQ==", | ||||
|             "version": "4.27.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.27.0.tgz", | ||||
|             "integrity": "sha512-n5NlbnmzT2MXlyT+Y0Jf0gsmAQzCnQSWXKy4RGSXVStjDvS5we9IWbh7qRVKdGcxT0WYlgcCYUK/HRg7xFhvjQ==", | ||||
|             "requires": { | ||||
|                 "@types/json-schema": "^7.0.7", | ||||
|                 "@typescript-eslint/scope-manager": "4.26.1", | ||||
|                 "@typescript-eslint/types": "4.26.1", | ||||
|                 "@typescript-eslint/typescript-estree": "4.26.1", | ||||
|                 "@typescript-eslint/scope-manager": "4.27.0", | ||||
|                 "@typescript-eslint/types": "4.27.0", | ||||
|                 "@typescript-eslint/typescript-estree": "4.27.0", | ||||
|                 "eslint-scope": "^5.1.1", | ||||
|                 "eslint-utils": "^3.0.0" | ||||
|             }, | ||||
| @ -9964,37 +9964,37 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@typescript-eslint/parser": { | ||||
|             "version": "4.26.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.26.1.tgz", | ||||
|             "integrity": "sha512-q7F3zSo/nU6YJpPJvQveVlIIzx9/wu75lr6oDbDzoeIRWxpoc/HQ43G4rmMoCc5my/3uSj2VEpg/D83LYZF5HQ==", | ||||
|             "version": "4.27.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.27.0.tgz", | ||||
|             "integrity": "sha512-XpbxL+M+gClmJcJ5kHnUpBGmlGdgNvy6cehgR6ufyxkEJMGP25tZKCaKyC0W/JVpuhU3VU1RBn7SYUPKSMqQvQ==", | ||||
|             "requires": { | ||||
|                 "@typescript-eslint/scope-manager": "4.26.1", | ||||
|                 "@typescript-eslint/types": "4.26.1", | ||||
|                 "@typescript-eslint/typescript-estree": "4.26.1", | ||||
|                 "@typescript-eslint/scope-manager": "4.27.0", | ||||
|                 "@typescript-eslint/types": "4.27.0", | ||||
|                 "@typescript-eslint/typescript-estree": "4.27.0", | ||||
|                 "debug": "^4.3.1" | ||||
|             } | ||||
|         }, | ||||
|         "@typescript-eslint/scope-manager": { | ||||
|             "version": "4.26.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.26.1.tgz", | ||||
|             "integrity": "sha512-TW1X2p62FQ8Rlne+WEShyd7ac2LA6o27S9i131W4NwDSfyeVlQWhw8ylldNNS8JG6oJB9Ha9Xyc+IUcqipvheQ==", | ||||
|             "version": "4.27.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.27.0.tgz", | ||||
|             "integrity": "sha512-DY73jK6SEH6UDdzc6maF19AHQJBFVRf6fgAXHPXCGEmpqD4vYgPEzqpFz1lf/daSbOcMpPPj9tyXXDPW2XReAw==", | ||||
|             "requires": { | ||||
|                 "@typescript-eslint/types": "4.26.1", | ||||
|                 "@typescript-eslint/visitor-keys": "4.26.1" | ||||
|                 "@typescript-eslint/types": "4.27.0", | ||||
|                 "@typescript-eslint/visitor-keys": "4.27.0" | ||||
|             } | ||||
|         }, | ||||
|         "@typescript-eslint/types": { | ||||
|             "version": "4.26.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.26.1.tgz", | ||||
|             "integrity": "sha512-STyMPxR3cS+LaNvS8yK15rb8Y0iL0tFXq0uyl6gY45glyI7w0CsyqyEXl/Fa0JlQy+pVANeK3sbwPneCbWE7yg==" | ||||
|             "version": "4.27.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.27.0.tgz", | ||||
|             "integrity": "sha512-I4ps3SCPFCKclRcvnsVA/7sWzh7naaM/b4pBO2hVxnM3wrU51Lveybdw5WoIktU/V4KfXrTt94V9b065b/0+wA==" | ||||
|         }, | ||||
|         "@typescript-eslint/typescript-estree": { | ||||
|             "version": "4.26.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.26.1.tgz", | ||||
|             "integrity": "sha512-l3ZXob+h0NQzz80lBGaykdScYaiEbFqznEs99uwzm8fPHhDjwaBFfQkjUC/slw6Sm7npFL8qrGEAMxcfBsBJUg==", | ||||
|             "version": "4.27.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.27.0.tgz", | ||||
|             "integrity": "sha512-KH03GUsUj41sRLLEy2JHstnezgpS5VNhrJouRdmh6yNdQ+yl8w5LrSwBkExM+jWwCJa7Ct2c8yl8NdtNRyQO6g==", | ||||
|             "requires": { | ||||
|                 "@typescript-eslint/types": "4.26.1", | ||||
|                 "@typescript-eslint/visitor-keys": "4.26.1", | ||||
|                 "@typescript-eslint/types": "4.27.0", | ||||
|                 "@typescript-eslint/visitor-keys": "4.27.0", | ||||
|                 "debug": "^4.3.1", | ||||
|                 "globby": "^11.0.3", | ||||
|                 "is-glob": "^4.0.1", | ||||
| @ -10018,11 +10018,11 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@typescript-eslint/visitor-keys": { | ||||
|             "version": "4.26.1", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.26.1.tgz", | ||||
|             "integrity": "sha512-IGouNSSd+6x/fHtYRyLOM6/C+QxMDzWlDtN41ea+flWuSF9g02iqcIlX8wM53JkfljoIjP0U+yp7SiTS1onEkw==", | ||||
|             "version": "4.27.0", | ||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.27.0.tgz", | ||||
|             "integrity": "sha512-es0GRYNZp0ieckZ938cEANfEhsfHrzuLrePukLKtY3/KPXcq1Xd555Mno9/GOgXhKzn0QfkDLVgqWO3dGY80bg==", | ||||
|             "requires": { | ||||
|                 "@typescript-eslint/types": "4.26.1", | ||||
|                 "@typescript-eslint/types": "4.27.0", | ||||
|                 "eslint-visitor-keys": "^2.0.0" | ||||
|             } | ||||
|         }, | ||||
| @ -13202,11 +13202,11 @@ | ||||
|             } | ||||
|         }, | ||||
|         "rollup": { | ||||
|             "version": "2.51.1", | ||||
|             "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.51.1.tgz", | ||||
|             "integrity": "sha512-8xfDbAtBleXotb6qKEHWuo/jkn94a9dVqGc7Rwl3sqspCVlnCfbRek7ldhCARSi7h32H0xR4QThm1t9zHN+3uw==", | ||||
|             "version": "2.52.1", | ||||
|             "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.1.tgz", | ||||
|             "integrity": "sha512-/SPqz8UGnp4P1hq6wc9gdTqA2bXQXGx13TtoL03GBm6qGRI6Hm3p4Io7GeiHNLl0BsQAne1JNYY+q/apcY933w==", | ||||
|             "requires": { | ||||
|                 "fsevents": "~2.3.1" | ||||
|                 "fsevents": "~2.3.2" | ||||
|             } | ||||
|         }, | ||||
|         "rollup-plugin-commonjs": { | ||||
| @ -13870,9 +13870,9 @@ | ||||
|             "integrity": "sha512-zKmsCQs4dZaeSKjEA7pLFDv7FHHqAFLPd0Mr//OIJvu8M+4p4bgSFJwZSEBEg3ec9W7RzRz1vi8giiX0+mheBQ==" | ||||
|         }, | ||||
|         "tslib": { | ||||
|             "version": "2.2.0", | ||||
|             "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", | ||||
|             "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" | ||||
|             "version": "2.3.0", | ||||
|             "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", | ||||
|             "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" | ||||
|         }, | ||||
|         "tsutils": { | ||||
|             "version": "3.21.0", | ||||
| @ -13898,9 +13898,9 @@ | ||||
|             } | ||||
|         }, | ||||
|         "typescript": { | ||||
|             "version": "4.3.2", | ||||
|             "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", | ||||
|             "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==" | ||||
|             "version": "4.3.3", | ||||
|             "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.3.tgz", | ||||
|             "integrity": "sha512-rUvLW0WtF7PF2b9yenwWUi9Da9euvDRhmH7BLyBG4DCFfOJ850LGNknmRpp8Z8kXNUPObdZQEfKOiHtXuQHHKA==" | ||||
|         }, | ||||
|         "uglify-js": { | ||||
|             "version": "3.13.0", | ||||
|  | ||||
| @ -38,14 +38,14 @@ | ||||
|         ] | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@babel/core": "^7.14.5", | ||||
|         "@babel/core": "^7.14.6", | ||||
|         "@babel/plugin-proposal-decorators": "^7.14.5", | ||||
|         "@babel/plugin-transform-runtime": "^7.14.5", | ||||
|         "@babel/preset-env": "^7.14.5", | ||||
|         "@babel/preset-typescript": "^7.14.5", | ||||
|         "@fortawesome/fontawesome-free": "^5.15.3", | ||||
|         "@lingui/cli": "^3.10.2", | ||||
|         "@lingui/core": "^3.10.2", | ||||
|         "@lingui/core": "^3.10.4", | ||||
|         "@lingui/macro": "^3.10.2", | ||||
|         "@patternfly/patternfly": "^4.108.2", | ||||
|         "@polymer/iron-form": "^3.0.1", | ||||
| @ -53,13 +53,13 @@ | ||||
|         "@rollup/plugin-babel": "^5.3.0", | ||||
|         "@rollup/plugin-replace": "^2.4.2", | ||||
|         "@rollup/plugin-typescript": "^8.2.1", | ||||
|         "@sentry/browser": "^6.5.1", | ||||
|         "@sentry/tracing": "^6.5.1", | ||||
|         "@sentry/browser": "^6.7.1", | ||||
|         "@sentry/tracing": "^6.7.1", | ||||
|         "@types/chart.js": "^2.9.32", | ||||
|         "@types/codemirror": "5.60.0", | ||||
|         "@types/grecaptcha": "^3.0.2", | ||||
|         "@typescript-eslint/eslint-plugin": "^4.26.1", | ||||
|         "@typescript-eslint/parser": "^4.26.1", | ||||
|         "@typescript-eslint/eslint-plugin": "^4.27.0", | ||||
|         "@typescript-eslint/parser": "^4.27.0", | ||||
|         "@webcomponents/webcomponentsjs": "^2.5.0", | ||||
|         "authentik-api": "file:api", | ||||
|         "babel-plugin-macros": "^3.1.0", | ||||
| @ -77,7 +77,7 @@ | ||||
|         "lit-html": "^1.4.1", | ||||
|         "moment": "^2.29.1", | ||||
|         "rapidoc": "^9.0.0", | ||||
|         "rollup": "^2.51.1", | ||||
|         "rollup": "^2.52.1", | ||||
|         "rollup-plugin-commonjs": "^10.1.0", | ||||
|         "rollup-plugin-copy": "^3.4.0", | ||||
|         "rollup-plugin-cssimport": "^1.0.2", | ||||
| @ -86,8 +86,8 @@ | ||||
|         "rollup-plugin-sourcemaps": "^0.6.3", | ||||
|         "rollup-plugin-terser": "^7.0.2", | ||||
|         "ts-lit-plugin": "^1.2.1", | ||||
|         "tslib": "^2.2.0", | ||||
|         "typescript": "^4.3.2", | ||||
|         "tslib": "^2.3.0", | ||||
|         "typescript": "^4.3.3", | ||||
|         "webcomponent-qr-code": "^1.0.5", | ||||
|         "yaml": "^1.10.2" | ||||
|     }, | ||||
|  | ||||
| @ -8,10 +8,22 @@ export interface EventUser { | ||||
| } | ||||
|  | ||||
| export interface EventContext { | ||||
|     [key: string]: EventContext | string | number | string[]; | ||||
|     [key: string]: EventContext | EventModel | string | number | string[]; | ||||
| } | ||||
|  | ||||
| export interface EventWithContext extends Event { | ||||
|     user: EventUser; | ||||
|     context: EventContext; | ||||
| } | ||||
|  | ||||
| export interface EventModel { | ||||
|     pk: string; | ||||
|     name: string; | ||||
|     app: string; | ||||
|     model_name: string; | ||||
| } | ||||
|  | ||||
| export interface EventRequest { | ||||
|     path: string; | ||||
|     method: string; | ||||
| } | ||||
|  | ||||
| @ -7,8 +7,9 @@ import { config } from "./Config"; | ||||
| import { Config } from "authentik-api"; | ||||
|  | ||||
| export const TAG_SENTRY_COMPONENT = "authentik.component"; | ||||
| export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities"; | ||||
|  | ||||
| export function configureSentry(canDoPpi: boolean = false, tags: { [key: string]: string; } = {}): Promise<Config> { | ||||
| export function configureSentry(canDoPpi: boolean = false): Promise<Config> { | ||||
|     return config().then((config) => { | ||||
|         if (config.errorReportingEnabled) { | ||||
|             Sentry.init({ | ||||
| @ -53,7 +54,7 @@ export function configureSentry(canDoPpi: boolean = false, tags: { [key: string] | ||||
|                     return event; | ||||
|                 }, | ||||
|             }); | ||||
|             Sentry.setTags(tags); | ||||
|             Sentry.setTag(TAG_SENTRY_CAPABILITIES, config.capabilities.join(",")); | ||||
|             if (window.location.pathname.includes("if/")) { | ||||
|                 // Get the interface name from URL | ||||
|                 const intf = window.location.pathname.replace(/.+if\/(.+)\//, "$1"); | ||||
|  | ||||
| @ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success"; | ||||
| export const ERROR_CLASS = "pf-m-danger"; | ||||
| export const PROGRESS_CLASS = "pf-m-in-progress"; | ||||
| export const CURRENT_CLASS = "pf-m-current"; | ||||
| export const VERSION = "2021.6.1-rc3"; | ||||
| export const VERSION = "2021.6.1"; | ||||
| export const PAGE_SIZE = 20; | ||||
| export const EVENT_REFRESH = "ak-refresh"; | ||||
| export const EVENT_NOTIFICATION_TOGGLE = "ak-notification-toggle"; | ||||
|  | ||||
| @ -1,27 +0,0 @@ | ||||
| import { customElement, CSSResult, html, LitElement, property, TemplateResult } from "lit-element"; | ||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||
| import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css"; | ||||
| import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; | ||||
| import AKGlobal from "../authentik.css"; | ||||
|  | ||||
| @customElement("ak-banner") | ||||
| export class Banner extends LitElement { | ||||
|  | ||||
|     @property() | ||||
|     level = "pf-m-warning"; | ||||
|  | ||||
|     static get styles(): CSSResult[] { | ||||
|         return [PFBase, PFBanner, PFFlex, AKGlobal]; | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html`<div class="pf-c-banner ${this.level} pf-m-sticky"> | ||||
|             <div class="pf-l-flex pf-m-justify-content-center pf-m-justify-content-space-between-on-lg pf-m-nowrap" style="height: 100%;"> | ||||
|                 <div class="pf-u-display-none pf-u-display-block-on-lg"> | ||||
|                     <slot></slot> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div>`; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -7,7 +7,6 @@ import PFFormControl from "@patternfly/patternfly/components/FormControl/form-co | ||||
| import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css"; | ||||
| import PFBackdrop from "@patternfly/patternfly/components/Backdrop/backdrop.css"; | ||||
| import PFPage from "@patternfly/patternfly/components/Page/page.css"; | ||||
| import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css"; | ||||
| import PFCard from "@patternfly/patternfly/components/Card/card.css"; | ||||
| import PFTitle from "@patternfly/patternfly/components/Title/title.css"; | ||||
| import PFContent from "@patternfly/patternfly/components/Content/content.css"; | ||||
| @ -40,7 +39,7 @@ export class ModalButton extends LitElement { | ||||
|     open = false; | ||||
|  | ||||
|     static get styles(): CSSResult[] { | ||||
|         return [PFBase, PFButton, PFModalBox, PFForm, PFTitle, PFFormControl, PFBullseye, PFBackdrop, PFPage, PFStack, PFCard, PFContent, AKGlobal, MODAL_BUTTON_STYLES]; | ||||
|         return [PFBase, PFButton, PFModalBox, PFForm, PFTitle, PFFormControl, PFBullseye, PFBackdrop, PFPage, PFCard, PFContent, AKGlobal, MODAL_BUTTON_STYLES]; | ||||
|     } | ||||
|  | ||||
|     constructor() { | ||||
|  | ||||
| @ -19,7 +19,6 @@ export class Sidebar extends LitElement { | ||||
|             css` | ||||
|                 :host { | ||||
|                     z-index: 100; | ||||
|                     box-shadow: none !important; | ||||
|                 } | ||||
|                 .pf-c-nav__link.pf-m-current::after, | ||||
|                 .pf-c-nav__link.pf-m-current:hover::after, | ||||
|  | ||||
| @ -21,6 +21,13 @@ export class SidebarItem extends LitElement { | ||||
|                     z-index: 100; | ||||
|                     box-shadow: none !important; | ||||
|                 } | ||||
|                 :host([highlight]) .pf-c-nav__item { | ||||
|                     background-color: var(--ak-accent); | ||||
|                     margin: 16px; | ||||
|                 } | ||||
|                 :host([highlight]) .pf-c-nav__item .pf-c-nav__link  { | ||||
|                     padding-left: 0.5rem; | ||||
|                 } | ||||
|                 .pf-c-nav__link.pf-m-current::after, | ||||
|                 .pf-c-nav__link.pf-m-current:hover::after, | ||||
|                 .pf-c-nav__item.pf-m-current:not(.pf-m-expanded) .pf-c-nav__link::after { | ||||
| @ -75,6 +82,12 @@ export class SidebarItem extends LitElement { | ||||
|     @property({ type: Boolean }) | ||||
|     isActive = false; | ||||
|  | ||||
|     @property({ type: Boolean }) | ||||
|     isAbsoluteLink?: boolean; | ||||
|  | ||||
|     @property({ type: Boolean }) | ||||
|     highlight?: boolean; | ||||
|  | ||||
|     parent?: SidebarItem; | ||||
|  | ||||
|     get childItems(): SidebarItem[] { | ||||
| @ -159,9 +172,15 @@ export class SidebarItem extends LitElement { | ||||
|             </li>`; | ||||
|         } | ||||
|         return html`<li class="pf-c-nav__item"> | ||||
|             <a href="#${this.path}" class="pf-c-nav__link ${this.isActive ? "pf-m-current" : ""}"> | ||||
|             ${this.path ? html` | ||||
|                 <a href="${this.isAbsoluteLink ? "" : "#"}${this.path}" class="pf-c-nav__link ${this.isActive ? "pf-m-current" : ""}"> | ||||
|                     <slot name="label"></slot> | ||||
|                 </a> | ||||
|             ` : html` | ||||
|                 <span class="pf-c-nav__link"> | ||||
|                     <slot name="label"></slot> | ||||
|                 </span> | ||||
|             `} | ||||
|         </li>`; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -26,7 +26,7 @@ import "./stages/password/PasswordStage"; | ||||
| import "./stages/prompt/PromptStage"; | ||||
| import "./sources/plex/PlexLoginInit"; | ||||
| import { StageHost } from "./stages/base"; | ||||
| import { ChallengeChoices, CurrentTenant, FlowChallengeRequest, FlowChallengeResponseRequest, FlowsApi, RedirectChallenge, ShellChallenge } from "authentik-api"; | ||||
| import { ChallengeChoices, CurrentTenant, ChallengeTypes, FlowChallengeResponseRequest, FlowsApi, RedirectChallenge, ShellChallenge } from "authentik-api"; | ||||
| import { DEFAULT_CONFIG, tenant } from "../api/Config"; | ||||
| import { ifDefined } from "lit-html/directives/if-defined"; | ||||
| import { until } from "lit-html/directives/until"; | ||||
| @ -40,7 +40,7 @@ export class FlowExecutor extends LitElement implements StageHost { | ||||
|     flowSlug: string; | ||||
|  | ||||
|     @property({attribute: false}) | ||||
|     challenge?: FlowChallengeRequest; | ||||
|     challenge?: ChallengeTypes; | ||||
|  | ||||
|     @property({type: Boolean}) | ||||
|     loading = false; | ||||
| @ -107,8 +107,8 @@ export class FlowExecutor extends LitElement implements StageHost { | ||||
|         }).then((data) => { | ||||
|             this.challenge = data; | ||||
|             this.postUpdate(); | ||||
|         }).catch((e: Error) => { | ||||
|             this.errorMessage(e.toString()); | ||||
|         }).catch((e: Error | Response) => { | ||||
|             this.errorMessage(e); | ||||
|         }).finally(() => { | ||||
|             this.loading = false; | ||||
|         }); | ||||
| @ -128,15 +128,21 @@ export class FlowExecutor extends LitElement implements StageHost { | ||||
|                 this.setBackground(this.challenge.flowInfo.background); | ||||
|             } | ||||
|             this.postUpdate(); | ||||
|         }).catch((e: Error) => { | ||||
|         }).catch((e: Error | Response) => { | ||||
|             // Catch JSON or Update errors | ||||
|             this.errorMessage(e.toString()); | ||||
|             this.errorMessage(e); | ||||
|         }).finally(() => { | ||||
|             this.loading = false; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     errorMessage(error: string): void { | ||||
|     async errorMessage(error: Error | Response): Promise<void> { | ||||
|         let body = ""; | ||||
|         if (error instanceof Error) { | ||||
|             body = error.message; | ||||
|         } else if (error instanceof Response) { | ||||
|             body = await error.text(); | ||||
|         } | ||||
|         this.challenge = { | ||||
|             type: ChallengeChoices.Shell, | ||||
|             body: `<header class="pf-c-login__main-header"> | ||||
| @ -146,7 +152,7 @@ export class FlowExecutor extends LitElement implements StageHost { | ||||
|             </header> | ||||
|             <div class="pf-c-login__main-body"> | ||||
|                 <h3>${t`Something went wrong! Please try again later.`}</h3> | ||||
|                 <pre class="ak-exception">${error}</pre> | ||||
|                 <pre class="ak-exception">${body}</pre> | ||||
|             </div> | ||||
|             <footer class="pf-c-login__main-footer"> | ||||
|                 <ul class="pf-c-login__main-footer-links"> | ||||
| @ -157,7 +163,7 @@ export class FlowExecutor extends LitElement implements StageHost { | ||||
|                     </li> | ||||
|                 </ul> | ||||
|             </footer>` | ||||
|         } as FlowChallengeRequest; | ||||
|         } as ChallengeTypes; | ||||
|     } | ||||
|  | ||||
|     renderLoading(): TemplateResult { | ||||
|  | ||||
| @ -11,7 +11,7 @@ import PFAlert from "@patternfly/patternfly/components/Alert/alert.css"; | ||||
| import AKGlobal from "../../../authentik.css"; | ||||
| import "../../../elements/forms/FormElement"; | ||||
| import "../../../elements/EmptyState"; | ||||
| import { FlowChallengeRequest, IdentificationChallenge, IdentificationChallengeResponseRequest, UILoginButton } from "authentik-api"; | ||||
| import { IdentificationChallenge, IdentificationChallengeResponseRequest, LoginSource, UserFieldsEnum } from "authentik-api"; | ||||
|  | ||||
| export const PasswordManagerPrefill: { | ||||
|     password: string | undefined; | ||||
| @ -21,6 +21,7 @@ export const PasswordManagerPrefill: { | ||||
|     totp: undefined, | ||||
| }; | ||||
|  | ||||
| export const OR_LIST_FORMATTERS = new Intl.ListFormat("default", { style: "short", type: "disjunction" }); | ||||
|  | ||||
| @customElement("ak-stage-identification") | ||||
| export class IdentificationStage extends BaseStage<IdentificationChallenge, IdentificationChallengeResponseRequest> { | ||||
| @ -109,7 +110,7 @@ export class IdentificationStage extends BaseStage<IdentificationChallenge, Iden | ||||
|         wrapperForm.appendChild(totp); | ||||
|     } | ||||
|  | ||||
|     renderSource(source: UILoginButton): TemplateResult { | ||||
|     renderSource(source: LoginSource): TemplateResult { | ||||
|         let icon = html`<i class="fas fas fa-share-square" title="${source.name}"></i>`; | ||||
|         if (source.iconUrl) { | ||||
|             icon = html`<img src="${source.iconUrl}" alt="${source.name}">`; | ||||
| @ -117,7 +118,7 @@ export class IdentificationStage extends BaseStage<IdentificationChallenge, Iden | ||||
|         return html`<li class="pf-c-login__main-footer-links-item"> | ||||
|                 <button type="button" @click=${() => { | ||||
|                     if (!this.host) return; | ||||
|                     this.host.challenge = source.challenge as FlowChallengeRequest; | ||||
|                     this.host.challenge = source.challenge; | ||||
|                 }}> | ||||
|                     ${icon} | ||||
|                 </button> | ||||
| @ -142,21 +143,23 @@ export class IdentificationStage extends BaseStage<IdentificationChallenge, Iden | ||||
|     } | ||||
|  | ||||
|     renderInput(): TemplateResult { | ||||
|         let label = ""; | ||||
|         let type = "text"; | ||||
|         if (!this.challenge?.userFields) { | ||||
|             return html`<p> | ||||
|                 ${t`Select one of the sources below to login.`} | ||||
|             </p>`; | ||||
|         } | ||||
|         if (this.challenge?.userFields === ["email"]) { | ||||
|             label = t`Email`; | ||||
|         const fields = (this.challenge?.userFields || []).sort(); | ||||
|         // Check if the field should be *only* email to set the input type | ||||
|         if (fields.includes(UserFieldsEnum.Email) && fields.length === 1) { | ||||
|             type = "email"; | ||||
|         } else if (this.challenge?.userFields === ["username"]) { | ||||
|             label = t`Username`; | ||||
|         } else { | ||||
|             label = t`Email or username`; | ||||
|         } | ||||
|         const uiFields: { [key: string]: string } = { | ||||
|             [UserFieldsEnum.Username]: t`Username`, | ||||
|             [UserFieldsEnum.Email]: t`Email`, | ||||
|             [UserFieldsEnum.Upn]: t`UPN`, | ||||
|         }; | ||||
|         const label = OR_LIST_FORMATTERS.format(fields.map(f => uiFields[f])); | ||||
|         return html`<ak-form-element | ||||
|                 label=${label} | ||||
|                 ?required="${true}" | ||||
| @ -165,7 +168,7 @@ export class IdentificationStage extends BaseStage<IdentificationChallenge, Iden | ||||
|                 <!-- @ts-ignore --> | ||||
|                 <input type=${type} | ||||
|                     name="uidField" | ||||
|                     placeholder="Email or Username" | ||||
|                     placeholder=${label} | ||||
|                     autofocus="" | ||||
|                     autocomplete="username" | ||||
|                     class="pf-c-form-control" | ||||
|  | ||||
							
								
								
									
										7
									
								
								web/src/global.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								web/src/global.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -1 +1,8 @@ | ||||
| declare module "*.css"; | ||||
|  | ||||
| declare namespace Intl { | ||||
|     class ListFormat { | ||||
|         constructor(locale: string, args: { [key: string]: string }); | ||||
|         public format: (items: string[]) => string; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,20 +1,102 @@ | ||||
| import "../elements/messages/MessageContainer"; | ||||
| import { customElement, html, TemplateResult } from "lit-element"; | ||||
| import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | ||||
| import { me } from "../api/Users"; | ||||
| import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "../elements/router/Route"; | ||||
| import { Interface } from "./Interface"; | ||||
| import "./locale"; | ||||
| import "../elements/sidebar/SidebarItem"; | ||||
| import { t } from "@lingui/macro"; | ||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||
| import PFPage from "@patternfly/patternfly/components/Page/page.css"; | ||||
| import PFButton from "@patternfly/patternfly/components/Button/button.css"; | ||||
| import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css"; | ||||
|  | ||||
| import "../elements/router/RouterOutlet"; | ||||
| import "../elements/messages/MessageContainer"; | ||||
| import "../elements/notifications/NotificationDrawer"; | ||||
| import "../elements/sidebar/Sidebar"; | ||||
| import { until } from "lit-html/directives/until"; | ||||
| import { EVENT_NOTIFICATION_TOGGLE, EVENT_SIDEBAR_TOGGLE, VERSION } from "../constants"; | ||||
| import { AdminApi } from "authentik-api"; | ||||
| import { DEFAULT_CONFIG } from "../api/Config"; | ||||
|  | ||||
|  | ||||
| @customElement("ak-interface-admin") | ||||
| export class AdminInterface extends Interface { | ||||
| export class AdminInterface extends LitElement { | ||||
|  | ||||
|     @property({type: Boolean}) | ||||
|     sidebarOpen = true; | ||||
|  | ||||
|     @property({type: Boolean}) | ||||
|     notificationOpen = false; | ||||
|  | ||||
|     static get styles(): CSSResult[] { | ||||
|         return [PFBase, PFPage, PFButton, PFDrawer, css` | ||||
|             .pf-c-page__main, .pf-c-drawer__content, .pf-c-page__drawer { | ||||
|                 z-index: auto !important; | ||||
|             } | ||||
|         `]; | ||||
|     } | ||||
|  | ||||
|     constructor() { | ||||
|         super(); | ||||
|         this.sidebarOpen = window.innerWidth >= 1280; | ||||
|         window.addEventListener("resize", () => { | ||||
|             this.sidebarOpen = window.innerWidth >= 1280; | ||||
|         }); | ||||
|         window.addEventListener(EVENT_SIDEBAR_TOGGLE, () => { | ||||
|             this.sidebarOpen = !this.sidebarOpen; | ||||
|         }); | ||||
|         window.addEventListener(EVENT_NOTIFICATION_TOGGLE, () => { | ||||
|             this.notificationOpen = !this.notificationOpen; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html` | ||||
|             <div class="pf-c-page"> | ||||
|                 <ak-sidebar class="pf-c-page__sidebar ${this.sidebarOpen ? "pf-m-expanded" : "pf-m-collapsed"}"> | ||||
|                     ${this.renderSidebarItems()} | ||||
|                 </ak-sidebar> | ||||
|                 <div class="pf-c-page__drawer"> | ||||
|                     <div class="pf-c-drawer ${this.notificationOpen ? "pf-m-expanded" : "pf-m-collapsed"}"> | ||||
|                         <div class="pf-c-drawer__main"> | ||||
|                             <div class="pf-c-drawer__content"> | ||||
|                                 <div class="pf-c-drawer__body"> | ||||
|                                     <main class="pf-c-page__main"> | ||||
|                                         <ak-router-outlet role="main" class="pf-c-page__main" tabindex="-1" id="main-content" defaultUrl="/library"> | ||||
|                                         </ak-router-outlet> | ||||
|                                     </main> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                             <ak-notification-drawer class="pf-c-drawer__panel pf-m-width-33"> | ||||
|                             </ak-notification-drawer> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div>`; | ||||
|     } | ||||
|  | ||||
|     renderSidebarItems(): TemplateResult { | ||||
|         const superUserCondition = () => { | ||||
|             return me().then(u => u.user.isSuperuser || false); | ||||
|         }; | ||||
|         return html` | ||||
|             ${until(new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve().then(version => { | ||||
|                 if (version.versionCurrent !== VERSION) { | ||||
|                     return html`<ak-sidebar-item ?highlight=${true}> | ||||
|                         <span slot="label">${t`A newer version of the frontend is available.`}</span> | ||||
|                     </ak-sidebar-item>`; | ||||
|                 } | ||||
|                 return html``; | ||||
|             }))} | ||||
|             ${until(me().then((u) => { | ||||
|                 if (u.original) { | ||||
|                     return html`<ak-sidebar-item ?highlight=${true} ?isAbsoluteLink=${true} path=${`/-/impersonation/end/?back=${window.location.pathname}%23${window.location.hash}`}> | ||||
|                         <span slot="label">${t`You're currently impersonating ${u.user.username}. Click to stop.`}</span> | ||||
|                     </ak-sidebar-item>`; | ||||
|                 } | ||||
|                 return html``; | ||||
|             }))} | ||||
|             <ak-sidebar-item path="/library"> | ||||
|                 <span slot="label">${t`Library`}</span> | ||||
|             </ak-sidebar-item> | ||||
|  | ||||
| @ -1,99 +0,0 @@ | ||||
| import { css, CSSResult, html, LitElement, property, TemplateResult } from "lit-element"; | ||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||
| import PFPage from "@patternfly/patternfly/components/Page/page.css"; | ||||
| import PFButton from "@patternfly/patternfly/components/Button/button.css"; | ||||
| import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css"; | ||||
|  | ||||
| import "../elements/router/RouterOutlet"; | ||||
| import "../elements/messages/MessageContainer"; | ||||
| import "../elements/notifications/NotificationDrawer"; | ||||
| import "../elements/Banner"; | ||||
| import "../elements/sidebar/Sidebar"; | ||||
| import { until } from "lit-html/directives/until"; | ||||
| import { me } from "../api/Users"; | ||||
| import { t } from "@lingui/macro"; | ||||
| import { EVENT_NOTIFICATION_TOGGLE, EVENT_SIDEBAR_TOGGLE, VERSION } from "../constants"; | ||||
| import { AdminApi } from "authentik-api"; | ||||
| import { DEFAULT_CONFIG } from "../api/Config"; | ||||
|  | ||||
| export abstract class Interface extends LitElement { | ||||
|     @property({type: Boolean}) | ||||
|     sidebarOpen = true; | ||||
|  | ||||
|     @property({type: Boolean}) | ||||
|     notificationOpen = false; | ||||
|  | ||||
|     static get styles(): CSSResult[] { | ||||
|         return [PFBase, PFPage, PFButton, PFDrawer, css` | ||||
|             .pf-c-page__main, .pf-c-drawer__content, .pf-c-page__drawer { | ||||
|                 z-index: auto !important; | ||||
|             } | ||||
|         `]; | ||||
|     } | ||||
|  | ||||
|     constructor() { | ||||
|         super(); | ||||
|         this.sidebarOpen = window.innerWidth >= 1280; | ||||
|         window.addEventListener("resize", () => { | ||||
|             this.sidebarOpen = window.innerWidth >= 1280; | ||||
|         }); | ||||
|         window.addEventListener(EVENT_SIDEBAR_TOGGLE, () => { | ||||
|             this.sidebarOpen = !this.sidebarOpen; | ||||
|         }); | ||||
|         window.addEventListener(EVENT_NOTIFICATION_TOGGLE, () => { | ||||
|             this.notificationOpen = !this.notificationOpen; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     renderSidebarItems(): TemplateResult { | ||||
|         return html``; | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html` | ||||
|             ${until(new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve().then(version => { | ||||
|                 if (version.versionCurrent !== VERSION) { | ||||
|                     return html`<ak-banner> | ||||
|                         ${t`A newer version of the frontend is available.`} | ||||
|                         <button @click=${() => { window.location.reload(true); }}> | ||||
|                             ${t`Reload`} | ||||
|                         </button> | ||||
|                     </ak-banner>`; | ||||
|                 } | ||||
|                 return html``; | ||||
|             }))} | ||||
|             ${until(me().then((u) => { | ||||
|                 if (u.original) { | ||||
|                     return html`<ak-banner> | ||||
|                         ${t`You're currently impersonating ${u.user.username}.`} | ||||
|                         <a href=${`/-/impersonation/end/?back=${window.location.pathname}%23${window.location.hash}`}> | ||||
|                             ${t`Stop impersonation`} | ||||
|                         </a> | ||||
|                     </ak-banner>`; | ||||
|                 } | ||||
|                 return html``; | ||||
|             }))} | ||||
|             <div class="pf-c-page"> | ||||
|                 <ak-sidebar class="pf-c-page__sidebar ${this.sidebarOpen ? "pf-m-expanded" : "pf-m-collapsed"}"> | ||||
|                     ${this.renderSidebarItems()} | ||||
|                 </ak-sidebar> | ||||
|                 <div class="pf-c-page__drawer"> | ||||
|                     <div class="pf-c-drawer ${this.notificationOpen ? "pf-m-expanded" : "pf-m-collapsed"}"> | ||||
|                         <div class="pf-c-drawer__main"> | ||||
|                             <div class="pf-c-drawer__content"> | ||||
|                                 <div class="pf-c-drawer__body"> | ||||
|                                     <main class="pf-c-page__main"> | ||||
|                                         <ak-router-outlet role="main" class="pf-c-page__main" tabindex="-1" id="main-content" defaultUrl="/library"> | ||||
|                                         </ak-router-outlet> | ||||
|                                     </main> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                             <ak-notification-drawer class="pf-c-drawer__panel pf-m-width-33"> | ||||
|                             </ak-notification-drawer> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div>`; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -32,7 +32,7 @@ msgstr "6 digits, widely compatible" | ||||
| msgid "8 digits, not compatible with apps like Google Authenticator" | ||||
| msgstr "8 digits, not compatible with apps like Google Authenticator" | ||||
|  | ||||
| #: src/interfaces/Interface.ts | ||||
| #: src/interfaces/AdminInterface.ts | ||||
| msgid "A newer version of the frontend is available." | ||||
| msgstr "A newer version of the frontend is available." | ||||
|  | ||||
| @ -674,11 +674,14 @@ msgstr "Configuration" | ||||
| #: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts | ||||
| #: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts | ||||
| #: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts | ||||
| #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||
| #: src/pages/stages/password/PasswordStageForm.ts | ||||
| msgid "Configuration flow" | ||||
| msgstr "Configuration flow" | ||||
|  | ||||
| #: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts | ||||
| msgid "Configuration stage" | ||||
| msgstr "Configuration stage" | ||||
|  | ||||
| #: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts | ||||
| msgid "Configure WebAuthn" | ||||
| msgstr "Configure WebAuthn" | ||||
| @ -1283,10 +1286,6 @@ msgstr "Email address" | ||||
| msgid "Email info:" | ||||
| msgstr "Email info:" | ||||
|  | ||||
| #: src/flows/stages/identification/IdentificationStage.ts | ||||
| msgid "Email or username" | ||||
| msgstr "Email or username" | ||||
|  | ||||
| #: src/pages/stages/prompt/PromptForm.ts | ||||
| msgid "Email: Text field with Email type." | ||||
| msgstr "Email: Text field with Email type." | ||||
| @ -1392,6 +1391,7 @@ msgstr "Event {0}" | ||||
| msgid "Events" | ||||
| msgstr "Events" | ||||
|  | ||||
| #: src/pages/events/EventInfo.ts | ||||
| #: src/pages/events/EventInfo.ts | ||||
| #: src/pages/events/EventInfo.ts | ||||
| msgid "Exception" | ||||
| @ -2498,6 +2498,10 @@ msgstr "Only send notification once, for example when sending a webhook into a c | ||||
| msgid "Open application" | ||||
| msgstr "Open application" | ||||
|  | ||||
| #: src/pages/events/EventInfo.ts | ||||
| msgid "Open issue on GitHub..." | ||||
| msgstr "Open issue on GitHub..." | ||||
|  | ||||
| #: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts | ||||
| msgid "OpenID Configuration Issuer" | ||||
| msgstr "OpenID Configuration Issuer" | ||||
| @ -2928,10 +2932,6 @@ msgstr "Regular expressions for which authentication is not required. Each new l | ||||
| msgid "Related" | ||||
| msgstr "Related" | ||||
|  | ||||
| #: src/interfaces/Interface.ts | ||||
| msgid "Reload" | ||||
| msgstr "Reload" | ||||
|  | ||||
| #: src/pages/stages/user_logout/UserLogoutStageForm.ts | ||||
| msgid "Remove the user from the current session." | ||||
| msgstr "Remove the user from the current session." | ||||
| @ -3409,10 +3409,6 @@ msgstr "Status: Disabled" | ||||
| msgid "Status: Enabled" | ||||
| msgstr "Status: Enabled" | ||||
|  | ||||
| #: src/interfaces/Interface.ts | ||||
| msgid "Stop impersonation" | ||||
| msgstr "Stop impersonation" | ||||
|  | ||||
| #: src/pages/events/EventInfo.ts | ||||
| #: src/pages/stages/email/EmailStageForm.ts | ||||
| msgid "Subject" | ||||
| @ -3772,6 +3768,7 @@ msgstr "Task finished with warnings" | ||||
| msgid "Template" | ||||
| msgstr "Template" | ||||
|  | ||||
| #: src/pages/events/EventListPage.ts | ||||
| #: src/pages/tenants/TenantListPage.ts | ||||
| msgid "Tenant" | ||||
| msgstr "Tenant" | ||||
| @ -3981,6 +3978,11 @@ msgstr "UI settings" | ||||
| msgid "UID" | ||||
| msgstr "UID" | ||||
|  | ||||
| #: src/flows/stages/identification/IdentificationStage.ts | ||||
| #: src/pages/stages/identification/IdentificationStageForm.ts | ||||
| msgid "UPN" | ||||
| msgstr "UPN" | ||||
|  | ||||
| #: src/pages/sources/oauth/OAuthSourceForm.ts | ||||
| msgid "URL settings" | ||||
| msgstr "URL settings" | ||||
| @ -4517,9 +4519,9 @@ msgstr "Yes" | ||||
| msgid "You can only select providers that match the type of the outpost." | ||||
| msgstr "You can only select providers that match the type of the outpost." | ||||
|  | ||||
| #: src/interfaces/Interface.ts | ||||
| msgid "You're currently impersonating {0}." | ||||
| msgstr "You're currently impersonating {0}." | ||||
| #: src/interfaces/AdminInterface.ts | ||||
| msgid "You're currently impersonating {0}. Click to stop." | ||||
| msgstr "You're currently impersonating {0}. Click to stop." | ||||
|  | ||||
| #: src/pages/stages/password/PasswordStageForm.ts | ||||
| msgid "authentik Builtin Database" | ||||
| @ -4529,6 +4531,10 @@ msgstr "authentik Builtin Database" | ||||
| msgid "authentik LDAP Backend" | ||||
| msgstr "authentik LDAP Backend" | ||||
|  | ||||
| #: src/elements/forms/DeleteForm.ts | ||||
| msgid "connecting object will be deleted" | ||||
| msgstr "connecting object will be deleted" | ||||
|  | ||||
| #: src/elements/Tabs.ts | ||||
| msgid "no tabs defined" | ||||
| msgstr "no tabs defined" | ||||
|  | ||||
| @ -669,10 +669,13 @@ msgstr "" | ||||
| #:  | ||||
| #:  | ||||
| #:  | ||||
| #:  | ||||
| msgid "Configuration flow" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| msgid "Configuration stage" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| msgid "Configure WebAuthn" | ||||
| msgstr "" | ||||
| @ -1275,10 +1278,6 @@ msgstr "" | ||||
| msgid "Email info:" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| msgid "Email or username" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| msgid "Email: Text field with Email type." | ||||
| msgstr "" | ||||
| @ -1384,6 +1383,7 @@ msgstr "" | ||||
| msgid "Events" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| #:  | ||||
| #:  | ||||
| msgid "Exception" | ||||
| @ -2490,6 +2490,10 @@ msgstr "" | ||||
| msgid "Open application" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| msgid "Open issue on GitHub..." | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| msgid "OpenID Configuration Issuer" | ||||
| msgstr "" | ||||
| @ -2920,10 +2924,6 @@ msgstr "" | ||||
| msgid "Related" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| msgid "Reload" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| msgid "Remove the user from the current session." | ||||
| msgstr "" | ||||
| @ -3401,10 +3401,6 @@ msgstr "" | ||||
| msgid "Status: Enabled" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| msgid "Stop impersonation" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| #:  | ||||
| msgid "Subject" | ||||
| @ -3764,6 +3760,7 @@ msgstr "" | ||||
| msgid "Template" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| #:  | ||||
| msgid "Tenant" | ||||
| msgstr "" | ||||
| @ -3969,6 +3966,11 @@ msgstr "" | ||||
| msgid "UID" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| #:  | ||||
| msgid "UPN" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| msgid "URL settings" | ||||
| msgstr "" | ||||
| @ -4504,7 +4506,7 @@ msgid "You can only select providers that match the type of the outpost." | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| msgid "You're currently impersonating {0}." | ||||
| msgid "You're currently impersonating {0}. Click to stop." | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| @ -4515,6 +4517,10 @@ msgstr "" | ||||
| msgid "authentik LDAP Backend" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| msgid "connecting object will be deleted" | ||||
| msgstr "" | ||||
|  | ||||
| #:  | ||||
| msgid "no tabs defined" | ||||
| msgstr "" | ||||
|  | ||||
| @ -34,7 +34,7 @@ export class UserCountStatusChart extends AKChart<UserMetrics> { | ||||
|             pageSize: 1 | ||||
|         })).pagination.count; | ||||
|         const superusers = (await api.coreUsersList({ | ||||
|             isSuperuser: "true" | ||||
|             isSuperuser: true | ||||
|         })).pagination.count; | ||||
|         this.centerText = count.toString(); | ||||
|         return { | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { Application, CoreApi, CheckAccessRequestRequest, PolicyTestResult } from "authentik-api"; | ||||
| import { Application, CoreApi, PolicyTestResult } from "authentik-api"; | ||||
| import { t } from "@lingui/macro"; | ||||
| import { customElement, property } from "lit-element"; | ||||
| import { html, TemplateResult } from "lit-html"; | ||||
| @ -8,7 +8,7 @@ import { until } from "lit-html/directives/until"; | ||||
| import "../../elements/forms/HorizontalFormElement"; | ||||
|  | ||||
| @customElement("ak-application-check-access-form") | ||||
| export class ApplicationCheckAccessForm extends Form<CheckAccessRequestRequest> { | ||||
| export class ApplicationCheckAccessForm extends Form<number> { | ||||
|  | ||||
|     @property({attribute: false}) | ||||
|     application!: Application; | ||||
| @ -17,17 +17,17 @@ export class ApplicationCheckAccessForm extends Form<CheckAccessRequestRequest> | ||||
|     result?: PolicyTestResult; | ||||
|  | ||||
|     @property({ attribute: false}) | ||||
|     request?: CheckAccessRequestRequest; | ||||
|     request?: number; | ||||
|  | ||||
|     getSuccessMessage(): string { | ||||
|         return t`Successfully sent test-request.`; | ||||
|     } | ||||
|  | ||||
|     send = (data: CheckAccessRequestRequest): Promise<PolicyTestResult> => { | ||||
|     send = (data: number): Promise<PolicyTestResult> => { | ||||
|         this.request = data; | ||||
|         return new CoreApi(DEFAULT_CONFIG).coreApplicationsCheckAccessCreate({ | ||||
|         return new CoreApi(DEFAULT_CONFIG).coreApplicationsCheckAccessRetrieve({ | ||||
|             slug: this.application?.slug, | ||||
|             checkAccessRequestRequest: data, | ||||
|             forUser: data, | ||||
|         }).then(result => this.result = result); | ||||
|     }; | ||||
|  | ||||
| @ -68,7 +68,7 @@ export class ApplicationCheckAccessForm extends Form<CheckAccessRequestRequest> | ||||
|                         ordering: "username", | ||||
|                     }).then(users => { | ||||
|                         return users.results.map(user => { | ||||
|                             return html`<option ?selected=${user.pk.toString() === this.request?.forUser?.toString()} value=${user.pk}>${user.username}</option>`; | ||||
|                             return html`<option ?selected=${user.pk.toString() === this.request?.toString()} value=${user.pk}>${user.username}</option>`; | ||||
|                         }); | ||||
|                     }), html`<option>${t`Loading...`}</option>`)} | ||||
|                 </select> | ||||
|  | ||||
| @ -5,13 +5,14 @@ import { EventMatcherPolicyActionEnum, FlowsApi } from "authentik-api"; | ||||
| import "../../elements/Spinner"; | ||||
| import "../../elements/Expand"; | ||||
| import { PFSize } from "../../elements/Spinner"; | ||||
| import { EventContext, EventWithContext } from "../../api/Events"; | ||||
| import { EventContext, EventModel, EventWithContext } from "../../api/Events"; | ||||
| import { DEFAULT_CONFIG } from "../../api/Config"; | ||||
|  | ||||
| import PFButton from "@patternfly/patternfly/components/Button/button.css"; | ||||
| import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; | ||||
| import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css"; | ||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||
| import PFList from "@patternfly/patternfly/components/List/list.css"; | ||||
| import { VERSION } from "../../constants"; | ||||
|  | ||||
| @customElement("ak-event-info") | ||||
| export class EventInfo extends LitElement { | ||||
| @ -20,7 +21,7 @@ export class EventInfo extends LitElement { | ||||
|     event!: EventWithContext; | ||||
|  | ||||
|     static get styles(): CSSResult[] { | ||||
|         return [PFBase, PFFlex, PFList, PFDescriptionList, | ||||
|         return [PFBase, PFButton, PFFlex, PFList, PFDescriptionList, | ||||
|             css` | ||||
|                 code { | ||||
|                     display: block; | ||||
| @ -40,7 +41,7 @@ export class EventInfo extends LitElement { | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     getModelInfo(context: EventContext): TemplateResult { | ||||
|     getModelInfo(context: EventModel): TemplateResult { | ||||
|         if (context === null) { | ||||
|             return html`<span>-</span>`; | ||||
|         } | ||||
| @ -50,7 +51,7 @@ export class EventInfo extends LitElement { | ||||
|                     <span class="pf-c-description-list__text">${t`UID`}</span> | ||||
|                 </dt> | ||||
|                 <dd class="pf-c-description-list__description"> | ||||
|                     <div class="pf-c-description-list__text">${context.pk as string}</div> | ||||
|                     <div class="pf-c-description-list__text">${context.pk}</div> | ||||
|                 </dd> | ||||
|             </div> | ||||
|             <div class="pf-c-description-list__group"> | ||||
| @ -58,7 +59,7 @@ export class EventInfo extends LitElement { | ||||
|                     <span class="pf-c-description-list__text">${t`Name`}</span> | ||||
|                 </dt> | ||||
|                 <dd class="pf-c-description-list__description"> | ||||
|                     <div class="pf-c-description-list__text">${context.name as string}</div> | ||||
|                     <div class="pf-c-description-list__text">${context.name}</div> | ||||
|                 </dd> | ||||
|             </div> | ||||
|             <div class="pf-c-description-list__group"> | ||||
| @ -66,7 +67,7 @@ export class EventInfo extends LitElement { | ||||
|                     <span class="pf-c-description-list__text">${t`App`}</span> | ||||
|                 </dt> | ||||
|                 <dd class="pf-c-description-list__description"> | ||||
|                     <div class="pf-c-description-list__text">${context.app as string}</div> | ||||
|                     <div class="pf-c-description-list__text">${context.app}</div> | ||||
|                 </dd> | ||||
|             </div> | ||||
|             <div class="pf-c-description-list__group"> | ||||
| @ -74,7 +75,7 @@ export class EventInfo extends LitElement { | ||||
|                     <span class="pf-c-description-list__text">${t`Model Name`}</span> | ||||
|                 </dt> | ||||
|                 <dd class="pf-c-description-list__description"> | ||||
|                     <div class="pf-c-description-list__text">${context.model_name as string}</div> | ||||
|                     <div class="pf-c-description-list__text">${context.model_name}</div> | ||||
|                 </dd> | ||||
|             </div> | ||||
|         </dl>`; | ||||
| @ -137,6 +138,52 @@ export class EventInfo extends LitElement { | ||||
|             </div>`; | ||||
|     } | ||||
|  | ||||
|     buildGitHubIssueUrl(context: EventContext): string { | ||||
|         const httpRequest = this.event.context.http_request as EventContext; | ||||
|         let title = ""; | ||||
|         if (httpRequest) { | ||||
|             title = `${httpRequest?.method} ${httpRequest?.path}`; | ||||
|         } | ||||
|         // https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-issues/about-automation-for-issues-and-pull-requests-with-query-parameters | ||||
|         const fullBody = ` | ||||
| **Describe the bug** | ||||
| A clear and concise description of what the bug is. | ||||
|  | ||||
| **To Reproduce** | ||||
| Steps to reproduce the behavior: | ||||
| 1. Go to '...' | ||||
| 2. Click on '....' | ||||
| 3. Scroll down to '....' | ||||
| 4. See error | ||||
|  | ||||
| **Expected behavior** | ||||
| A clear and concise description of what you expected to happen. | ||||
|  | ||||
| **Screenshots** | ||||
| If applicable, add screenshots to help explain your problem. | ||||
|  | ||||
| **Logs** | ||||
| <details> | ||||
|     <summary>Stacktrace from authentik</summary> | ||||
|  | ||||
| \`\`\` | ||||
| ${context.message as string} | ||||
| \`\`\` | ||||
| </details> | ||||
|  | ||||
|  | ||||
| **Version and Deployment (please complete the following information):** | ||||
| - authentik version: ${VERSION} | ||||
| - Deployment: [e.g. docker-compose, helm] | ||||
|  | ||||
| **Additional context** | ||||
| Add any other context about the problem here. | ||||
|         `; | ||||
|         return `https://github.com/goauthentik/authentik/issues/ | ||||
| new?labels=bug,from_authentik&title=${encodeURIComponent(title)} | ||||
| &body=${encodeURIComponent(fullBody)}`.trim(); | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         if (!this.event) { | ||||
|             return html`<ak-spinner size=${PFSize.Medium}></ak-spinner>`; | ||||
| @ -147,13 +194,13 @@ export class EventInfo extends LitElement { | ||||
|         case EventMatcherPolicyActionEnum.ModelDeleted: | ||||
|             return html` | ||||
|                 <h3>${t`Affected model:`}</h3> | ||||
|                 ${this.getModelInfo(this.event.context?.model as EventContext)} | ||||
|                 ${this.getModelInfo(this.event.context?.model as EventModel)} | ||||
|                 `; | ||||
|         case EventMatcherPolicyActionEnum.AuthorizeApplication: | ||||
|             return html`<div class="pf-l-flex"> | ||||
|                     <div class="pf-l-flex__item"> | ||||
|                         <h3>${t`Authorized application:`}</h3> | ||||
|                         ${this.getModelInfo(this.event.context.authorized_application as EventContext)} | ||||
|                         ${this.getModelInfo(this.event.context.authorized_application as EventModel)} | ||||
|                     </div> | ||||
|                     <div class="pf-l-flex__item"> | ||||
|                         <h3>${t`Using flow`}</h3> | ||||
| @ -175,7 +222,24 @@ export class EventInfo extends LitElement { | ||||
|         case EventMatcherPolicyActionEnum.SecretView: | ||||
|             return html` | ||||
|                 <h3>${t`Secret:`}</h3> | ||||
|                 ${this.getModelInfo(this.event.context.secret as EventContext)}`; | ||||
|                 ${this.getModelInfo(this.event.context.secret as EventModel)}`; | ||||
|         case EventMatcherPolicyActionEnum.SystemException: | ||||
|             return html` | ||||
|                 <a | ||||
|                     class="pf-c-button pf-m-primary" | ||||
|                     target="_blank" | ||||
|                     href=${this.buildGitHubIssueUrl( | ||||
|                         this.event.context | ||||
|                     )}> | ||||
|                     ${t`Open issue on GitHub...`} | ||||
|                 </a> | ||||
|                 <div class="pf-l-flex"> | ||||
|                     <div class="pf-l-flex__item"> | ||||
|                         <h3>${t`Exception`}</h3> | ||||
|                         <code>${this.event.context.message}</code> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <ak-expand>${this.defaultResponse()}</ak-expand>`; | ||||
|         case EventMatcherPolicyActionEnum.PropertyMappingException: | ||||
|             return html`<div class="pf-l-flex"> | ||||
|                     <div class="pf-l-flex__item"> | ||||
| @ -192,12 +256,12 @@ export class EventInfo extends LitElement { | ||||
|             return html`<div class="pf-l-flex"> | ||||
|                     <div class="pf-l-flex__item"> | ||||
|                         <h3>${t`Binding`}</h3> | ||||
|                         ${this.getModelInfo(this.event.context.binding as EventContext)} | ||||
|                         ${this.getModelInfo(this.event.context.binding as EventModel)} | ||||
|                     </div> | ||||
|                     <div class="pf-l-flex__item"> | ||||
|                         <h3>${t`Request`}</h3> | ||||
|                         <ul class="pf-c-list"> | ||||
|                             <li>${t`Object`}: ${this.getModelInfo((this.event.context.request as EventContext).obj as EventContext)}</li> | ||||
|                             <li>${t`Object`}: ${this.getModelInfo((this.event.context.request as EventContext).obj as EventModel)}</li> | ||||
|                             <li><span>${t`Context`}: <code>${JSON.stringify((this.event.context.request as EventContext).context, null, 4)}</code></span></li> | ||||
|                         </ul> | ||||
|                     </div> | ||||
| @ -211,12 +275,12 @@ export class EventInfo extends LitElement { | ||||
|             return html`<div class="pf-l-flex"> | ||||
|                     <div class="pf-l-flex__item"> | ||||
|                         <h3>${t`Binding`}</h3> | ||||
|                         ${this.getModelInfo(this.event.context.binding as EventContext)} | ||||
|                         ${this.getModelInfo(this.event.context.binding as EventModel)} | ||||
|                     </div> | ||||
|                     <div class="pf-l-flex__item"> | ||||
|                         <h3>${t`Request`}</h3> | ||||
|                         <ul class="pf-c-list"> | ||||
|                             <li>${t`Object`}: ${this.getModelInfo((this.event.context.request as EventContext).obj as EventContext)}</li> | ||||
|                             <li>${t`Object`}: ${this.getModelInfo((this.event.context.request as EventContext).obj as EventModel)}</li> | ||||
|                             <li><span>${t`Context`}: <code>${JSON.stringify((this.event.context.request as EventContext).context, null, 4)}</code></span></li> | ||||
|                         </ul> | ||||
|                     </div> | ||||
| @ -252,7 +316,7 @@ export class EventInfo extends LitElement { | ||||
|                 return html`<div class="pf-l-flex"> | ||||
|                     <div class="pf-l-flex__item"> | ||||
|                         <h3>${t`Using source`}</h3> | ||||
|                         ${this.getModelInfo(this.event.context.using_source as EventContext)} | ||||
|                         ${this.getModelInfo(this.event.context.using_source as EventModel)} | ||||
|                     </div> | ||||
|                 </div>`; | ||||
|             } | ||||
|  | ||||
| @ -44,6 +44,7 @@ export class EventListPage extends TablePage<Event> { | ||||
|             new TableColumn(t`User`, "user"), | ||||
|             new TableColumn(t`Creation Date`, "created"), | ||||
|             new TableColumn(t`Client IP`, "client_ip"), | ||||
|             new TableColumn(t`Tenant`, "tenant_name"), | ||||
|             new TableColumn(""), | ||||
|         ]; | ||||
|     } | ||||
| @ -62,6 +63,7 @@ export class EventListPage extends TablePage<Event> { | ||||
|                 html`-`, | ||||
|             html`<span>${item.created?.toLocaleString()}</span>`, | ||||
|             html`<span>${item.clientIp || "-"}</span>`, | ||||
|             html`<span>${item.tenant?.name || "-"}</span>`, | ||||
|             html`<a href="#/events/log/${item.pk}"> | ||||
|                 <i class="fas fas fa-share-square"></i> | ||||
|             </a>`, | ||||
|  | ||||
| @ -103,7 +103,7 @@ export class FlowListPage extends TablePage<Flow> { | ||||
|                 }}> | ||||
|                 ${t`Execute`} | ||||
|             </button> | ||||
|             <a class="pf-c-button pf-m-secondary" href=${flow.exportUrl}> | ||||
|             <a class="pf-c-button pf-m-secondary" href=${item.exportUrl}> | ||||
|                 ${t`Export`} | ||||
|             </a>`, | ||||
|         ]; | ||||
|  | ||||
| @ -102,7 +102,11 @@ export class OutpostForm extends ModelForm<Outpost, string> { | ||||
|                         ordering: "pk" | ||||
|                     }).then(scs => { | ||||
|                         return scs.results.map(sc => { | ||||
|                             return html`<option value=${ifDefined(sc.pk)} ?selected=${this.instance?.serviceConnection === sc.pk}> | ||||
|                             let selected = this.instance?.serviceConnection === sc.pk; | ||||
|                             if (scs.results.length === 1 && !this.instance) { | ||||
|                                 selected = true; | ||||
|                             } | ||||
|                             return html`<option value=${ifDefined(sc.pk)} ?selected=${selected}> | ||||
|                                 ${sc.name} (${sc.verboseName}) | ||||
|                             </option>`; | ||||
|                         }); | ||||
| @ -134,6 +138,9 @@ export class OutpostForm extends ModelForm<Outpost, string> { | ||||
|                     return YAML.stringify(fc); | ||||
|                 }))}"></ak-codemirror> | ||||
|                 <p class="pf-c-form__helper-text">${t`Set custom attributes using YAML or JSON.`}</p> | ||||
|                 <p class="pf-c-form__helper-text"> | ||||
|                     See <a target="_blank" href="https://goauthentik.io/docs/outposts/outposts#configuration">documentation</a>. | ||||
|                 </p> | ||||
|             </ak-form-element-horizontal> | ||||
|         </form>`; | ||||
|     } | ||||
|  | ||||
| @ -186,7 +186,7 @@ export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> { | ||||
|                             <option value="" ?selected=${this.instance?.rsaKey === undefined}>---------</option> | ||||
|                             ${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({ | ||||
|                                 ordering: "pk", | ||||
|                                 hasKey: "true", | ||||
|                                 hasKey: true, | ||||
|                             }).then(keys => { | ||||
|                                 return keys.results.map(key => { | ||||
|                                     let selected = this.instance?.rsaKey === key.pk; | ||||
|  | ||||
| @ -218,7 +218,7 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> { | ||||
|                             <option value="" ?selected=${this.instance?.certificate === undefined}>---------</option> | ||||
|                             ${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({ | ||||
|                                 ordering: "pk", | ||||
|                                 hasKey: "true", | ||||
|                                 hasKey: true, | ||||
|                             }).then(keys => { | ||||
|                                 return keys.results.map(key => { | ||||
|                                     return html`<option value=${ifDefined(key.pk)} ?selected=${this.instance?.certificate === key.pk}>${key.name}</option>`; | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	