Compare commits
	
		
			48 Commits
		
	
	
		
			version/20
			...
			version/20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 680b182d95 | |||
| b2a832175e | |||
| b3ce8331f5 | |||
| ef0f618234 | |||
| b8a7186a55 | |||
| b39530f873 | |||
| 7937c84f2b | |||
| 621843c60c | |||
| c19da839b1 | |||
| fea1f3be6f | |||
| 6f5ec7838f | |||
| 94300492e7 | |||
| 5d3931c128 | |||
| 262a8b5ae8 | |||
| fe069c5e55 | |||
| c6e60c0ebc | |||
| 90b457c5ee | |||
| 5e724e4299 | |||
| b4c8dd6b91 | |||
| 63d163cc65 | |||
| 2b1356bb91 | |||
| ba9edd6c44 | |||
| 3b2b3262d7 | |||
| 5431e7fe9d | |||
| 7d9c74ce04 | |||
| 60c3cf890a | |||
| 4ec5df6b12 | |||
| 0403f6d373 | |||
| b7f4d15a94 | |||
| 56450887ca | |||
| 9bd613a31d | |||
| 3fe0483dbf | |||
| 63a28ca1e9 | |||
| 2543b075be | |||
| b8bdf7a035 | |||
| a3ff7cea23 | |||
| bb776c2710 | |||
| c9ad87d419 | |||
| 0d81eaffff | |||
| 6930c84425 | |||
| eaaeaccf5d | |||
| efbbd0adcf | |||
| c8d9771640 | |||
| 2b98637ca5 | |||
| e3f7185564 | |||
| d1198fc6c1 | |||
| 8cb5f8fbee | |||
| fad5b09aee | 
| @ -1,5 +1,5 @@ | |||||||
| [bumpversion] | [bumpversion] | ||||||
| current_version = 2021.6.2 | current_version = 2021.6.3 | ||||||
| tag = True | tag = True | ||||||
| commit = True | commit = True | ||||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*) | parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*) | ||||||
|  | |||||||
							
								
								
									
										20
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @ -33,14 +33,14 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           push: ${{ github.event_name == 'release' }} |           push: ${{ github.event_name == 'release' }} | ||||||
|           tags: | |           tags: | | ||||||
|             beryju/authentik:2021.6.2, |             beryju/authentik:2021.6.3, | ||||||
|             beryju/authentik:latest, |             beryju/authentik:latest, | ||||||
|             ghcr.io/goauthentik/server:2021.6.2, |             ghcr.io/goauthentik/server:2021.6.3, | ||||||
|             ghcr.io/goauthentik/server:latest |             ghcr.io/goauthentik/server:latest | ||||||
|           platforms: linux/amd64,linux/arm64 |           platforms: linux/amd64,linux/arm64 | ||||||
|           context: . |           context: . | ||||||
|       - name: Building Docker Image (stable) |       - name: Building Docker Image (stable) | ||||||
|         if: ${{ github.event_name == 'release' && !contains('2021.6.2', 'rc') }} |         if: ${{ github.event_name == 'release' && !contains('2021.6.3', 'rc') }} | ||||||
|         run: | |         run: | | ||||||
|           docker pull beryju/authentik:latest |           docker pull beryju/authentik:latest | ||||||
|           docker tag beryju/authentik:latest beryju/authentik:stable |           docker tag beryju/authentik:latest beryju/authentik:stable | ||||||
| @ -75,14 +75,14 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           push: ${{ github.event_name == 'release' }} |           push: ${{ github.event_name == 'release' }} | ||||||
|           tags: | |           tags: | | ||||||
|             beryju/authentik-proxy:2021.6.2, |             beryju/authentik-proxy:2021.6.3, | ||||||
|             beryju/authentik-proxy:latest, |             beryju/authentik-proxy:latest, | ||||||
|             ghcr.io/goauthentik/proxy:2021.6.2, |             ghcr.io/goauthentik/proxy:2021.6.3, | ||||||
|             ghcr.io/goauthentik/proxy:latest |             ghcr.io/goauthentik/proxy:latest | ||||||
|           file: outpost/proxy.Dockerfile |           file: outpost/proxy.Dockerfile | ||||||
|           platforms: linux/amd64,linux/arm64 |           platforms: linux/amd64,linux/arm64 | ||||||
|       - name: Building Docker Image (stable) |       - name: Building Docker Image (stable) | ||||||
|         if: ${{ github.event_name == 'release' && !contains('2021.6.2', 'rc') }} |         if: ${{ github.event_name == 'release' && !contains('2021.6.3', 'rc') }} | ||||||
|         run: | |         run: | | ||||||
|           docker pull beryju/authentik-proxy:latest |           docker pull beryju/authentik-proxy:latest | ||||||
|           docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable |           docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable | ||||||
| @ -117,14 +117,14 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           push: ${{ github.event_name == 'release' }} |           push: ${{ github.event_name == 'release' }} | ||||||
|           tags: | |           tags: | | ||||||
|             beryju/authentik-ldap:2021.6.2, |             beryju/authentik-ldap:2021.6.3, | ||||||
|             beryju/authentik-ldap:latest, |             beryju/authentik-ldap:latest, | ||||||
|             ghcr.io/goauthentik/ldap:2021.6.2, |             ghcr.io/goauthentik/ldap:2021.6.3, | ||||||
|             ghcr.io/goauthentik/ldap:latest |             ghcr.io/goauthentik/ldap:latest | ||||||
|           file: outpost/ldap.Dockerfile |           file: outpost/ldap.Dockerfile | ||||||
|           platforms: linux/amd64,linux/arm64 |           platforms: linux/amd64,linux/arm64 | ||||||
|       - name: Building Docker Image (stable) |       - name: Building Docker Image (stable) | ||||||
|         if: ${{ github.event_name == 'release' && !contains('2021.6.2', 'rc') }} |         if: ${{ github.event_name == 'release' && !contains('2021.6.3', 'rc') }} | ||||||
|         run: | |         run: | | ||||||
|           docker pull beryju/authentik-ldap:latest |           docker pull beryju/authentik-ldap:latest | ||||||
|           docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable |           docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable | ||||||
| @ -176,6 +176,6 @@ jobs: | |||||||
|           SENTRY_PROJECT: authentik |           SENTRY_PROJECT: authentik | ||||||
|           SENTRY_URL: https://sentry.beryju.org |           SENTRY_URL: https://sentry.beryju.org | ||||||
|         with: |         with: | ||||||
|           version: authentik@2021.6.2 |           version: authentik@2021.6.3 | ||||||
|           environment: beryjuorg-prod |           environment: beryjuorg-prod | ||||||
|           sourcemaps: './web/dist' |           sourcemaps: './web/dist' | ||||||
|  | |||||||
							
								
								
									
										64
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										64
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							| @ -76,11 +76,11 @@ | |||||||
|         }, |         }, | ||||||
|         "asgiref": { |         "asgiref": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee", |                 "sha256:05914d0fa65a21711e732adc6572edad6c8da5f1435c3f0c060689ced5e85195", | ||||||
|                 "sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78" |                 "sha256:d36fa91dd90e3aa3c81a6bd426ccc8fb20bd3d22b0cf14a12800289e9c3e2563" | ||||||
|             ], |             ], | ||||||
|             "markers": "python_version >= '3.6'", |             "markers": "python_version >= '3.6'", | ||||||
|             "version": "==3.3.4" |             "version": "==3.4.0" | ||||||
|         }, |         }, | ||||||
|         "async-timeout": { |         "async-timeout": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -122,19 +122,19 @@ | |||||||
|         }, |         }, | ||||||
|         "boto3": { |         "boto3": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:2c2f70608934b03f9c08f4cd185de223b5abd18245dd4d4800e1fbc2a2523e31", |                 "sha256:6300e9ee9a404038113250bd218e2c4827f5e676efb14e77de2ad2dcb67679bc", | ||||||
|                 "sha256:fccfa81cda69bb2317ed97e7149d7d84d19e6ec3bfbe3f721139e7ac0c407c73" |                 "sha256:be4714f0475c1f5183eea09ddbf568ced6fa41b0fc9976f2698b8442e1b17303" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==1.17.98" |             "version": "==1.17.102" | ||||||
|         }, |         }, | ||||||
|         "botocore": { |         "botocore": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:b2a49de4ee04b690142c8e7240f0f5758e3f7673dd39cf398efe893bf5e11c3f", |                 "sha256:2f57f7ceed1598d96cc497aeb45317db5d3b21a5aafea4732d0e561d0fc2a8fa", | ||||||
|                 "sha256:b955b23fe2fbdbbc8e66f37fe2970de6b5d8169f940b200bcf434751709d38f6" |                 "sha256:bdf08a4f7f01ead00d386848f089c08270499711447569c18d0db60023619c06" | ||||||
|             ], |             ], | ||||||
|             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", |             "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.98" |             "version": "==1.20.102" | ||||||
|         }, |         }, | ||||||
|         "cachetools": { |         "cachetools": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -165,11 +165,11 @@ | |||||||
|         }, |         }, | ||||||
|         "celery": { |         "celery": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:54436cd97b031bf2e08064223240e2a83d601d9414bcb1b702f94c6c33c29485", |                 "sha256:8d9a3de9162965e97f8e8cc584c67aad83b3f7a267584fa47701ed11c3e0d4b0", | ||||||
|                 "sha256:b5399d76cf70d5cfac3ec993f8796ec1aa90d4cef55972295751f384758a80d7" |                 "sha256:9dab2170b4038f7bf10ef2861dbf486ddf1d20592290a1040f7b7a1259705d42" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==5.1.1" |             "version": "==5.1.2" | ||||||
|         }, |         }, | ||||||
|         "certifi": { |         "certifi": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -948,10 +948,30 @@ | |||||||
|         }, |         }, | ||||||
|         "pyrsistent": { |         "pyrsistent": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" |                 "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2", | ||||||
|  |                 "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7", | ||||||
|  |                 "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea", | ||||||
|  |                 "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426", | ||||||
|  |                 "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710", | ||||||
|  |                 "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1", | ||||||
|  |                 "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396", | ||||||
|  |                 "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2", | ||||||
|  |                 "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680", | ||||||
|  |                 "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35", | ||||||
|  |                 "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427", | ||||||
|  |                 "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b", | ||||||
|  |                 "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b", | ||||||
|  |                 "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f", | ||||||
|  |                 "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef", | ||||||
|  |                 "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c", | ||||||
|  |                 "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4", | ||||||
|  |                 "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d", | ||||||
|  |                 "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78", | ||||||
|  |                 "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b", | ||||||
|  |                 "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72" | ||||||
|             ], |             ], | ||||||
|             "markers": "python_version >= '3.5'", |             "markers": "python_version >= '3.6'", | ||||||
|             "version": "==0.17.3" |             "version": "==0.18.0" | ||||||
|         }, |         }, | ||||||
|         "python-dateutil": { |         "python-dateutil": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -1167,11 +1187,11 @@ | |||||||
|                 "secure" |                 "secure" | ||||||
|             ], |             ], | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", |                 "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", | ||||||
|                 "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" |                 "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==1.26.5" |             "version": "==1.26.6" | ||||||
|         }, |         }, | ||||||
|         "uvicorn": { |         "uvicorn": { | ||||||
|             "extras": [ |             "extras": [ | ||||||
| @ -1565,7 +1585,7 @@ | |||||||
|                 "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56", |                 "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56", | ||||||
|                 "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c" |                 "sha256:8e2c107091cfec7286bc0f68a547d0ba4c094d460b732075b6fba674f1035c0c" | ||||||
|             ], |             ], | ||||||
|             "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", |             "markers": "python_version < '4' and python_full_version >= '3.6.1'", | ||||||
|             "version": "==5.9.1" |             "version": "==5.9.1" | ||||||
|         }, |         }, | ||||||
|         "lazy-object-proxy": { |         "lazy-object-proxy": { | ||||||
| @ -1838,11 +1858,11 @@ | |||||||
|                 "secure" |                 "secure" | ||||||
|             ], |             ], | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", |                 "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", | ||||||
|                 "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" |                 "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==1.26.5" |             "version": "==1.26.6" | ||||||
|         }, |         }, | ||||||
|         "wrapt": { |         "wrapt": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|  | |||||||
| @ -1,3 +1,3 @@ | |||||||
| """authentik""" | """authentik""" | ||||||
| __version__ = "2021.6.2" | __version__ = "2021.6.3" | ||||||
| ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ def token_from_header(raw_header: bytes) -> Optional[Token]: | |||||||
|     auth_credentials = raw_header.decode() |     auth_credentials = raw_header.decode() | ||||||
|     if auth_credentials == "" or " " not in auth_credentials: |     if auth_credentials == "" or " " not in auth_credentials: | ||||||
|         return None |         return None | ||||||
|     auth_type, auth_credentials = auth_credentials.split() |     auth_type, _, auth_credentials = auth_credentials.partition(" ") | ||||||
|     if auth_type.lower() not in ["basic", "bearer"]: |     if auth_type.lower() not in ["basic", "bearer"]: | ||||||
|         LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower()) |         LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower()) | ||||||
|         raise AuthenticationFailed("Unsupported authentication type") |         raise AuthenticationFailed("Unsupported authentication type") | ||||||
|  | |||||||
| @ -213,7 +213,7 @@ class SourceFlowManager: | |||||||
|         planner = FlowPlanner(flow) |         planner = FlowPlanner(flow) | ||||||
|         plan = planner.plan(self.request, kwargs) |         plan = planner.plan(self.request, kwargs) | ||||||
|         for stage in self.get_stages_to_append(flow): |         for stage in self.get_stages_to_append(flow): | ||||||
|             plan.append(stage) |             plan.append_stage(stage=stage) | ||||||
|         self.request.session[SESSION_KEY_PLAN] = plan |         self.request.session[SESSION_KEY_PLAN] = plan | ||||||
|         return redirect_with_qs( |         return redirect_with_qs( | ||||||
|             "authentik_core:if-flow", |             "authentik_core:if-flow", | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ | |||||||
| <script src="{% static 'dist/FlowInterface.js' %}?v={{ ak_version }}" type="module"></script> | <script src="{% static 'dist/FlowInterface.js' %}?v={{ ak_version }}" type="module"></script> | ||||||
| <style> | <style> | ||||||
| .pf-c-background-image::before { | .pf-c-background-image::before { | ||||||
|     background-image: url("{{ flow.background_url }}"); |     --ak-flow-background: url("{{ flow.background_url }}"); | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ | |||||||
| {% block head %} | {% block head %} | ||||||
| <style> | <style> | ||||||
| .pf-c-background-image::before { | .pf-c-background-image::before { | ||||||
|     background-image: url("/static/dist/assets/images/flow_background.jpg"); |     --ak-flow-background: url("/static/dist/assets/images/flow_background.jpg"); | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  | |||||||
| @ -6,11 +6,11 @@ from drf_spectacular.types import OpenApiTypes | |||||||
| from drf_spectacular.utils import OpenApiParameter, extend_schema | from drf_spectacular.utils import OpenApiParameter, extend_schema | ||||||
| from guardian.shortcuts import get_objects_for_user | from guardian.shortcuts import get_objects_for_user | ||||||
| from rest_framework.decorators import action | from rest_framework.decorators import action | ||||||
| from rest_framework.fields import CharField, DictField, IntegerField | from rest_framework.fields import DictField, IntegerField | ||||||
| from rest_framework.request import Request | from rest_framework.request import Request | ||||||
| from rest_framework.response import Response | from rest_framework.response import Response | ||||||
| from rest_framework.serializers import ModelSerializer | from rest_framework.serializers import ModelSerializer | ||||||
| from rest_framework.viewsets import ReadOnlyModelViewSet | from rest_framework.viewsets import ModelViewSet | ||||||
|  |  | ||||||
| from authentik.core.api.utils import PassiveSerializer, TypeCreateSerializer | from authentik.core.api.utils import PassiveSerializer, TypeCreateSerializer | ||||||
| from authentik.events.models import Event, EventAction | from authentik.events.models import Event, EventAction | ||||||
| @ -19,11 +19,6 @@ from authentik.events.models import Event, EventAction | |||||||
| class EventSerializer(ModelSerializer): | class EventSerializer(ModelSerializer): | ||||||
|     """Event Serializer""" |     """Event Serializer""" | ||||||
|  |  | ||||||
|     # Since we only use this serializer for read-only operations, |  | ||||||
|     # no checking of the action is done here. |  | ||||||
|     # This allows clients to check wildcards, prefixes and custom types |  | ||||||
|     action = CharField() |  | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|         model = Event |         model = Event | ||||||
| @ -96,7 +91,7 @@ class EventsFilter(django_filters.FilterSet): | |||||||
|         fields = ["action", "client_ip", "username"] |         fields = ["action", "client_ip", "username"] | ||||||
|  |  | ||||||
|  |  | ||||||
| class EventViewSet(ReadOnlyModelViewSet): | class EventViewSet(ModelViewSet): | ||||||
|     """Event Read-Only Viewset""" |     """Event Read-Only Viewset""" | ||||||
|  |  | ||||||
|     queryset = Event.objects.all() |     queryset = Event.objects.all() | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ from authentik.core.models import User | |||||||
| from authentik.events.models import Event, EventAction, Notification | from authentik.events.models import Event, EventAction, Notification | ||||||
| from authentik.events.signals import EventNewThread | from authentik.events.signals import EventNewThread | ||||||
| from authentik.events.utils import model_to_dict | from authentik.events.utils import model_to_dict | ||||||
|  | from authentik.lib.sentry import before_send | ||||||
| from authentik.lib.utils.errors import exception_to_string | from authentik.lib.utils.errors import exception_to_string | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -62,12 +63,13 @@ class AuditMiddleware: | |||||||
|  |  | ||||||
|         if settings.DEBUG: |         if settings.DEBUG: | ||||||
|             return |             return | ||||||
|         thread = EventNewThread( |         if before_send({}, {"exc_info": (None, exception, None)}) is not None: | ||||||
|             EventAction.SYSTEM_EXCEPTION, |             thread = EventNewThread( | ||||||
|             request, |                 EventAction.SYSTEM_EXCEPTION, | ||||||
|             message=exception_to_string(exception), |                 request, | ||||||
|         ) |                 message=exception_to_string(exception), | ||||||
|         thread.run() |             ) | ||||||
|  |             thread.run() | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     # pylint: disable=unused-argument |     # pylint: disable=unused-argument | ||||||
|  | |||||||
| @ -105,7 +105,11 @@ def notification_transport( | |||||||
|     """Send notification over specified transport""" |     """Send notification over specified transport""" | ||||||
|     self.save_on_success = False |     self.save_on_success = False | ||||||
|     try: |     try: | ||||||
|         notification: Notification = Notification.objects.get(pk=notification_pk) |         notification: Notification = Notification.objects.filter( | ||||||
|  |             pk=notification_pk | ||||||
|  |         ).first() | ||||||
|  |         if not notification: | ||||||
|  |             return | ||||||
|         transport: NotificationTransport = NotificationTransport.objects.get( |         transport: NotificationTransport = NotificationTransport.objects.get( | ||||||
|             pk=transport_pk |             pk=transport_pk | ||||||
|         ) |         ) | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ class FlowStageBindingSerializer(ModelSerializer): | |||||||
|             "re_evaluate_policies", |             "re_evaluate_policies", | ||||||
|             "order", |             "order", | ||||||
|             "policy_engine_mode", |             "policy_engine_mode", | ||||||
|  |             "invalid_response_action", | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -5,8 +5,7 @@ from typing import TYPE_CHECKING, Optional | |||||||
| from django.http.request import HttpRequest | from django.http.request import HttpRequest | ||||||
| from structlog.stdlib import get_logger | from structlog.stdlib import get_logger | ||||||
|  |  | ||||||
| from authentik.core.models import User | from authentik.flows.models import FlowStageBinding | ||||||
| from authentik.flows.models import Stage |  | ||||||
| from authentik.policies.engine import PolicyEngine | from authentik.policies.engine import PolicyEngine | ||||||
| from authentik.policies.models import PolicyBinding | from authentik.policies.models import PolicyBinding | ||||||
|  |  | ||||||
| @ -22,11 +21,14 @@ class StageMarker: | |||||||
|  |  | ||||||
|     # pylint: disable=unused-argument |     # pylint: disable=unused-argument | ||||||
|     def process( |     def process( | ||||||
|         self, plan: "FlowPlan", stage: Stage, http_request: Optional[HttpRequest] |         self, | ||||||
|     ) -> Optional[Stage]: |         plan: "FlowPlan", | ||||||
|  |         binding: FlowStageBinding, | ||||||
|  |         http_request: HttpRequest, | ||||||
|  |     ) -> Optional[FlowStageBinding]: | ||||||
|         """Process callback for this marker. This should be overridden by sub-classes. |         """Process callback for this marker. This should be overridden by sub-classes. | ||||||
|         If a stage should be removed, return None.""" |         If a stage should be removed, return None.""" | ||||||
|         return stage |         return binding | ||||||
|  |  | ||||||
|  |  | ||||||
| @dataclass | @dataclass | ||||||
| @ -34,24 +36,34 @@ class ReevaluateMarker(StageMarker): | |||||||
|     """Reevaluate Marker, forces stage's policies to be evaluated again.""" |     """Reevaluate Marker, forces stage's policies to be evaluated again.""" | ||||||
|  |  | ||||||
|     binding: PolicyBinding |     binding: PolicyBinding | ||||||
|     user: User |  | ||||||
|  |  | ||||||
|     def process( |     def process( | ||||||
|         self, plan: "FlowPlan", stage: Stage, http_request: Optional[HttpRequest] |         self, | ||||||
|     ) -> Optional[Stage]: |         plan: "FlowPlan", | ||||||
|  |         binding: FlowStageBinding, | ||||||
|  |         http_request: HttpRequest, | ||||||
|  |     ) -> Optional[FlowStageBinding]: | ||||||
|         """Re-evaluate policies bound to stage, and if they fail, remove from plan""" |         """Re-evaluate policies bound to stage, and if they fail, remove from plan""" | ||||||
|         engine = PolicyEngine(self.binding, self.user) |         from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER | ||||||
|  |  | ||||||
|  |         LOGGER.debug( | ||||||
|  |             "f(plan_inst)[re-eval marker]: running re-evaluation", | ||||||
|  |             binding=binding, | ||||||
|  |             policy_binding=self.binding, | ||||||
|  |         ) | ||||||
|  |         engine = PolicyEngine( | ||||||
|  |             self.binding, plan.context.get(PLAN_CONTEXT_PENDING_USER, http_request.user) | ||||||
|  |         ) | ||||||
|         engine.use_cache = False |         engine.use_cache = False | ||||||
|         if http_request: |         engine.request.set_http_request(http_request) | ||||||
|             engine.request.set_http_request(http_request) |  | ||||||
|         engine.request.context = plan.context |         engine.request.context = plan.context | ||||||
|         engine.build() |         engine.build() | ||||||
|         result = engine.result |         result = engine.result | ||||||
|         if result.passing: |         if result.passing: | ||||||
|             return stage |             return binding | ||||||
|         LOGGER.warning( |         LOGGER.warning( | ||||||
|             "f(plan_inst)[re-eval marker]: stage failed re-evaluation", |             "f(plan_inst)[re-eval marker]: binding failed re-evaluation", | ||||||
|             stage=stage, |             binding=binding, | ||||||
|             messages=result.messages, |             messages=result.messages, | ||||||
|         ) |         ) | ||||||
|         return None |         return None | ||||||
|  | |||||||
| @ -135,7 +135,7 @@ class Migration(migrations.Migration): | |||||||
|  |  | ||||||
|     dependencies = [ |     dependencies = [ | ||||||
|         ("authentik_flows", "0017_auto_20210329_1334"), |         ("authentik_flows", "0017_auto_20210329_1334"), | ||||||
|         ("authentik_stages_user_write", "__latest__"), |         ("authentik_stages_user_write", "0002_auto_20200918_1653"), | ||||||
|         ("authentik_stages_user_login", "__latest__"), |         ("authentik_stages_user_login", "__latest__"), | ||||||
|         ("authentik_stages_password", "0002_passwordstage_change_flow"), |         ("authentik_stages_password", "0002_passwordstage_change_flow"), | ||||||
|         ("authentik_policies", "0001_initial"), |         ("authentik_policies", "0001_initial"), | ||||||
|  | |||||||
| @ -0,0 +1,22 @@ | |||||||
|  | # Generated by Django 3.2.4 on 2021-06-27 16:20 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("authentik_flows", "0020_flow_compatibility_mode"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="flowstagebinding", | ||||||
|  |             name="invalid_response_action", | ||||||
|  |             field=models.TextField( | ||||||
|  |                 choices=[("retry", "Retry"), ("continue", "Continue")], | ||||||
|  |                 default="retry", | ||||||
|  |                 help_text="Configure how the flow executor should handle an invalid response to a challenge. RETRY returns the error message and a similar challenge to the executor while CONTINUE continues with the next stage.", | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @ -27,6 +27,14 @@ class NotConfiguredAction(models.TextChoices): | |||||||
|     CONFIGURE = "configure" |     CONFIGURE = "configure" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class InvalidResponseAction(models.TextChoices): | ||||||
|  |     """Configure how the flow executor should handle invalid responses to challenges""" | ||||||
|  |  | ||||||
|  |     RETRY = "retry" | ||||||
|  |     RESTART = "restart" | ||||||
|  |     RESTART_WITH_CONTEXT = "restart_with_context" | ||||||
|  |  | ||||||
|  |  | ||||||
| class FlowDesignation(models.TextChoices): | class FlowDesignation(models.TextChoices): | ||||||
|     """Designation of what a Flow should be used for. At a later point, this |     """Designation of what a Flow should be used for. At a later point, this | ||||||
|     should be replaced by a database entry.""" |     should be replaced by a database entry.""" | ||||||
| @ -201,6 +209,17 @@ class FlowStageBinding(SerializerModel, PolicyBindingModel): | |||||||
|         help_text=_("Evaluate policies when the Stage is present to the user."), |         help_text=_("Evaluate policies when the Stage is present to the user."), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     invalid_response_action = models.TextField( | ||||||
|  |         choices=InvalidResponseAction.choices, | ||||||
|  |         default=InvalidResponseAction.RETRY, | ||||||
|  |         help_text=_( | ||||||
|  |             "Configure how the flow executor should handle an invalid response to a " | ||||||
|  |             "challenge. RETRY returns the error message and a similar challenge to the " | ||||||
|  |             "executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT " | ||||||
|  |             "restarts the flow while keeping the current context." | ||||||
|  |         ), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     order = models.IntegerField() |     order = models.IntegerField() | ||||||
|  |  | ||||||
|     objects = InheritanceManager() |     objects = InheritanceManager() | ||||||
|  | |||||||
| @ -52,33 +52,41 @@ class FlowPlan: | |||||||
|  |  | ||||||
|     flow_pk: str |     flow_pk: str | ||||||
|  |  | ||||||
|     stages: list[Stage] = field(default_factory=list) |     bindings: list[FlowStageBinding] = field(default_factory=list) | ||||||
|     context: dict[str, Any] = field(default_factory=dict) |     context: dict[str, Any] = field(default_factory=dict) | ||||||
|     markers: list[StageMarker] = field(default_factory=list) |     markers: list[StageMarker] = field(default_factory=list) | ||||||
|  |  | ||||||
|     def append(self, stage: Stage, marker: Optional[StageMarker] = None): |     def append_stage(self, stage: Stage, marker: Optional[StageMarker] = None): | ||||||
|         """Append `stage` to all stages, optionall with stage marker""" |         """Append `stage` to all stages, optionall with stage marker""" | ||||||
|         self.stages.append(stage) |         return self.append(FlowStageBinding(stage=stage), marker) | ||||||
|  |  | ||||||
|  |     def append(self, binding: FlowStageBinding, marker: Optional[StageMarker] = None): | ||||||
|  |         """Append `stage` to all stages, optionall with stage marker""" | ||||||
|  |         self.bindings.append(binding) | ||||||
|         self.markers.append(marker or StageMarker()) |         self.markers.append(marker or StageMarker()) | ||||||
|  |  | ||||||
|     def insert(self, stage: Stage, marker: Optional[StageMarker] = None): |     def insert_stage(self, stage: Stage, marker: Optional[StageMarker] = None): | ||||||
|         """Insert stage into plan, as immediate next stage""" |         """Insert stage into plan, as immediate next stage""" | ||||||
|         self.stages.insert(1, stage) |         self.bindings.insert(1, FlowStageBinding(stage=stage, order=0)) | ||||||
|         self.markers.insert(1, marker or StageMarker()) |         self.markers.insert(1, marker or StageMarker()) | ||||||
|  |  | ||||||
|     def next(self, http_request: Optional[HttpRequest]) -> Optional[Stage]: |     def next(self, http_request: Optional[HttpRequest]) -> Optional[FlowStageBinding]: | ||||||
|         """Return next pending stage from the bottom of the list""" |         """Return next pending stage from the bottom of the list""" | ||||||
|         if not self.has_stages: |         if not self.has_stages: | ||||||
|             return None |             return None | ||||||
|         stage = self.stages[0] |         binding = self.bindings[0] | ||||||
|         marker = self.markers[0] |         marker = self.markers[0] | ||||||
|  |  | ||||||
|         if marker.__class__ is not StageMarker: |         if marker.__class__ is not StageMarker: | ||||||
|             LOGGER.debug("f(plan_inst): stage has marker", stage=stage, marker=marker) |             LOGGER.debug( | ||||||
|         marked_stage = marker.process(self, stage, http_request) |                 "f(plan_inst): stage has marker", binding=binding, marker=marker | ||||||
|  |             ) | ||||||
|  |         marked_stage = marker.process(self, binding, http_request) | ||||||
|         if not marked_stage: |         if not marked_stage: | ||||||
|             LOGGER.debug("f(plan_inst): marker returned none, next stage", stage=stage) |             LOGGER.debug( | ||||||
|             self.stages.remove(stage) |                 "f(plan_inst): marker returned none, next stage", binding=binding | ||||||
|  |             ) | ||||||
|  |             self.bindings.remove(binding) | ||||||
|             self.markers.remove(marker) |             self.markers.remove(marker) | ||||||
|             if not self.has_stages: |             if not self.has_stages: | ||||||
|                 return None |                 return None | ||||||
| @ -89,12 +97,12 @@ class FlowPlan: | |||||||
|     def pop(self): |     def pop(self): | ||||||
|         """Pop next pending stage from bottom of list""" |         """Pop next pending stage from bottom of list""" | ||||||
|         self.markers.pop(0) |         self.markers.pop(0) | ||||||
|         self.stages.pop(0) |         self.bindings.pop(0) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def has_stages(self) -> bool: |     def has_stages(self) -> bool: | ||||||
|         """Check if there are any stages left in this plan""" |         """Check if there are any stages left in this plan""" | ||||||
|         return len(self.markers) + len(self.stages) > 0 |         return len(self.markers) + len(self.bindings) > 0 | ||||||
|  |  | ||||||
|  |  | ||||||
| class FlowPlanner: | class FlowPlanner: | ||||||
| @ -161,7 +169,7 @@ class FlowPlanner: | |||||||
|             plan = self._build_plan(user, request, default_context) |             plan = self._build_plan(user, request, default_context) | ||||||
|             cache.set(cache_key(self.flow, user), plan, CACHE_TIMEOUT) |             cache.set(cache_key(self.flow, user), plan, CACHE_TIMEOUT) | ||||||
|             GAUGE_FLOWS_CACHED.update() |             GAUGE_FLOWS_CACHED.update() | ||||||
|             if not plan.stages and not self.allow_empty_flows: |             if not plan.bindings and not self.allow_empty_flows: | ||||||
|                 raise EmptyFlowException() |                 raise EmptyFlowException() | ||||||
|             return plan |             return plan | ||||||
|  |  | ||||||
| @ -216,9 +224,9 @@ class FlowPlanner: | |||||||
|                         "f(plan): stage has re-evaluate marker", |                         "f(plan): stage has re-evaluate marker", | ||||||
|                         stage=binding.stage, |                         stage=binding.stage, | ||||||
|                     ) |                     ) | ||||||
|                     marker = ReevaluateMarker(binding=binding, user=user) |                     marker = ReevaluateMarker(binding=binding) | ||||||
|                 if stage: |                 if stage: | ||||||
|                     plan.append(stage, marker) |                     plan.append(binding, marker) | ||||||
|             HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug) |             HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug) | ||||||
|         self._logger.debug( |         self._logger.debug( | ||||||
|             "f(plan): finished building", |             "f(plan): finished building", | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ from authentik.flows.challenge import ( | |||||||
|     HttpChallengeResponse, |     HttpChallengeResponse, | ||||||
|     WithUserInfoChallenge, |     WithUserInfoChallenge, | ||||||
| ) | ) | ||||||
|  | from authentik.flows.models import InvalidResponseAction | ||||||
| from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER | from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER | ||||||
| from authentik.flows.views import FlowExecutorView | from authentik.flows.views import FlowExecutorView | ||||||
|  |  | ||||||
| @ -69,7 +70,13 @@ class ChallengeStageView(StageView): | |||||||
|         """Return a challenge for the frontend to solve""" |         """Return a challenge for the frontend to solve""" | ||||||
|         challenge = self._get_challenge(*args, **kwargs) |         challenge = self._get_challenge(*args, **kwargs) | ||||||
|         if not challenge.is_valid(): |         if not challenge.is_valid(): | ||||||
|             LOGGER.warning(challenge.errors, stage_view=self, challenge=challenge) |             LOGGER.warning( | ||||||
|  |                 "f(ch): Invalid challenge", | ||||||
|  |                 binding=self.executor.current_binding, | ||||||
|  |                 errors=challenge.errors, | ||||||
|  |                 stage_view=self, | ||||||
|  |                 challenge=challenge, | ||||||
|  |             ) | ||||||
|         return HttpChallengeResponse(challenge) |         return HttpChallengeResponse(challenge) | ||||||
|  |  | ||||||
|     # pylint: disable=unused-argument |     # pylint: disable=unused-argument | ||||||
| @ -77,6 +84,21 @@ class ChallengeStageView(StageView): | |||||||
|         """Handle challenge response""" |         """Handle challenge response""" | ||||||
|         challenge: ChallengeResponse = self.get_response_instance(data=request.data) |         challenge: ChallengeResponse = self.get_response_instance(data=request.data) | ||||||
|         if not challenge.is_valid(): |         if not challenge.is_valid(): | ||||||
|  |             if self.executor.current_binding.invalid_response_action in [ | ||||||
|  |                 InvalidResponseAction.RESTART, | ||||||
|  |                 InvalidResponseAction.RESTART_WITH_CONTEXT, | ||||||
|  |             ]: | ||||||
|  |                 keep_context = ( | ||||||
|  |                     self.executor.current_binding.invalid_response_action | ||||||
|  |                     == InvalidResponseAction.RESTART_WITH_CONTEXT | ||||||
|  |                 ) | ||||||
|  |                 LOGGER.debug( | ||||||
|  |                     "f(ch): Invalid response, restarting flow", | ||||||
|  |                     binding=self.executor.current_binding, | ||||||
|  |                     stage_view=self, | ||||||
|  |                     keep_context=keep_context, | ||||||
|  |                 ) | ||||||
|  |                 return self.executor.restart_flow(keep_context) | ||||||
|             return self.challenge_invalid(challenge) |             return self.challenge_invalid(challenge) | ||||||
|         return self.challenge_valid(challenge) |         return self.challenge_valid(challenge) | ||||||
|  |  | ||||||
| @ -126,5 +148,10 @@ class ChallengeStageView(StageView): | |||||||
|                 ) |                 ) | ||||||
|         challenge_response.initial_data["response_errors"] = full_errors |         challenge_response.initial_data["response_errors"] = full_errors | ||||||
|         if not challenge_response.is_valid(): |         if not challenge_response.is_valid(): | ||||||
|             LOGGER.warning(challenge_response.errors) |             LOGGER.warning( | ||||||
|  |                 "f(ch): invalid challenge response", | ||||||
|  |                 binding=self.executor.current_binding, | ||||||
|  |                 errors=challenge_response.errors, | ||||||
|  |                 stage_view=self, | ||||||
|  |             ) | ||||||
|         return HttpChallengeResponse(challenge_response) |         return HttpChallengeResponse(challenge_response) | ||||||
|  | |||||||
| @ -182,8 +182,8 @@ class TestFlowPlanner(TestCase): | |||||||
|             planner = FlowPlanner(flow) |             planner = FlowPlanner(flow) | ||||||
|             plan = planner.plan(request) |             plan = planner.plan(request) | ||||||
|  |  | ||||||
|             self.assertEqual(plan.stages[0], binding.stage) |             self.assertEqual(plan.bindings[0], binding) | ||||||
|             self.assertEqual(plan.stages[1], binding2.stage) |             self.assertEqual(plan.bindings[1], binding2) | ||||||
|  |  | ||||||
|             self.assertIsInstance(plan.markers[0], StageMarker) |             self.assertIsInstance(plan.markers[0], StageMarker) | ||||||
|             self.assertIsInstance(plan.markers[1], ReevaluateMarker) |             self.assertIsInstance(plan.markers[1], ReevaluateMarker) | ||||||
|  | |||||||
| @ -11,15 +11,23 @@ from authentik.core.models import User | |||||||
| from authentik.flows.challenge import ChallengeTypes | from authentik.flows.challenge import ChallengeTypes | ||||||
| from authentik.flows.exceptions import FlowNonApplicableException | from authentik.flows.exceptions import FlowNonApplicableException | ||||||
| from authentik.flows.markers import ReevaluateMarker, StageMarker | from authentik.flows.markers import ReevaluateMarker, StageMarker | ||||||
| from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding | from authentik.flows.models import ( | ||||||
|  |     Flow, | ||||||
|  |     FlowDesignation, | ||||||
|  |     FlowStageBinding, | ||||||
|  |     InvalidResponseAction, | ||||||
|  | ) | ||||||
| from authentik.flows.planner import FlowPlan, FlowPlanner | from authentik.flows.planner import FlowPlan, FlowPlanner | ||||||
| from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageView | from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageView | ||||||
| from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_PLAN, FlowExecutorView | from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_PLAN, FlowExecutorView | ||||||
| from authentik.lib.config import CONFIG | from authentik.lib.config import CONFIG | ||||||
| from authentik.policies.dummy.models import DummyPolicy | from authentik.policies.dummy.models import DummyPolicy | ||||||
| from authentik.policies.models import PolicyBinding | from authentik.policies.models import PolicyBinding | ||||||
|  | from authentik.policies.reputation.models import ReputationPolicy | ||||||
| from authentik.policies.types import PolicyResult | from authentik.policies.types import PolicyResult | ||||||
|  | from authentik.stages.deny.models import DenyStage | ||||||
| from authentik.stages.dummy.models import DummyStage | from authentik.stages.dummy.models import DummyStage | ||||||
|  | from authentik.stages.identification.models import IdentificationStage, UserFields | ||||||
|  |  | ||||||
| POLICY_RETURN_FALSE = PropertyMock(return_value=PolicyResult(False)) | POLICY_RETURN_FALSE = PropertyMock(return_value=PolicyResult(False)) | ||||||
| POLICY_RETURN_TRUE = MagicMock(return_value=PolicyResult(True)) | POLICY_RETURN_TRUE = MagicMock(return_value=PolicyResult(True)) | ||||||
| @ -52,8 +60,9 @@ class TestFlowExecutor(TestCase): | |||||||
|             designation=FlowDesignation.AUTHENTICATION, |             designation=FlowDesignation.AUTHENTICATION, | ||||||
|         ) |         ) | ||||||
|         stage = DummyStage.objects.create(name="dummy") |         stage = DummyStage.objects.create(name="dummy") | ||||||
|  |         binding = FlowStageBinding(target=flow, stage=stage, order=0) | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=flow.pk.hex + "a", stages=[stage], markers=[StageMarker()] |             flow_pk=flow.pk.hex + "a", bindings=[binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         session[SESSION_KEY_PLAN] = plan |         session[SESSION_KEY_PLAN] = plan | ||||||
| @ -163,7 +172,7 @@ class TestFlowExecutor(TestCase): | |||||||
|         # Check that two stages are in plan |         # Check that two stages are in plan | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         plan: FlowPlan = session[SESSION_KEY_PLAN] |         plan: FlowPlan = session[SESSION_KEY_PLAN] | ||||||
|         self.assertEqual(len(plan.stages), 2) |         self.assertEqual(len(plan.bindings), 2) | ||||||
|         # Second request, submit form, one stage left |         # Second request, submit form, one stage left | ||||||
|         response = self.client.post(exec_url) |         response = self.client.post(exec_url) | ||||||
|         # Second request redirects to the same URL |         # Second request redirects to the same URL | ||||||
| @ -172,7 +181,7 @@ class TestFlowExecutor(TestCase): | |||||||
|         # Check that two stages are in plan |         # Check that two stages are in plan | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         plan: FlowPlan = session[SESSION_KEY_PLAN] |         plan: FlowPlan = session[SESSION_KEY_PLAN] | ||||||
|         self.assertEqual(len(plan.stages), 1) |         self.assertEqual(len(plan.bindings), 1) | ||||||
|  |  | ||||||
|     @patch( |     @patch( | ||||||
|         "authentik.flows.views.to_stage_response", |         "authentik.flows.views.to_stage_response", | ||||||
| @ -213,8 +222,8 @@ class TestFlowExecutor(TestCase): | |||||||
|  |  | ||||||
|             plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] |             plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] | ||||||
|  |  | ||||||
|             self.assertEqual(plan.stages[0], binding.stage) |             self.assertEqual(plan.bindings[0], binding) | ||||||
|             self.assertEqual(plan.stages[1], binding2.stage) |             self.assertEqual(plan.bindings[1], binding2) | ||||||
|  |  | ||||||
|             self.assertIsInstance(plan.markers[0], StageMarker) |             self.assertIsInstance(plan.markers[0], StageMarker) | ||||||
|             self.assertIsInstance(plan.markers[1], ReevaluateMarker) |             self.assertIsInstance(plan.markers[1], ReevaluateMarker) | ||||||
| @ -267,9 +276,9 @@ class TestFlowExecutor(TestCase): | |||||||
|             self.assertEqual(response.status_code, 200) |             self.assertEqual(response.status_code, 200) | ||||||
|             plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] |             plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] | ||||||
|  |  | ||||||
|             self.assertEqual(plan.stages[0], binding.stage) |             self.assertEqual(plan.bindings[0], binding) | ||||||
|             self.assertEqual(plan.stages[1], binding2.stage) |             self.assertEqual(plan.bindings[1], binding2) | ||||||
|             self.assertEqual(plan.stages[2], binding3.stage) |             self.assertEqual(plan.bindings[2], binding3) | ||||||
|  |  | ||||||
|             self.assertIsInstance(plan.markers[0], StageMarker) |             self.assertIsInstance(plan.markers[0], StageMarker) | ||||||
|             self.assertIsInstance(plan.markers[1], ReevaluateMarker) |             self.assertIsInstance(plan.markers[1], ReevaluateMarker) | ||||||
| @ -281,8 +290,8 @@ class TestFlowExecutor(TestCase): | |||||||
|  |  | ||||||
|             plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] |             plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] | ||||||
|  |  | ||||||
|             self.assertEqual(plan.stages[0], binding2.stage) |             self.assertEqual(plan.bindings[0], binding2) | ||||||
|             self.assertEqual(plan.stages[1], binding3.stage) |             self.assertEqual(plan.bindings[1], binding3) | ||||||
|  |  | ||||||
|             self.assertIsInstance(plan.markers[0], StageMarker) |             self.assertIsInstance(plan.markers[0], StageMarker) | ||||||
|             self.assertIsInstance(plan.markers[1], StageMarker) |             self.assertIsInstance(plan.markers[1], StageMarker) | ||||||
| @ -338,9 +347,9 @@ class TestFlowExecutor(TestCase): | |||||||
|             self.assertEqual(response.status_code, 200) |             self.assertEqual(response.status_code, 200) | ||||||
|             plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] |             plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] | ||||||
|  |  | ||||||
|             self.assertEqual(plan.stages[0], binding.stage) |             self.assertEqual(plan.bindings[0], binding) | ||||||
|             self.assertEqual(plan.stages[1], binding2.stage) |             self.assertEqual(plan.bindings[1], binding2) | ||||||
|             self.assertEqual(plan.stages[2], binding3.stage) |             self.assertEqual(plan.bindings[2], binding3) | ||||||
|  |  | ||||||
|             self.assertIsInstance(plan.markers[0], StageMarker) |             self.assertIsInstance(plan.markers[0], StageMarker) | ||||||
|             self.assertIsInstance(plan.markers[1], ReevaluateMarker) |             self.assertIsInstance(plan.markers[1], ReevaluateMarker) | ||||||
| @ -352,8 +361,8 @@ class TestFlowExecutor(TestCase): | |||||||
|  |  | ||||||
|             plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] |             plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] | ||||||
|  |  | ||||||
|             self.assertEqual(plan.stages[0], binding2.stage) |             self.assertEqual(plan.bindings[0], binding2) | ||||||
|             self.assertEqual(plan.stages[1], binding3.stage) |             self.assertEqual(plan.bindings[1], binding3) | ||||||
|  |  | ||||||
|             self.assertIsInstance(plan.markers[0], StageMarker) |             self.assertIsInstance(plan.markers[0], StageMarker) | ||||||
|             self.assertIsInstance(plan.markers[1], StageMarker) |             self.assertIsInstance(plan.markers[1], StageMarker) | ||||||
| @ -364,7 +373,7 @@ class TestFlowExecutor(TestCase): | |||||||
|  |  | ||||||
|             plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] |             plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] | ||||||
|  |  | ||||||
|             self.assertEqual(plan.stages[0], binding3.stage) |             self.assertEqual(plan.bindings[0], binding3) | ||||||
|  |  | ||||||
|             self.assertIsInstance(plan.markers[0], StageMarker) |             self.assertIsInstance(plan.markers[0], StageMarker) | ||||||
|  |  | ||||||
| @ -438,10 +447,10 @@ class TestFlowExecutor(TestCase): | |||||||
|  |  | ||||||
|             plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] |             plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] | ||||||
|  |  | ||||||
|             self.assertEqual(plan.stages[0], binding.stage) |             self.assertEqual(plan.bindings[0], binding) | ||||||
|             self.assertEqual(plan.stages[1], binding2.stage) |             self.assertEqual(plan.bindings[1], binding2) | ||||||
|             self.assertEqual(plan.stages[2], binding3.stage) |             self.assertEqual(plan.bindings[2], binding3) | ||||||
|             self.assertEqual(plan.stages[3], binding4.stage) |             self.assertEqual(plan.bindings[3], binding4) | ||||||
|  |  | ||||||
|             self.assertIsInstance(plan.markers[0], StageMarker) |             self.assertIsInstance(plan.markers[0], StageMarker) | ||||||
|             self.assertIsInstance(plan.markers[1], ReevaluateMarker) |             self.assertIsInstance(plan.markers[1], ReevaluateMarker) | ||||||
| @ -512,3 +521,78 @@ class TestFlowExecutor(TestCase): | |||||||
|  |  | ||||||
|         stage_view = StageView(executor) |         stage_view = StageView(executor) | ||||||
|         self.assertEqual(ident, stage_view.get_pending_user(for_display=True).username) |         self.assertEqual(ident, stage_view.get_pending_user(for_display=True).username) | ||||||
|  |  | ||||||
|  |     def test_invalid_restart(self): | ||||||
|  |         """Test flow that restarts on invalid entry""" | ||||||
|  |         flow = Flow.objects.create( | ||||||
|  |             name="restart-on-invalid", | ||||||
|  |             slug="restart-on-invalid", | ||||||
|  |             designation=FlowDesignation.AUTHENTICATION, | ||||||
|  |         ) | ||||||
|  |         # Stage 0 is a deny stage that is added dynamically | ||||||
|  |         # when the reputation policy says so | ||||||
|  |         deny_stage = DenyStage.objects.create(name="deny") | ||||||
|  |         reputation_policy = ReputationPolicy.objects.create( | ||||||
|  |             name="reputation", threshold=-1, check_ip=False | ||||||
|  |         ) | ||||||
|  |         deny_binding = FlowStageBinding.objects.create( | ||||||
|  |             target=flow, | ||||||
|  |             stage=deny_stage, | ||||||
|  |             order=0, | ||||||
|  |             evaluate_on_plan=False, | ||||||
|  |             re_evaluate_policies=True, | ||||||
|  |         ) | ||||||
|  |         PolicyBinding.objects.create( | ||||||
|  |             policy=reputation_policy, target=deny_binding, order=0 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Stage 1 is an identification stage | ||||||
|  |         ident_stage = IdentificationStage.objects.create( | ||||||
|  |             name="ident", | ||||||
|  |             user_fields=[UserFields.E_MAIL], | ||||||
|  |         ) | ||||||
|  |         FlowStageBinding.objects.create( | ||||||
|  |             target=flow, | ||||||
|  |             stage=ident_stage, | ||||||
|  |             order=1, | ||||||
|  |             invalid_response_action=InvalidResponseAction.RESTART_WITH_CONTEXT, | ||||||
|  |         ) | ||||||
|  |         exec_url = reverse( | ||||||
|  |             "authentik_api:flow-executor", kwargs={"flow_slug": flow.slug} | ||||||
|  |         ) | ||||||
|  |         # First request, run the planner | ||||||
|  |         response = self.client.get(exec_url) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |         self.assertJSONEqual( | ||||||
|  |             force_str(response.content), | ||||||
|  |             { | ||||||
|  |                 "type": ChallengeTypes.NATIVE.value, | ||||||
|  |                 "component": "ak-stage-identification", | ||||||
|  |                 "flow_info": { | ||||||
|  |                     "background": flow.background_url, | ||||||
|  |                     "cancel_url": reverse("authentik_flows:cancel"), | ||||||
|  |                     "title": "", | ||||||
|  |                 }, | ||||||
|  |                 "password_fields": False, | ||||||
|  |                 "primary_action": "Log in", | ||||||
|  |                 "sources": [], | ||||||
|  |                 "user_fields": [UserFields.E_MAIL], | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |         response = self.client.post( | ||||||
|  |             exec_url, {"uid_field": "invalid-string"}, follow=True | ||||||
|  |         ) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |         self.assertJSONEqual( | ||||||
|  |             force_str(response.content), | ||||||
|  |             { | ||||||
|  |                 "component": "ak-stage-access-denied", | ||||||
|  |                 "error_message": None, | ||||||
|  |                 "flow_info": { | ||||||
|  |                     "background": flow.background_url, | ||||||
|  |                     "cancel_url": reverse("authentik_flows:cancel"), | ||||||
|  |                     "title": "", | ||||||
|  |                 }, | ||||||
|  |                 "type": ChallengeTypes.NATIVE.value, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ from typing import Any, Optional | |||||||
|  |  | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
|  | from django.core.cache import cache | ||||||
| from django.http import Http404, HttpRequest, HttpResponse, HttpResponseRedirect | from django.http import Http404, HttpRequest, HttpResponse, HttpResponseRedirect | ||||||
| from django.http.request import QueryDict | from django.http.request import QueryDict | ||||||
| from django.shortcuts import get_object_or_404, redirect | from django.shortcuts import get_object_or_404, redirect | ||||||
| @ -37,7 +38,13 @@ from authentik.flows.challenge import ( | |||||||
|     WithUserInfoChallenge, |     WithUserInfoChallenge, | ||||||
| ) | ) | ||||||
| from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException | from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException | ||||||
| from authentik.flows.models import ConfigurableStage, Flow, FlowDesignation, Stage | from authentik.flows.models import ( | ||||||
|  |     ConfigurableStage, | ||||||
|  |     Flow, | ||||||
|  |     FlowDesignation, | ||||||
|  |     FlowStageBinding, | ||||||
|  |     Stage, | ||||||
|  | ) | ||||||
| from authentik.flows.planner import ( | from authentik.flows.planner import ( | ||||||
|     PLAN_CONTEXT_PENDING_USER, |     PLAN_CONTEXT_PENDING_USER, | ||||||
|     PLAN_CONTEXT_REDIRECT, |     PLAN_CONTEXT_REDIRECT, | ||||||
| @ -107,6 +114,7 @@ class FlowExecutorView(APIView): | |||||||
|     flow: Flow |     flow: Flow | ||||||
|  |  | ||||||
|     plan: Optional[FlowPlan] = None |     plan: Optional[FlowPlan] = None | ||||||
|  |     current_binding: FlowStageBinding | ||||||
|     current_stage: Stage |     current_stage: Stage | ||||||
|     current_stage_view: View |     current_stage_view: View | ||||||
|  |  | ||||||
| @ -159,11 +167,12 @@ class FlowExecutorView(APIView): | |||||||
|         request.session[SESSION_KEY_GET] = QueryDict(request.GET.get("query", "")) |         request.session[SESSION_KEY_GET] = QueryDict(request.GET.get("query", "")) | ||||||
|         # We don't save the Plan after getting the next stage |         # We don't save the Plan after getting the next stage | ||||||
|         # as it hasn't been successfully passed yet |         # as it hasn't been successfully passed yet | ||||||
|         next_stage = self.plan.next(self.request) |         next_binding = self.plan.next(self.request) | ||||||
|         if not next_stage: |         if not next_binding: | ||||||
|             self._logger.debug("f(exec): no more stages, flow is done.") |             self._logger.debug("f(exec): no more stages, flow is done.") | ||||||
|             return self._flow_done() |             return self._flow_done() | ||||||
|         self.current_stage = next_stage |         self.current_binding = next_binding | ||||||
|  |         self.current_stage = next_binding.stage | ||||||
|         self._logger.debug( |         self._logger.debug( | ||||||
|             "f(exec): Current stage", |             "f(exec): Current stage", | ||||||
|             current_stage=self.current_stage, |             current_stage=self.current_stage, | ||||||
| @ -268,8 +277,31 @@ class FlowExecutorView(APIView): | |||||||
|         planner = FlowPlanner(self.flow) |         planner = FlowPlanner(self.flow) | ||||||
|         plan = planner.plan(self.request) |         plan = planner.plan(self.request) | ||||||
|         self.request.session[SESSION_KEY_PLAN] = plan |         self.request.session[SESSION_KEY_PLAN] = plan | ||||||
|  |         try: | ||||||
|  |             # Call the has_stages getter to check that | ||||||
|  |             # there are no issues with the class we might've gotten | ||||||
|  |             # from the cache. If there are errors, just delete all cached flows | ||||||
|  |             _ = plan.has_stages | ||||||
|  |         except Exception:  # pylint: disable=broad-except | ||||||
|  |             keys = cache.keys("flow_*") | ||||||
|  |             cache.delete_many(keys) | ||||||
|  |             return self._initiate_plan() | ||||||
|         return plan |         return plan | ||||||
|  |  | ||||||
|  |     def restart_flow(self, keep_context=False) -> HttpResponse: | ||||||
|  |         """Restart the currently active flow, optionally keeping the current context""" | ||||||
|  |         planner = FlowPlanner(self.flow) | ||||||
|  |         default_context = None | ||||||
|  |         if keep_context: | ||||||
|  |             default_context = self.plan.context | ||||||
|  |         plan = planner.plan(self.request, default_context) | ||||||
|  |         self.request.session[SESSION_KEY_PLAN] = plan | ||||||
|  |         kwargs = self.kwargs | ||||||
|  |         kwargs.update({"flow_slug": self.flow.slug}) | ||||||
|  |         return redirect_with_qs( | ||||||
|  |             "authentik_api:flow-executor", self.request.GET, **kwargs | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def _flow_done(self) -> HttpResponse: |     def _flow_done(self) -> HttpResponse: | ||||||
|         """User Successfully passed all stages""" |         """User Successfully passed all stages""" | ||||||
|         # Since this is wrapped by the ExecutorShell, the next argument is saved in the session |         # Since this is wrapped by the ExecutorShell, the next argument is saved in the session | ||||||
| @ -293,10 +325,10 @@ class FlowExecutorView(APIView): | |||||||
|         ) |         ) | ||||||
|         self.plan.pop() |         self.plan.pop() | ||||||
|         self.request.session[SESSION_KEY_PLAN] = self.plan |         self.request.session[SESSION_KEY_PLAN] = self.plan | ||||||
|         if self.plan.stages: |         if self.plan.bindings: | ||||||
|             self._logger.debug( |             self._logger.debug( | ||||||
|                 "f(exec): Continuing with next stage", |                 "f(exec): Continuing with next stage", | ||||||
|                 remaining=len(self.plan.stages), |                 remaining=len(self.plan.bindings), | ||||||
|             ) |             ) | ||||||
|             kwargs = self.kwargs |             kwargs = self.kwargs | ||||||
|             kwargs.update({"flow_slug": self.flow.slug}) |             kwargs.update({"flow_slug": self.flow.slug}) | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ import re | |||||||
| from textwrap import indent | from textwrap import indent | ||||||
| from typing import Any, Iterable, Optional | from typing import Any, Iterable, Optional | ||||||
|  |  | ||||||
|  | from django.core.exceptions import FieldError | ||||||
| from requests import Session | from requests import Session | ||||||
| from rest_framework.serializers import ValidationError | from rest_framework.serializers import ValidationError | ||||||
| from sentry_sdk.hub import Hub | from sentry_sdk.hub import Hub | ||||||
| @ -29,10 +30,10 @@ class BaseEvaluator: | |||||||
|         # update website/docs/expressions/_objects.md |         # update website/docs/expressions/_objects.md | ||||||
|         # update website/docs/expressions/_functions.md |         # update website/docs/expressions/_functions.md | ||||||
|         self._globals = { |         self._globals = { | ||||||
|             "regex_match": BaseEvaluator.expr_filter_regex_match, |             "regex_match": BaseEvaluator.expr_regex_match, | ||||||
|             "regex_replace": BaseEvaluator.expr_filter_regex_replace, |             "regex_replace": BaseEvaluator.expr_regex_replace, | ||||||
|             "ak_is_group_member": BaseEvaluator.expr_func_is_group_member, |             "ak_is_group_member": BaseEvaluator.expr_is_group_member, | ||||||
|             "ak_user_by": BaseEvaluator.expr_func_user_by, |             "ak_user_by": BaseEvaluator.expr_user_by, | ||||||
|             "ak_logger": get_logger(), |             "ak_logger": get_logger(), | ||||||
|             "requests": Session(), |             "requests": Session(), | ||||||
|         } |         } | ||||||
| @ -40,25 +41,28 @@ class BaseEvaluator: | |||||||
|         self._filename = "BaseEvalautor" |         self._filename = "BaseEvalautor" | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def expr_filter_regex_match(value: Any, regex: str) -> bool: |     def expr_regex_match(value: Any, regex: str) -> bool: | ||||||
|         """Expression Filter to run re.search""" |         """Expression Filter to run re.search""" | ||||||
|         return re.search(regex, value) is None |         return re.search(regex, value) is not None | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def expr_filter_regex_replace(value: Any, regex: str, repl: str) -> str: |     def expr_regex_replace(value: Any, regex: str, repl: str) -> str: | ||||||
|         """Expression Filter to run re.sub""" |         """Expression Filter to run re.sub""" | ||||||
|         return re.sub(regex, repl, value) |         return re.sub(regex, repl, value) | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def expr_func_user_by(**filters) -> Optional[User]: |     def expr_user_by(**filters) -> Optional[User]: | ||||||
|         """Get user by filters""" |         """Get user by filters""" | ||||||
|         users = User.objects.filter(**filters) |         try: | ||||||
|         if users: |             users = User.objects.filter(**filters) | ||||||
|             return users.first() |             if users: | ||||||
|         return None |                 return users.first() | ||||||
|  |             return None | ||||||
|  |         except FieldError: | ||||||
|  |             return None | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def expr_func_is_group_member(user: User, **group_filters) -> bool: |     def expr_is_group_member(user: User, **group_filters) -> bool: | ||||||
|         """Check if `user` is member of group with name `group_name`""" |         """Check if `user` is member of group with name `group_name`""" | ||||||
|         return user.ak_groups.filter(**group_filters).exists() |         return user.ak_groups.filter(**group_filters).exists() | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										32
									
								
								authentik/lib/tests/test_evaluator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								authentik/lib/tests/test_evaluator.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | |||||||
|  | """Test Evaluator base functions""" | ||||||
|  | from django.test import TestCase | ||||||
|  |  | ||||||
|  | from authentik.core.models import User | ||||||
|  | from authentik.lib.expression.evaluator import BaseEvaluator | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestEvaluator(TestCase): | ||||||
|  |     """Test Evaluator base functions""" | ||||||
|  |  | ||||||
|  |     def test_regex_match(self): | ||||||
|  |         """Test expr_regex_match""" | ||||||
|  |         self.assertFalse(BaseEvaluator.expr_regex_match("foo", "bar")) | ||||||
|  |         self.assertTrue(BaseEvaluator.expr_regex_match("foo", "foo")) | ||||||
|  |  | ||||||
|  |     def test_regex_replace(self): | ||||||
|  |         """Test expr_regex_replace""" | ||||||
|  |         self.assertEqual(BaseEvaluator.expr_regex_replace("foo", "o", "a"), "faa") | ||||||
|  |  | ||||||
|  |     def test_user_by(self): | ||||||
|  |         """Test expr_user_by""" | ||||||
|  |         self.assertIsNotNone(BaseEvaluator.expr_user_by(username="akadmin")) | ||||||
|  |         self.assertIsNone(BaseEvaluator.expr_user_by(username="bar")) | ||||||
|  |         self.assertIsNone(BaseEvaluator.expr_user_by(foo="bar")) | ||||||
|  |  | ||||||
|  |     def test_is_group_member(self): | ||||||
|  |         """Test expr_is_group_member""" | ||||||
|  |         self.assertFalse( | ||||||
|  |             BaseEvaluator.expr_is_group_member( | ||||||
|  |                 User.objects.get(username="akadmin"), name="test" | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
| @ -53,6 +53,27 @@ class DockerController(BaseController): | |||||||
|                 return True |                 return True | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
|  |     def _comp_ports(self, container: Container) -> bool: | ||||||
|  |         """Check that the container has the correct ports exposed. Return true if container needs | ||||||
|  |         to be rebuilt.""" | ||||||
|  |         # with TEST enabled, we use host-network | ||||||
|  |         if settings.TEST: | ||||||
|  |             return False | ||||||
|  |         # When the container isn't running, the API doesn't report any port mappings | ||||||
|  |         if container.status != "running": | ||||||
|  |             return False | ||||||
|  |         # {'6379/tcp': [{'HostIp': '127.0.0.1', 'HostPort': '6379'}]} | ||||||
|  |         for port in self.deployment_ports: | ||||||
|  |             key = f"{port.inner_port or port.port}/{port.protocol.lower()}" | ||||||
|  |             if key not in container.ports: | ||||||
|  |                 return True | ||||||
|  |             host_matching = False | ||||||
|  |             for host_port in container.ports[key]: | ||||||
|  |                 host_matching = host_port.get("HostPort") == port.port | ||||||
|  |             if not host_matching: | ||||||
|  |                 return True | ||||||
|  |         return False | ||||||
|  |  | ||||||
|     def _get_container(self) -> tuple[Container, bool]: |     def _get_container(self) -> tuple[Container, bool]: | ||||||
|         container_name = f"authentik-proxy-{self.outpost.uuid.hex}" |         container_name = f"authentik-proxy-{self.outpost.uuid.hex}" | ||||||
|         try: |         try: | ||||||
| @ -98,6 +119,11 @@ class DockerController(BaseController): | |||||||
|                     ) |                     ) | ||||||
|                     self.down() |                     self.down() | ||||||
|                     return self.up() |                     return self.up() | ||||||
|  |             # Check container's ports | ||||||
|  |             if self._comp_ports(container): | ||||||
|  |                 self.logger.info("Container has mis-matched ports, re-creating...") | ||||||
|  |                 self.down() | ||||||
|  |                 return self.up() | ||||||
|             # Check that container values match our values |             # Check that container values match our values | ||||||
|             if self._comp_env(container): |             if self._comp_env(container): | ||||||
|                 self.logger.info("Container has outdated config, re-creating...") |                 self.logger.info("Container has outdated config, re-creating...") | ||||||
|  | |||||||
| @ -405,7 +405,10 @@ class Outpost(models.Model): | |||||||
|  |  | ||||||
|     def get_required_objects(self) -> Iterable[Union[models.Model, str]]: |     def get_required_objects(self) -> Iterable[Union[models.Model, str]]: | ||||||
|         """Get an iterator of all objects the user needs read access to""" |         """Get an iterator of all objects the user needs read access to""" | ||||||
|         objects: list[Union[models.Model, str]] = [self] |         objects: list[Union[models.Model, str]] = [ | ||||||
|  |             self, | ||||||
|  |             "authentik_events.add_event", | ||||||
|  |         ] | ||||||
|         for provider in ( |         for provider in ( | ||||||
|             Provider.objects.filter(outpost=self).select_related().select_subclasses() |             Provider.objects.filter(outpost=self).select_related().select_subclasses() | ||||||
|         ): |         ): | ||||||
|  | |||||||
| @ -33,21 +33,21 @@ class ReputationPolicy(Policy): | |||||||
|  |  | ||||||
|     def passes(self, request: PolicyRequest) -> PolicyResult: |     def passes(self, request: PolicyRequest) -> PolicyResult: | ||||||
|         remote_ip = get_client_ip(request.http_request) |         remote_ip = get_client_ip(request.http_request) | ||||||
|         passing = True |         passing = False | ||||||
|         if self.check_ip: |         if self.check_ip: | ||||||
|             score = cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0) |             score = cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0) | ||||||
|             passing = passing and score <= self.threshold |             passing += passing or score <= self.threshold | ||||||
|             LOGGER.debug("Score for IP", ip=remote_ip, score=score, passing=passing) |             LOGGER.debug("Score for IP", ip=remote_ip, score=score, passing=passing) | ||||||
|         if self.check_username: |         if self.check_username: | ||||||
|             score = cache.get_or_set(CACHE_KEY_USER_PREFIX + request.user.username, 0) |             score = cache.get_or_set(CACHE_KEY_USER_PREFIX + request.user.username, 0) | ||||||
|             passing = passing and score <= self.threshold |             passing += passing or score <= self.threshold | ||||||
|             LOGGER.debug( |             LOGGER.debug( | ||||||
|                 "Score for Username", |                 "Score for Username", | ||||||
|                 username=request.user.username, |                 username=request.user.username, | ||||||
|                 score=score, |                 score=score, | ||||||
|                 passing=passing, |                 passing=passing, | ||||||
|             ) |             ) | ||||||
|         return PolicyResult(passing) |         return PolicyResult(bool(passing)) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|  | |||||||
| @ -474,7 +474,7 @@ class RefreshToken(ExpiringModel, BaseGrantModel): | |||||||
|         now = int(time.time()) |         now = int(time.time()) | ||||||
|         iat_time = now |         iat_time = now | ||||||
|         exp_time = int( |         exp_time = int( | ||||||
|             now + timedelta_from_string(self.provider.token_validity).seconds |             now + timedelta_from_string(self.provider.token_validity).total_seconds() | ||||||
|         ) |         ) | ||||||
|         # We use the timestamp of the user's last successful login (EventAction.LOGIN) for auth_time |         # We use the timestamp of the user's last successful login (EventAction.LOGIN) for auth_time | ||||||
|         auth_events = Event.objects.filter( |         auth_events = Event.objects.filter( | ||||||
|  | |||||||
| @ -374,9 +374,9 @@ class OAuthFulfillmentStage(StageView): | |||||||
|             query_fragment["code"] = code.code |             query_fragment["code"] = code.code | ||||||
|  |  | ||||||
|         query_fragment["token_type"] = "bearer" |         query_fragment["token_type"] = "bearer" | ||||||
|         query_fragment["expires_in"] = timedelta_from_string( |         query_fragment["expires_in"] = int( | ||||||
|             self.provider.token_validity |             timedelta_from_string(self.provider.token_validity).total_seconds() | ||||||
|         ).seconds |         ) | ||||||
|         query_fragment["state"] = self.params.state if self.params.state else "" |         query_fragment["state"] = self.params.state if self.params.state else "" | ||||||
|  |  | ||||||
|         return query_fragment |         return query_fragment | ||||||
| @ -468,14 +468,14 @@ class AuthorizationFlowInitView(PolicyAccessView): | |||||||
|         # OpenID clients can specify a `prompt` parameter, and if its set to consent we |         # OpenID clients can specify a `prompt` parameter, and if its set to consent we | ||||||
|         # need to inject a consent stage |         # need to inject a consent stage | ||||||
|         if PROMPT_CONSNET in self.params.prompt: |         if PROMPT_CONSNET in self.params.prompt: | ||||||
|             if not any(isinstance(x, ConsentStageView) for x in plan.stages): |             if not any(isinstance(x.stage, ConsentStageView) for x in plan.bindings): | ||||||
|                 # Plan does not have any consent stage, so we add an in-memory one |                 # Plan does not have any consent stage, so we add an in-memory one | ||||||
|                 stage = ConsentStage( |                 stage = ConsentStage( | ||||||
|                     name="OAuth2 Provider In-memory consent stage", |                     name="OAuth2 Provider In-memory consent stage", | ||||||
|                     mode=ConsentMode.ALWAYS_REQUIRE, |                     mode=ConsentMode.ALWAYS_REQUIRE, | ||||||
|                 ) |                 ) | ||||||
|                 plan.append(stage) |                 plan.append_stage(stage) | ||||||
|         plan.append(in_memory_stage(OAuthFulfillmentStage)) |         plan.append_stage(in_memory_stage(OAuthFulfillmentStage)) | ||||||
|         self.request.session[SESSION_KEY_PLAN] = plan |         self.request.session[SESSION_KEY_PLAN] = plan | ||||||
|         return redirect_with_qs( |         return redirect_with_qs( | ||||||
|             "authentik_core:if-flow", |             "authentik_core:if-flow", | ||||||
|  | |||||||
| @ -215,9 +215,11 @@ class TokenView(View): | |||||||
|             "access_token": refresh_token.access_token, |             "access_token": refresh_token.access_token, | ||||||
|             "refresh_token": refresh_token.refresh_token, |             "refresh_token": refresh_token.refresh_token, | ||||||
|             "token_type": "bearer", |             "token_type": "bearer", | ||||||
|             "expires_in": timedelta_from_string( |             "expires_in": int( | ||||||
|                 self.params.provider.token_validity |                 timedelta_from_string( | ||||||
|             ).seconds, |                     self.params.provider.token_validity | ||||||
|  |                 ).total_seconds() | ||||||
|  |             ), | ||||||
|             "id_token": refresh_token.provider.encode(refresh_token.id_token.to_dict()), |             "id_token": refresh_token.provider.encode(refresh_token.id_token.to_dict()), | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @ -258,9 +260,11 @@ class TokenView(View): | |||||||
|             "access_token": refresh_token.access_token, |             "access_token": refresh_token.access_token, | ||||||
|             "refresh_token": refresh_token.refresh_token, |             "refresh_token": refresh_token.refresh_token, | ||||||
|             "token_type": "bearer", |             "token_type": "bearer", | ||||||
|             "expires_in": timedelta_from_string( |             "expires_in": int( | ||||||
|                 refresh_token.provider.token_validity |                 timedelta_from_string( | ||||||
|             ).seconds, |                     refresh_token.provider.token_validity | ||||||
|  |                 ).total_seconds() | ||||||
|  |             ), | ||||||
|             "id_token": self.params.provider.encode(refresh_token.id_token.to_dict()), |             "id_token": self.params.provider.encode(refresh_token.id_token.to_dict()), | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | |||||||
| @ -79,7 +79,7 @@ class SAMLSSOView(PolicyAccessView): | |||||||
|                 PLAN_CONTEXT_CONSENT_PERMISSIONS: [], |                 PLAN_CONTEXT_CONSENT_PERMISSIONS: [], | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
|         plan.append(in_memory_stage(SAMLFlowFinalView)) |         plan.append_stage(in_memory_stage(SAMLFlowFinalView)) | ||||||
|         request.session[SESSION_KEY_PLAN] = plan |         request.session[SESSION_KEY_PLAN] = plan | ||||||
|         return redirect_with_qs( |         return redirect_with_qs( | ||||||
|             "authentik_core:if-flow", |             "authentik_core:if-flow", | ||||||
|  | |||||||
| @ -153,6 +153,7 @@ SPECTACULAR_SETTINGS = { | |||||||
|         "url": "https://github.com/goauthentik/authentik/blob/master/LICENSE", |         "url": "https://github.com/goauthentik/authentik/blob/master/LICENSE", | ||||||
|     }, |     }, | ||||||
|     "ENUM_NAME_OVERRIDES": { |     "ENUM_NAME_OVERRIDES": { | ||||||
|  |         "EventActions": "authentik.events.models.EventAction", | ||||||
|         "ChallengeChoices": "authentik.flows.challenge.ChallengeTypes", |         "ChallengeChoices": "authentik.flows.challenge.ChallengeTypes", | ||||||
|         "FlowDesignationEnum": "authentik.flows.models.FlowDesignation", |         "FlowDesignationEnum": "authentik.flows.models.FlowDesignation", | ||||||
|         "PolicyEngineMode": "authentik.policies.models.PolicyEngineMode", |         "PolicyEngineMode": "authentik.policies.models.PolicyEngineMode", | ||||||
|  | |||||||
| @ -60,14 +60,21 @@ class LDAPPasswordChanger: | |||||||
|     def check_ad_password_complexity_enabled(self) -> bool: |     def check_ad_password_complexity_enabled(self) -> bool: | ||||||
|         """Check if DOMAIN_PASSWORD_COMPLEX is enabled""" |         """Check if DOMAIN_PASSWORD_COMPLEX is enabled""" | ||||||
|         root_dn = self.get_domain_root_dn() |         root_dn = self.get_domain_root_dn() | ||||||
|         root_attrs = self._source.connection.extend.standard.paged_search( |         try: | ||||||
|             search_base=root_dn, |             root_attrs = self._source.connection.extend.standard.paged_search( | ||||||
|             search_filter="(objectClass=*)", |                 search_base=root_dn, | ||||||
|             search_scope=ldap3.BASE, |                 search_filter="(objectClass=*)", | ||||||
|             attributes=["pwdProperties"], |                 search_scope=ldap3.BASE, | ||||||
|         ) |                 attributes=["pwdProperties"], | ||||||
|  |             ) | ||||||
|  |         except ldap3.core.exceptions.LDAPAttributeError: | ||||||
|  |             return False | ||||||
|         root_attrs = list(root_attrs)[0] |         root_attrs = list(root_attrs)[0] | ||||||
|         pwd_properties = PwdProperties(root_attrs["attributes"]["pwdProperties"]) |         raw_pwd_properties = root_attrs.get("attributes", {}).get("pwdProperties", None) | ||||||
|  |         if raw_pwd_properties is None: | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |         pwd_properties = PwdProperties(raw_pwd_properties) | ||||||
|         if PwdProperties.DOMAIN_PASSWORD_COMPLEX in pwd_properties: |         if PwdProperties.DOMAIN_PASSWORD_COMPLEX in pwd_properties: | ||||||
|             return True |             return True | ||||||
|  |  | ||||||
|  | |||||||
| @ -90,7 +90,7 @@ class InitiateView(View): | |||||||
|         planner.allow_empty_flows = True |         planner.allow_empty_flows = True | ||||||
|         plan = planner.plan(self.request, kwargs) |         plan = planner.plan(self.request, kwargs) | ||||||
|         for stage in stages_to_append: |         for stage in stages_to_append: | ||||||
|             plan.append(stage) |             plan.append_stage(stage) | ||||||
|         self.request.session[SESSION_KEY_PLAN] = plan |         self.request.session[SESSION_KEY_PLAN] = plan | ||||||
|         return redirect_with_qs( |         return redirect_with_qs( | ||||||
|             "authentik_core:if-flow", |             "authentik_core:if-flow", | ||||||
|  | |||||||
| @ -63,7 +63,7 @@ class AuthenticatorDuoStageView(ChallengeStageView): | |||||||
|                 "type": ChallengeTypes.NATIVE.value, |                 "type": ChallengeTypes.NATIVE.value, | ||||||
|                 "activation_barcode": enroll["activation_barcode"], |                 "activation_barcode": enroll["activation_barcode"], | ||||||
|                 "activation_code": enroll["activation_code"], |                 "activation_code": enroll["activation_code"], | ||||||
|                 "stage_uuid": stage.stage_uuid, |                 "stage_uuid": str(stage.stage_uuid), | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | |||||||
| @ -148,7 +148,7 @@ class AuthenticatorValidateStageView(ChallengeStageView): | |||||||
|                 stage = Stage.objects.get_subclass(pk=stage.configuration_stage.pk) |                 stage = Stage.objects.get_subclass(pk=stage.configuration_stage.pk) | ||||||
|                 # plan.insert inserts at 1 index, so when stage_ok pops 0, |                 # plan.insert inserts at 1 index, so when stage_ok pops 0, | ||||||
|                 # the configuration stage is next |                 # the configuration stage is next | ||||||
|                 self.executor.plan.insert(stage) |                 self.executor.plan.insert_stage(stage) | ||||||
|                 return self.executor.stage_ok() |                 return self.executor.stage_ok() | ||||||
|         return super().get(request, *args, **kwargs) |         return super().get(request, *args, **kwargs) | ||||||
|  |  | ||||||
|  | |||||||
| @ -36,12 +36,14 @@ class TestCaptchaStage(TestCase): | |||||||
|             public_key=RECAPTCHA_PUBLIC_KEY, |             public_key=RECAPTCHA_PUBLIC_KEY, | ||||||
|             private_key=RECAPTCHA_PRIVATE_KEY, |             private_key=RECAPTCHA_PRIVATE_KEY, | ||||||
|         ) |         ) | ||||||
|         FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) |         self.binding = FlowStageBinding.objects.create( | ||||||
|  |             target=self.flow, stage=self.stage, order=2 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def test_valid(self): |     def test_valid(self): | ||||||
|         """Test valid captcha""" |         """Test valid captcha""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         session[SESSION_KEY_PLAN] = plan |         session[SESSION_KEY_PLAN] = plan | ||||||
|  | |||||||
| @ -39,9 +39,11 @@ class TestConsentStage(TestCase): | |||||||
|         stage = ConsentStage.objects.create( |         stage = ConsentStage.objects.create( | ||||||
|             name="consent", mode=ConsentMode.ALWAYS_REQUIRE |             name="consent", mode=ConsentMode.ALWAYS_REQUIRE | ||||||
|         ) |         ) | ||||||
|         FlowStageBinding.objects.create(target=flow, stage=stage, order=2) |         binding = FlowStageBinding.objects.create(target=flow, stage=stage, order=2) | ||||||
|  |  | ||||||
|         plan = FlowPlan(flow_pk=flow.pk.hex, stages=[stage], markers=[StageMarker()]) |         plan = FlowPlan( | ||||||
|  |             flow_pk=flow.pk.hex, bindings=[binding], markers=[StageMarker()] | ||||||
|  |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         session[SESSION_KEY_PLAN] = plan |         session[SESSION_KEY_PLAN] = plan | ||||||
|         session.save() |         session.save() | ||||||
| @ -69,11 +71,11 @@ class TestConsentStage(TestCase): | |||||||
|             designation=FlowDesignation.AUTHENTICATION, |             designation=FlowDesignation.AUTHENTICATION, | ||||||
|         ) |         ) | ||||||
|         stage = ConsentStage.objects.create(name="consent", mode=ConsentMode.PERMANENT) |         stage = ConsentStage.objects.create(name="consent", mode=ConsentMode.PERMANENT) | ||||||
|         FlowStageBinding.objects.create(target=flow, stage=stage, order=2) |         binding = FlowStageBinding.objects.create(target=flow, stage=stage, order=2) | ||||||
|  |  | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=flow.pk.hex, |             flow_pk=flow.pk.hex, | ||||||
|             stages=[stage], |             bindings=[binding], | ||||||
|             markers=[StageMarker()], |             markers=[StageMarker()], | ||||||
|             context={PLAN_CONTEXT_APPLICATION: self.application}, |             context={PLAN_CONTEXT_APPLICATION: self.application}, | ||||||
|         ) |         ) | ||||||
| @ -110,11 +112,11 @@ class TestConsentStage(TestCase): | |||||||
|         stage = ConsentStage.objects.create( |         stage = ConsentStage.objects.create( | ||||||
|             name="consent", mode=ConsentMode.EXPIRING, consent_expire_in="seconds=1" |             name="consent", mode=ConsentMode.EXPIRING, consent_expire_in="seconds=1" | ||||||
|         ) |         ) | ||||||
|         FlowStageBinding.objects.create(target=flow, stage=stage, order=2) |         binding = FlowStageBinding.objects.create(target=flow, stage=stage, order=2) | ||||||
|  |  | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=flow.pk.hex, |             flow_pk=flow.pk.hex, | ||||||
|             stages=[stage], |             bindings=[binding], | ||||||
|             markers=[StageMarker()], |             markers=[StageMarker()], | ||||||
|             context={PLAN_CONTEXT_APPLICATION: self.application}, |             context={PLAN_CONTEXT_APPLICATION: self.application}, | ||||||
|         ) |         ) | ||||||
|  | |||||||
| @ -26,12 +26,14 @@ class TestUserDenyStage(TestCase): | |||||||
|             designation=FlowDesignation.AUTHENTICATION, |             designation=FlowDesignation.AUTHENTICATION, | ||||||
|         ) |         ) | ||||||
|         self.stage = DenyStage.objects.create(name="logout") |         self.stage = DenyStage.objects.create(name="logout") | ||||||
|         FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) |         self.binding = FlowStageBinding.objects.create( | ||||||
|  |             target=self.flow, stage=self.stage, order=2 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def test_valid_password(self): |     def test_valid_password(self): | ||||||
|         """Test with a valid pending user and backend""" |         """Test with a valid pending user and backend""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         session[SESSION_KEY_PLAN] = plan |         session[SESSION_KEY_PLAN] = plan | ||||||
|  | |||||||
| @ -34,12 +34,14 @@ class TestEmailStageSending(TestCase): | |||||||
|         self.stage = EmailStage.objects.create( |         self.stage = EmailStage.objects.create( | ||||||
|             name="email", |             name="email", | ||||||
|         ) |         ) | ||||||
|         FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) |         self.binding = FlowStageBinding.objects.create( | ||||||
|  |             target=self.flow, stage=self.stage, order=2 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def test_pending_user(self): |     def test_pending_user(self): | ||||||
|         """Test with pending user""" |         """Test with pending user""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
| @ -67,7 +69,7 @@ class TestEmailStageSending(TestCase): | |||||||
|     def test_send_error(self): |     def test_send_error(self): | ||||||
|         """Test error during sending (sending will be retried)""" |         """Test error during sending (sending will be retried)""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|  | |||||||
| @ -35,12 +35,14 @@ class TestEmailStage(TestCase): | |||||||
|         self.stage = EmailStage.objects.create( |         self.stage = EmailStage.objects.create( | ||||||
|             name="email", |             name="email", | ||||||
|         ) |         ) | ||||||
|         FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) |         self.binding = FlowStageBinding.objects.create( | ||||||
|  |             target=self.flow, stage=self.stage, order=2 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def test_rendering(self): |     def test_rendering(self): | ||||||
|         """Test with pending user""" |         """Test with pending user""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
| @ -56,7 +58,7 @@ class TestEmailStage(TestCase): | |||||||
|     def test_without_user(self): |     def test_without_user(self): | ||||||
|         """Test without pending user""" |         """Test without pending user""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         session[SESSION_KEY_PLAN] = plan |         session[SESSION_KEY_PLAN] = plan | ||||||
| @ -71,7 +73,7 @@ class TestEmailStage(TestCase): | |||||||
|     def test_pending_user(self): |     def test_pending_user(self): | ||||||
|         """Test with pending user""" |         """Test with pending user""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
| @ -102,7 +104,7 @@ class TestEmailStage(TestCase): | |||||||
|         # Make sure token exists |         # Make sure token exists | ||||||
|         self.test_pending_user() |         self.test_pending_user() | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         session[SESSION_KEY_PLAN] = plan |         session[SESSION_KEY_PLAN] = plan | ||||||
|  | |||||||
| @ -85,6 +85,18 @@ class IdentificationChallengeResponse(ChallengeResponse): | |||||||
|             identification_failed.send( |             identification_failed.send( | ||||||
|                 sender=self, request=self.stage.request, uid_field=uid_field |                 sender=self, request=self.stage.request, uid_field=uid_field | ||||||
|             ) |             ) | ||||||
|  |             # We set the pending_user even on failure so it's part of the context, even | ||||||
|  |             # when the input is invalid | ||||||
|  |             # This is so its part of the current flow plan, and on flow restart can be kept, and | ||||||
|  |             # policies can be applied. | ||||||
|  |             self.stage.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User( | ||||||
|  |                 username=uid_field, | ||||||
|  |                 email=uid_field, | ||||||
|  |             ) | ||||||
|  |             if not current_stage.show_matched_user: | ||||||
|  |                 self.stage.executor.plan.context[ | ||||||
|  |                     PLAN_CONTEXT_PENDING_USER_IDENTIFIER | ||||||
|  |                 ] = uid_field | ||||||
|             raise ValidationError("Failed to authenticate.") |             raise ValidationError("Failed to authenticate.") | ||||||
|         self.pre_user = pre_user |         self.pre_user = pre_user | ||||||
|         if not current_stage.password_stage: |         if not current_stage.password_stage: | ||||||
|  | |||||||
| @ -35,7 +35,9 @@ class TestUserLoginStage(TestCase): | |||||||
|             designation=FlowDesignation.AUTHENTICATION, |             designation=FlowDesignation.AUTHENTICATION, | ||||||
|         ) |         ) | ||||||
|         self.stage = InvitationStage.objects.create(name="invitation") |         self.stage = InvitationStage.objects.create(name="invitation") | ||||||
|         FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) |         self.binding = FlowStageBinding.objects.create( | ||||||
|  |             target=self.flow, stage=self.stage, order=2 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     @patch( |     @patch( | ||||||
|         "authentik.flows.views.to_stage_response", |         "authentik.flows.views.to_stage_response", | ||||||
| @ -44,7 +46,7 @@ class TestUserLoginStage(TestCase): | |||||||
|     def test_without_invitation_fail(self): |     def test_without_invitation_fail(self): | ||||||
|         """Test without any invitation, continue_flow_without_invitation not set.""" |         """Test without any invitation, continue_flow_without_invitation not set.""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|         plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO |         plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO | ||||||
| @ -75,7 +77,7 @@ class TestUserLoginStage(TestCase): | |||||||
|         self.stage.continue_flow_without_invitation = True |         self.stage.continue_flow_without_invitation = True | ||||||
|         self.stage.save() |         self.stage.save() | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|         plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO |         plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO | ||||||
| @ -103,7 +105,7 @@ class TestUserLoginStage(TestCase): | |||||||
|     def test_with_invitation_get(self): |     def test_with_invitation_get(self): | ||||||
|         """Test with invitation, check data in session""" |         """Test with invitation, check data in session""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         session[SESSION_KEY_PLAN] = plan |         session[SESSION_KEY_PLAN] = plan | ||||||
| @ -143,7 +145,7 @@ class TestUserLoginStage(TestCase): | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PROMPT] = {INVITATION_TOKEN_KEY: invite.pk.hex} |         plan.context[PLAN_CONTEXT_PROMPT] = {INVITATION_TOKEN_KEY: invite.pk.hex} | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|  | |||||||
| @ -39,7 +39,9 @@ class TestPasswordStage(TestCase): | |||||||
|         self.stage = PasswordStage.objects.create( |         self.stage = PasswordStage.objects.create( | ||||||
|             name="password", backends=[BACKEND_DJANGO] |             name="password", backends=[BACKEND_DJANGO] | ||||||
|         ) |         ) | ||||||
|         FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) |         self.binding = FlowStageBinding.objects.create( | ||||||
|  |             target=self.flow, stage=self.stage, order=2 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     @patch( |     @patch( | ||||||
|         "authentik.flows.views.to_stage_response", |         "authentik.flows.views.to_stage_response", | ||||||
| @ -48,7 +50,7 @@ class TestPasswordStage(TestCase): | |||||||
|     def test_without_user(self): |     def test_without_user(self): | ||||||
|         """Test without user""" |         """Test without user""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         session[SESSION_KEY_PLAN] = plan |         session[SESSION_KEY_PLAN] = plan | ||||||
| @ -84,7 +86,7 @@ class TestPasswordStage(TestCase): | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         session[SESSION_KEY_PLAN] = plan |         session[SESSION_KEY_PLAN] = plan | ||||||
| @ -101,7 +103,7 @@ class TestPasswordStage(TestCase): | |||||||
|     def test_valid_password(self): |     def test_valid_password(self): | ||||||
|         """Test with a valid pending user and valid password""" |         """Test with a valid pending user and valid password""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
| @ -129,7 +131,7 @@ class TestPasswordStage(TestCase): | |||||||
|     def test_invalid_password(self): |     def test_invalid_password(self): | ||||||
|         """Test with a valid pending user and invalid password""" |         """Test with a valid pending user and invalid password""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
| @ -148,7 +150,7 @@ class TestPasswordStage(TestCase): | |||||||
|     def test_invalid_password_lockout(self): |     def test_invalid_password_lockout(self): | ||||||
|         """Test with a valid pending user and invalid password (trigger logout counter)""" |         """Test with a valid pending user and invalid password (trigger logout counter)""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
| @ -189,7 +191,7 @@ class TestPasswordStage(TestCase): | |||||||
|         """Test with a valid pending user and valid password. |         """Test with a valid pending user and valid password. | ||||||
|         Backend is patched to return PermissionError""" |         Backend is patched to return PermissionError""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|  | |||||||
| @ -90,6 +90,14 @@ class PromptChallengeResponse(ChallengeResponse): | |||||||
|             raise ValidationError(_("Passwords don't match.")) |             raise ValidationError(_("Passwords don't match.")) | ||||||
|  |  | ||||||
|     def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: |     def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: | ||||||
|  |         # Check if we have any static or hidden fields, and ensure they | ||||||
|  |         # still have the same value | ||||||
|  |         static_hidden_fields: QuerySet[Prompt] = self.stage.fields.filter( | ||||||
|  |             type__in=[FieldTypes.HIDDEN, FieldTypes.STATIC] | ||||||
|  |         ) | ||||||
|  |         for static_hidden in static_hidden_fields: | ||||||
|  |             attrs[static_hidden.field_key] = static_hidden.placeholder | ||||||
|  |  | ||||||
|         # Check if we have two password fields, and make sure they are the same |         # Check if we have two password fields, and make sure they are the same | ||||||
|         password_fields: QuerySet[Prompt] = self.stage.fields.filter( |         password_fields: QuerySet[Prompt] = self.stage.fields.filter( | ||||||
|             type=FieldTypes.PASSWORD |             type=FieldTypes.PASSWORD | ||||||
|  | |||||||
| @ -78,6 +78,12 @@ class TestPromptStage(TestCase): | |||||||
|             required=True, |             required=True, | ||||||
|             placeholder="HIDDEN_PLACEHOLDER", |             placeholder="HIDDEN_PLACEHOLDER", | ||||||
|         ) |         ) | ||||||
|  |         static_prompt = Prompt.objects.create( | ||||||
|  |             field_key="static_prompt", | ||||||
|  |             type=FieldTypes.STATIC, | ||||||
|  |             required=True, | ||||||
|  |             placeholder="static", | ||||||
|  |         ) | ||||||
|         self.stage = PromptStage.objects.create(name="prompt-stage") |         self.stage = PromptStage.objects.create(name="prompt-stage") | ||||||
|         self.stage.fields.set( |         self.stage.fields.set( | ||||||
|             [ |             [ | ||||||
| @ -88,6 +94,7 @@ class TestPromptStage(TestCase): | |||||||
|                 password2_prompt, |                 password2_prompt, | ||||||
|                 number_prompt, |                 number_prompt, | ||||||
|                 hidden_prompt, |                 hidden_prompt, | ||||||
|  |                 static_prompt, | ||||||
|             ] |             ] | ||||||
|         ) |         ) | ||||||
|         self.stage.save() |         self.stage.save() | ||||||
| @ -100,14 +107,17 @@ class TestPromptStage(TestCase): | |||||||
|             password2_prompt.field_key: "test", |             password2_prompt.field_key: "test", | ||||||
|             number_prompt.field_key: 3, |             number_prompt.field_key: 3, | ||||||
|             hidden_prompt.field_key: hidden_prompt.placeholder, |             hidden_prompt.field_key: hidden_prompt.placeholder, | ||||||
|  |             static_prompt.field_key: static_prompt.placeholder, | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) |         self.binding = FlowStageBinding.objects.create( | ||||||
|  |             target=self.flow, stage=self.stage, order=2 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def test_render(self): |     def test_render(self): | ||||||
|         """Test render of form, check if all prompts are rendered correctly""" |         """Test render of form, check if all prompts are rendered correctly""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         session[SESSION_KEY_PLAN] = plan |         session[SESSION_KEY_PLAN] = plan | ||||||
| @ -125,7 +135,7 @@ class TestPromptStage(TestCase): | |||||||
|     def test_valid_challenge_with_policy(self) -> PromptChallengeResponse: |     def test_valid_challenge_with_policy(self) -> PromptChallengeResponse: | ||||||
|         """Test challenge_response validation""" |         """Test challenge_response validation""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         expr = "return request.context['password_prompt'] == request.context['password2_prompt']" |         expr = "return request.context['password_prompt'] == request.context['password2_prompt']" | ||||||
|         expr_policy = ExpressionPolicy.objects.create( |         expr_policy = ExpressionPolicy.objects.create( | ||||||
| @ -142,7 +152,7 @@ class TestPromptStage(TestCase): | |||||||
|     def test_invalid_challenge(self) -> PromptChallengeResponse: |     def test_invalid_challenge(self) -> PromptChallengeResponse: | ||||||
|         """Test challenge_response validation""" |         """Test challenge_response validation""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         expr = "False" |         expr = "False" | ||||||
|         expr_policy = ExpressionPolicy.objects.create( |         expr_policy = ExpressionPolicy.objects.create( | ||||||
| @ -159,7 +169,7 @@ class TestPromptStage(TestCase): | |||||||
|     def test_valid_challenge_request(self): |     def test_valid_challenge_request(self): | ||||||
|         """Test a request with valid challenge_response data""" |         """Test a request with valid challenge_response data""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         session[SESSION_KEY_PLAN] = plan |         session[SESSION_KEY_PLAN] = plan | ||||||
| @ -196,7 +206,7 @@ class TestPromptStage(TestCase): | |||||||
|     def test_invalid_password(self): |     def test_invalid_password(self): | ||||||
|         """Test challenge_response validation""" |         """Test challenge_response validation""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         self.prompt_data["password2_prompt"] = "qwerqwerqr" |         self.prompt_data["password2_prompt"] = "qwerqwerqr" | ||||||
|         challenge_response = PromptChallengeResponse( |         challenge_response = PromptChallengeResponse( | ||||||
| @ -215,7 +225,7 @@ class TestPromptStage(TestCase): | |||||||
|     def test_invalid_username(self): |     def test_invalid_username(self): | ||||||
|         """Test challenge_response validation""" |         """Test challenge_response validation""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         self.prompt_data["username_prompt"] = "akadmin" |         self.prompt_data["username_prompt"] = "akadmin" | ||||||
|         challenge_response = PromptChallengeResponse( |         challenge_response = PromptChallengeResponse( | ||||||
| @ -230,3 +240,17 @@ class TestPromptStage(TestCase): | |||||||
|                 ] |                 ] | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |     def test_static_hidden_overwrite(self): | ||||||
|  |         """Test that static and hidden fields ignore any value sent to them""" | ||||||
|  |         plan = FlowPlan( | ||||||
|  |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|  |         ) | ||||||
|  |         self.prompt_data["hidden_prompt"] = "foo" | ||||||
|  |         self.prompt_data["static_prompt"] = "foo" | ||||||
|  |         challenge_response = PromptChallengeResponse( | ||||||
|  |             None, stage=self.stage, plan=plan, data=self.prompt_data | ||||||
|  |         ) | ||||||
|  |         self.assertEqual(challenge_response.is_valid(), True) | ||||||
|  |         self.assertNotEqual(challenge_response.validated_data["hidden_prompt"], "foo") | ||||||
|  |         self.assertNotEqual(challenge_response.validated_data["static_prompt"], "foo") | ||||||
|  | |||||||
| @ -30,7 +30,9 @@ class TestUserDeleteStage(TestCase): | |||||||
|             designation=FlowDesignation.AUTHENTICATION, |             designation=FlowDesignation.AUTHENTICATION, | ||||||
|         ) |         ) | ||||||
|         self.stage = UserDeleteStage.objects.create(name="delete") |         self.stage = UserDeleteStage.objects.create(name="delete") | ||||||
|         FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) |         self.binding = FlowStageBinding.objects.create( | ||||||
|  |             target=self.flow, stage=self.stage, order=2 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     @patch( |     @patch( | ||||||
|         "authentik.flows.views.to_stage_response", |         "authentik.flows.views.to_stage_response", | ||||||
| @ -39,7 +41,7 @@ class TestUserDeleteStage(TestCase): | |||||||
|     def test_no_user(self): |     def test_no_user(self): | ||||||
|         """Test without user set""" |         """Test without user set""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         session[SESSION_KEY_PLAN] = plan |         session[SESSION_KEY_PLAN] = plan | ||||||
| @ -66,7 +68,7 @@ class TestUserDeleteStage(TestCase): | |||||||
|     def test_user_delete_get(self): |     def test_user_delete_get(self): | ||||||
|         """Test Form render""" |         """Test Form render""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|  | |||||||
| @ -30,12 +30,14 @@ class TestUserLoginStage(TestCase): | |||||||
|             designation=FlowDesignation.AUTHENTICATION, |             designation=FlowDesignation.AUTHENTICATION, | ||||||
|         ) |         ) | ||||||
|         self.stage = UserLoginStage.objects.create(name="login") |         self.stage = UserLoginStage.objects.create(name="login") | ||||||
|         FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) |         self.binding = FlowStageBinding.objects.create( | ||||||
|  |             target=self.flow, stage=self.stage, order=2 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def test_valid_password(self): |     def test_valid_password(self): | ||||||
|         """Test with a valid pending user and backend""" |         """Test with a valid pending user and backend""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
| @ -61,7 +63,7 @@ class TestUserLoginStage(TestCase): | |||||||
|         self.stage.session_duration = "seconds=2" |         self.stage.session_duration = "seconds=2" | ||||||
|         self.stage.save() |         self.stage.save() | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
| @ -92,7 +94,7 @@ class TestUserLoginStage(TestCase): | |||||||
|     def test_without_user(self): |     def test_without_user(self): | ||||||
|         """Test a plan without any pending user, resulting in a denied""" |         """Test a plan without any pending user, resulting in a denied""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         session[SESSION_KEY_PLAN] = plan |         session[SESSION_KEY_PLAN] = plan | ||||||
|  | |||||||
| @ -28,12 +28,14 @@ class TestUserLogoutStage(TestCase): | |||||||
|             designation=FlowDesignation.AUTHENTICATION, |             designation=FlowDesignation.AUTHENTICATION, | ||||||
|         ) |         ) | ||||||
|         self.stage = UserLogoutStage.objects.create(name="logout") |         self.stage = UserLogoutStage.objects.create(name="logout") | ||||||
|         FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) |         self.binding = FlowStageBinding.objects.create( | ||||||
|  |             target=self.flow, stage=self.stage, order=2 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def test_valid_password(self): |     def test_valid_password(self): | ||||||
|         """Test with a valid pending user and backend""" |         """Test with a valid pending user and backend""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user |         plan.context[PLAN_CONTEXT_PENDING_USER] = self.user | ||||||
|         plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO |         plan.context[PLAN_CONTEXT_AUTHENTICATION_BACKEND] = BACKEND_DJANGO | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ class UserWriteStageSerializer(StageSerializer): | |||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|         model = UserWriteStage |         model = UserWriteStage | ||||||
|         fields = StageSerializer.Meta.fields |         fields = StageSerializer.Meta.fields + ["create_users_as_inactive"] | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserWriteStageViewSet(UsedByMixin, ModelViewSet): | class UserWriteStageViewSet(UsedByMixin, ModelViewSet): | ||||||
|  | |||||||
| @ -0,0 +1,21 @@ | |||||||
|  | # Generated by Django 3.2.4 on 2021-06-28 20:31 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("authentik_stages_user_write", "0002_auto_20200918_1653"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="userwritestage", | ||||||
|  |             name="create_users_as_inactive", | ||||||
|  |             field=models.BooleanField( | ||||||
|  |                 default=False, | ||||||
|  |                 help_text="When set, newly created users are inactive and cannot login.", | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @ -1,6 +1,7 @@ | |||||||
| """write stage models""" | """write stage models""" | ||||||
| from typing import Type | from typing import Type | ||||||
|  |  | ||||||
|  | from django.db import models | ||||||
| from django.utils.translation import gettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
| from django.views import View | from django.views import View | ||||||
| from rest_framework.serializers import BaseSerializer | from rest_framework.serializers import BaseSerializer | ||||||
| @ -12,6 +13,11 @@ class UserWriteStage(Stage): | |||||||
|     """Writes currently pending data into the pending user, or if no user exists, |     """Writes currently pending data into the pending user, or if no user exists, | ||||||
|     creates a new user with the data.""" |     creates a new user with the data.""" | ||||||
|  |  | ||||||
|  |     create_users_as_inactive = models.BooleanField( | ||||||
|  |         default=False, | ||||||
|  |         help_text=_("When set, newly created users are inactive and cannot login."), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def serializer(self) -> BaseSerializer: |     def serializer(self) -> BaseSerializer: | ||||||
|         from authentik.stages.user_write.api import UserWriteStageSerializer |         from authentik.stages.user_write.api import UserWriteStageSerializer | ||||||
|  | |||||||
| @ -35,7 +35,9 @@ class UserWriteStageView(StageView): | |||||||
|         data = self.executor.plan.context[PLAN_CONTEXT_PROMPT] |         data = self.executor.plan.context[PLAN_CONTEXT_PROMPT] | ||||||
|         user_created = False |         user_created = False | ||||||
|         if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context: |         if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context: | ||||||
|             self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User() |             self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User( | ||||||
|  |                 is_active=not self.executor.current_stage.create_users_as_inactive | ||||||
|  |             ) | ||||||
|             self.executor.plan.context[ |             self.executor.plan.context[ | ||||||
|                 PLAN_CONTEXT_AUTHENTICATION_BACKEND |                 PLAN_CONTEXT_AUTHENTICATION_BACKEND | ||||||
|             ] = class_to_path(ModelBackend) |             ] = class_to_path(ModelBackend) | ||||||
|  | |||||||
| @ -37,7 +37,9 @@ class TestUserWriteStage(TestCase): | |||||||
|             designation=FlowDesignation.AUTHENTICATION, |             designation=FlowDesignation.AUTHENTICATION, | ||||||
|         ) |         ) | ||||||
|         self.stage = UserWriteStage.objects.create(name="write") |         self.stage = UserWriteStage.objects.create(name="write") | ||||||
|         FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) |         self.binding = FlowStageBinding.objects.create( | ||||||
|  |             target=self.flow, stage=self.stage, order=2 | ||||||
|  |         ) | ||||||
|         self.source = Source.objects.create(name="fake_source") |         self.source = Source.objects.create(name="fake_source") | ||||||
|  |  | ||||||
|     def test_user_create(self): |     def test_user_create(self): | ||||||
| @ -48,7 +50,7 @@ class TestUserWriteStage(TestCase): | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PROMPT] = { |         plan.context[PLAN_CONTEXT_PROMPT] = { | ||||||
|             "username": "test-user", |             "username": "test-user", | ||||||
| @ -92,7 +94,7 @@ class TestUserWriteStage(TestCase): | |||||||
|             for _ in range(8) |             for _ in range(8) | ||||||
|         ) |         ) | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         plan.context[PLAN_CONTEXT_PENDING_USER] = User.objects.create( |         plan.context[PLAN_CONTEXT_PENDING_USER] = User.objects.create( | ||||||
|             username="unittest", email="test@beryju.org" |             username="unittest", email="test@beryju.org" | ||||||
| @ -135,7 +137,7 @@ class TestUserWriteStage(TestCase): | |||||||
|     def test_without_data(self): |     def test_without_data(self): | ||||||
|         """Test without data results in error""" |         """Test without data results in error""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         session[SESSION_KEY_PLAN] = plan |         session[SESSION_KEY_PLAN] = plan | ||||||
| @ -167,7 +169,7 @@ class TestUserWriteStage(TestCase): | |||||||
|     def test_blank_username(self): |     def test_blank_username(self): | ||||||
|         """Test with blank username results in error""" |         """Test with blank username results in error""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         plan.context[PLAN_CONTEXT_PROMPT] = { |         plan.context[PLAN_CONTEXT_PROMPT] = { | ||||||
| @ -204,7 +206,7 @@ class TestUserWriteStage(TestCase): | |||||||
|     def test_duplicate_data(self): |     def test_duplicate_data(self): | ||||||
|         """Test with duplicate data, should trigger error""" |         """Test with duplicate data, should trigger error""" | ||||||
|         plan = FlowPlan( |         plan = FlowPlan( | ||||||
|             flow_pk=self.flow.pk.hex, stages=[self.stage], markers=[StageMarker()] |             flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()] | ||||||
|         ) |         ) | ||||||
|         session = self.client.session |         session = self.client.session | ||||||
|         plan.context[PLAN_CONTEXT_PROMPT] = { |         plan.context[PLAN_CONTEXT_PROMPT] = { | ||||||
|  | |||||||
| @ -54,6 +54,9 @@ class CurrentTenantSerializer(PassiveSerializer): | |||||||
|         default=CONFIG.y("footer_links", []), |         default=CONFIG.y("footer_links", []), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     flow_authentication = CharField(source="flow_authentication.slug", required=False) | ||||||
|  |     flow_invalidation = CharField(source="flow_invalidation.slug", required=False) | ||||||
|  |     flow_recovery = CharField(source="flow_recovery.slug", required=False) | ||||||
|     flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False) |     flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -20,6 +20,8 @@ class TestTenants(TestCase): | |||||||
|                 "branding_title": "authentik", |                 "branding_title": "authentik", | ||||||
|                 "matched_domain": "authentik-default", |                 "matched_domain": "authentik-default", | ||||||
|                 "ui_footer_links": CONFIG.y("footer_links"), |                 "ui_footer_links": CONFIG.y("footer_links"), | ||||||
|  |                 "flow_authentication": "default-authentication-flow", | ||||||
|  |                 "flow_invalidation": "default-invalidation-flow", | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | |||||||
| @ -21,7 +21,7 @@ services: | |||||||
|     networks: |     networks: | ||||||
|       - internal |       - internal | ||||||
|   server: |   server: | ||||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.2} |     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.3} | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     command: server |     command: server | ||||||
|     environment: |     environment: | ||||||
| @ -44,7 +44,7 @@ services: | |||||||
|       - "0.0.0.0:9000:9000" |       - "0.0.0.0:9000:9000" | ||||||
|       - "0.0.0.0:9443:9443" |       - "0.0.0.0:9443:9443" | ||||||
|   worker: |   worker: | ||||||
|     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.2} |     image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.6.3} | ||||||
|     restart: unless-stopped |     restart: unless-stopped | ||||||
|     command: worker |     command: worker | ||||||
|     networks: |     networks: | ||||||
|  | |||||||
| @ -1,3 +1,3 @@ | |||||||
| package constants | package constants | ||||||
|  |  | ||||||
| const VERSION = "2021.6.2" | const VERSION = "2021.6.3" | ||||||
|  | |||||||
| @ -99,15 +99,15 @@ func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if *u.IsActive { | 	if *u.IsActive { | ||||||
| 		attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}}) |         attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}}) | ||||||
| 	} else { | 	} else { | ||||||
| 		attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}}) |         attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if u.IsSuperuser { | 	if u.IsSuperuser { | ||||||
| 		attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"inactive"}}) |  | ||||||
| 	} else { |  | ||||||
| 		attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"active"}}) | 		attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"active"}}) | ||||||
|  | 	} else { | ||||||
|  |         attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"inactive"}}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	attrs = append(attrs, &ldap.EntryAttribute{Name: "memberOf", Values: pi.GroupsForUser(u)}) | 	attrs = append(attrs, &ldap.EntryAttribute{Name: "memberOf", Values: pi.GroupsForUser(u)}) | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const VERSION = "2021.6.2" | const VERSION = "2021.6.3" | ||||||
|  |  | ||||||
| func BUILD() string { | func BUILD() string { | ||||||
| 	build := os.Getenv("GIT_BUILD_HASH") | 	build := os.Getenv("GIT_BUILD_HASH") | ||||||
|  | |||||||
							
								
								
									
										276
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										276
									
								
								schema.yml
									
									
									
									
									
								
							| @ -1,7 +1,7 @@ | |||||||
| openapi: 3.0.3 | openapi: 3.0.3 | ||||||
| info: | info: | ||||||
|   title: authentik |   title: authentik | ||||||
|   version: 2021.6.1 |   version: 2021.6.2 | ||||||
|   description: Making authentication simple. |   description: Making authentication simple. | ||||||
|   contact: |   contact: | ||||||
|     email: hello@beryju.org |     email: hello@beryju.org | ||||||
| @ -3572,6 +3572,37 @@ paths: | |||||||
|           $ref: '#/components/schemas/ValidationError' |           $ref: '#/components/schemas/ValidationError' | ||||||
|         '403': |         '403': | ||||||
|           $ref: '#/components/schemas/GenericError' |           $ref: '#/components/schemas/GenericError' | ||||||
|  |     post: | ||||||
|  |       operationId: events_events_create | ||||||
|  |       description: Event Read-Only Viewset | ||||||
|  |       tags: | ||||||
|  |       - events | ||||||
|  |       requestBody: | ||||||
|  |         content: | ||||||
|  |           application/json: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/EventRequest' | ||||||
|  |           application/x-www-form-urlencoded: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/EventRequest' | ||||||
|  |           multipart/form-data: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/EventRequest' | ||||||
|  |         required: true | ||||||
|  |       security: | ||||||
|  |       - authentik: [] | ||||||
|  |       - cookieAuth: [] | ||||||
|  |       responses: | ||||||
|  |         '201': | ||||||
|  |           content: | ||||||
|  |             application/json: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/Event' | ||||||
|  |           description: '' | ||||||
|  |         '400': | ||||||
|  |           $ref: '#/components/schemas/ValidationError' | ||||||
|  |         '403': | ||||||
|  |           $ref: '#/components/schemas/GenericError' | ||||||
|   /api/v2beta/events/events/{event_uuid}/: |   /api/v2beta/events/events/{event_uuid}/: | ||||||
|     get: |     get: | ||||||
|       operationId: events_events_retrieve |       operationId: events_events_retrieve | ||||||
| @ -3600,6 +3631,106 @@ paths: | |||||||
|           $ref: '#/components/schemas/ValidationError' |           $ref: '#/components/schemas/ValidationError' | ||||||
|         '403': |         '403': | ||||||
|           $ref: '#/components/schemas/GenericError' |           $ref: '#/components/schemas/GenericError' | ||||||
|  |     put: | ||||||
|  |       operationId: events_events_update | ||||||
|  |       description: Event Read-Only Viewset | ||||||
|  |       parameters: | ||||||
|  |       - in: path | ||||||
|  |         name: event_uuid | ||||||
|  |         schema: | ||||||
|  |           type: string | ||||||
|  |           format: uuid | ||||||
|  |         description: A UUID string identifying this Event. | ||||||
|  |         required: true | ||||||
|  |       tags: | ||||||
|  |       - events | ||||||
|  |       requestBody: | ||||||
|  |         content: | ||||||
|  |           application/json: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/EventRequest' | ||||||
|  |           application/x-www-form-urlencoded: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/EventRequest' | ||||||
|  |           multipart/form-data: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/EventRequest' | ||||||
|  |         required: true | ||||||
|  |       security: | ||||||
|  |       - authentik: [] | ||||||
|  |       - cookieAuth: [] | ||||||
|  |       responses: | ||||||
|  |         '200': | ||||||
|  |           content: | ||||||
|  |             application/json: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/Event' | ||||||
|  |           description: '' | ||||||
|  |         '400': | ||||||
|  |           $ref: '#/components/schemas/ValidationError' | ||||||
|  |         '403': | ||||||
|  |           $ref: '#/components/schemas/GenericError' | ||||||
|  |     patch: | ||||||
|  |       operationId: events_events_partial_update | ||||||
|  |       description: Event Read-Only Viewset | ||||||
|  |       parameters: | ||||||
|  |       - in: path | ||||||
|  |         name: event_uuid | ||||||
|  |         schema: | ||||||
|  |           type: string | ||||||
|  |           format: uuid | ||||||
|  |         description: A UUID string identifying this Event. | ||||||
|  |         required: true | ||||||
|  |       tags: | ||||||
|  |       - events | ||||||
|  |       requestBody: | ||||||
|  |         content: | ||||||
|  |           application/json: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/PatchedEventRequest' | ||||||
|  |           application/x-www-form-urlencoded: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/PatchedEventRequest' | ||||||
|  |           multipart/form-data: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/PatchedEventRequest' | ||||||
|  |       security: | ||||||
|  |       - authentik: [] | ||||||
|  |       - cookieAuth: [] | ||||||
|  |       responses: | ||||||
|  |         '200': | ||||||
|  |           content: | ||||||
|  |             application/json: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/Event' | ||||||
|  |           description: '' | ||||||
|  |         '400': | ||||||
|  |           $ref: '#/components/schemas/ValidationError' | ||||||
|  |         '403': | ||||||
|  |           $ref: '#/components/schemas/GenericError' | ||||||
|  |     delete: | ||||||
|  |       operationId: events_events_destroy | ||||||
|  |       description: Event Read-Only Viewset | ||||||
|  |       parameters: | ||||||
|  |       - in: path | ||||||
|  |         name: event_uuid | ||||||
|  |         schema: | ||||||
|  |           type: string | ||||||
|  |           format: uuid | ||||||
|  |         description: A UUID string identifying this Event. | ||||||
|  |         required: true | ||||||
|  |       tags: | ||||||
|  |       - events | ||||||
|  |       security: | ||||||
|  |       - authentik: [] | ||||||
|  |       - cookieAuth: [] | ||||||
|  |       responses: | ||||||
|  |         '204': | ||||||
|  |           description: No response body | ||||||
|  |         '400': | ||||||
|  |           $ref: '#/components/schemas/ValidationError' | ||||||
|  |         '403': | ||||||
|  |           $ref: '#/components/schemas/GenericError' | ||||||
|   /api/v2beta/events/events/actions/: |   /api/v2beta/events/events/actions/: | ||||||
|     get: |     get: | ||||||
|       operationId: events_events_actions_list |       operationId: events_events_actions_list | ||||||
| @ -4441,6 +4572,18 @@ paths: | |||||||
|         schema: |         schema: | ||||||
|           type: string |           type: string | ||||||
|           format: uuid |           format: uuid | ||||||
|  |       - in: query | ||||||
|  |         name: invalid_response_action | ||||||
|  |         schema: | ||||||
|  |           type: string | ||||||
|  |           enum: | ||||||
|  |           - restart | ||||||
|  |           - restart_with_context | ||||||
|  |           - retry | ||||||
|  |         description: Configure how the flow executor should handle an invalid response | ||||||
|  |           to a challenge. RETRY returns the error message and a similar challenge | ||||||
|  |           to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT | ||||||
|  |           restarts the flow while keeping the current context. | ||||||
|       - in: query |       - in: query | ||||||
|         name: order |         name: order | ||||||
|         schema: |         schema: | ||||||
| @ -18759,6 +18902,12 @@ components: | |||||||
|             name: Documentation |             name: Documentation | ||||||
|           - href: https://goauthentik.io/ |           - href: https://goauthentik.io/ | ||||||
|             name: authentik Website |             name: authentik Website | ||||||
|  |         flow_authentication: | ||||||
|  |           type: string | ||||||
|  |         flow_invalidation: | ||||||
|  |           type: string | ||||||
|  |         flow_recovery: | ||||||
|  |           type: string | ||||||
|         flow_unenrollment: |         flow_unenrollment: | ||||||
|           type: string |           type: string | ||||||
|       required: |       required: | ||||||
| @ -19242,7 +19391,7 @@ components: | |||||||
|           type: object |           type: object | ||||||
|           additionalProperties: {} |           additionalProperties: {} | ||||||
|         action: |         action: | ||||||
|           type: string |           $ref: '#/components/schemas/EventActions' | ||||||
|         app: |         app: | ||||||
|           type: string |           type: string | ||||||
|         context: |         context: | ||||||
| @ -19266,6 +19415,34 @@ components: | |||||||
|       - app |       - app | ||||||
|       - created |       - created | ||||||
|       - pk |       - pk | ||||||
|  |     EventActions: | ||||||
|  |       enum: | ||||||
|  |       - login | ||||||
|  |       - login_failed | ||||||
|  |       - logout | ||||||
|  |       - user_write | ||||||
|  |       - suspicious_request | ||||||
|  |       - password_set | ||||||
|  |       - secret_view | ||||||
|  |       - invitation_used | ||||||
|  |       - authorize_application | ||||||
|  |       - source_linked | ||||||
|  |       - impersonation_started | ||||||
|  |       - impersonation_ended | ||||||
|  |       - policy_execution | ||||||
|  |       - policy_exception | ||||||
|  |       - property_mapping_exception | ||||||
|  |       - system_task_execution | ||||||
|  |       - system_task_exception | ||||||
|  |       - system_exception | ||||||
|  |       - configuration_error | ||||||
|  |       - model_created | ||||||
|  |       - model_updated | ||||||
|  |       - model_deleted | ||||||
|  |       - email_sent | ||||||
|  |       - update_available | ||||||
|  |       - custom_ | ||||||
|  |       type: string | ||||||
|     EventMatcherPolicy: |     EventMatcherPolicy: | ||||||
|       type: object |       type: object | ||||||
|       description: Event Matcher Policy Serializer |       description: Event Matcher Policy Serializer | ||||||
| @ -19296,7 +19473,7 @@ components: | |||||||
|           readOnly: true |           readOnly: true | ||||||
|         action: |         action: | ||||||
|           allOf: |           allOf: | ||||||
|           - $ref: '#/components/schemas/EventMatcherPolicyActionEnum' |           - $ref: '#/components/schemas/EventActions' | ||||||
|           description: Match created events with this action type. When left empty, |           description: Match created events with this action type. When left empty, | ||||||
|             all action types will be matched. |             all action types will be matched. | ||||||
|         client_ip: |         client_ip: | ||||||
| @ -19314,34 +19491,6 @@ components: | |||||||
|       - pk |       - pk | ||||||
|       - verbose_name |       - verbose_name | ||||||
|       - verbose_name_plural |       - verbose_name_plural | ||||||
|     EventMatcherPolicyActionEnum: |  | ||||||
|       enum: |  | ||||||
|       - login |  | ||||||
|       - login_failed |  | ||||||
|       - logout |  | ||||||
|       - user_write |  | ||||||
|       - suspicious_request |  | ||||||
|       - password_set |  | ||||||
|       - secret_view |  | ||||||
|       - invitation_used |  | ||||||
|       - authorize_application |  | ||||||
|       - source_linked |  | ||||||
|       - impersonation_started |  | ||||||
|       - impersonation_ended |  | ||||||
|       - policy_execution |  | ||||||
|       - policy_exception |  | ||||||
|       - property_mapping_exception |  | ||||||
|       - system_task_execution |  | ||||||
|       - system_task_exception |  | ||||||
|       - system_exception |  | ||||||
|       - configuration_error |  | ||||||
|       - model_created |  | ||||||
|       - model_updated |  | ||||||
|       - model_deleted |  | ||||||
|       - email_sent |  | ||||||
|       - update_available |  | ||||||
|       - custom_ |  | ||||||
|       type: string |  | ||||||
|     EventMatcherPolicyRequest: |     EventMatcherPolicyRequest: | ||||||
|       type: object |       type: object | ||||||
|       description: Event Matcher Policy Serializer |       description: Event Matcher Policy Serializer | ||||||
| @ -19355,7 +19504,7 @@ components: | |||||||
|             will be logged. By default, only execution errors are logged. |             will be logged. By default, only execution errors are logged. | ||||||
|         action: |         action: | ||||||
|           allOf: |           allOf: | ||||||
|           - $ref: '#/components/schemas/EventMatcherPolicyActionEnum' |           - $ref: '#/components/schemas/EventActions' | ||||||
|           description: Match created events with this action type. When left empty, |           description: Match created events with this action type. When left empty, | ||||||
|             all action types will be matched. |             all action types will be matched. | ||||||
|         client_ip: |         client_ip: | ||||||
| @ -19375,7 +19524,7 @@ components: | |||||||
|           type: object |           type: object | ||||||
|           additionalProperties: {} |           additionalProperties: {} | ||||||
|         action: |         action: | ||||||
|           type: string |           $ref: '#/components/schemas/EventActions' | ||||||
|         app: |         app: | ||||||
|           type: string |           type: string | ||||||
|         context: |         context: | ||||||
| @ -19673,6 +19822,13 @@ components: | |||||||
|           minimum: -2147483648 |           minimum: -2147483648 | ||||||
|         policy_engine_mode: |         policy_engine_mode: | ||||||
|           $ref: '#/components/schemas/PolicyEngineMode' |           $ref: '#/components/schemas/PolicyEngineMode' | ||||||
|  |         invalid_response_action: | ||||||
|  |           allOf: | ||||||
|  |           - $ref: '#/components/schemas/InvalidResponseActionEnum' | ||||||
|  |           description: Configure how the flow executor should handle an invalid response | ||||||
|  |             to a challenge. RETRY returns the error message and a similar challenge | ||||||
|  |             to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT | ||||||
|  |             restarts the flow while keeping the current context. | ||||||
|       required: |       required: | ||||||
|       - order |       - order | ||||||
|       - pk |       - pk | ||||||
| @ -19703,6 +19859,13 @@ components: | |||||||
|           minimum: -2147483648 |           minimum: -2147483648 | ||||||
|         policy_engine_mode: |         policy_engine_mode: | ||||||
|           $ref: '#/components/schemas/PolicyEngineMode' |           $ref: '#/components/schemas/PolicyEngineMode' | ||||||
|  |         invalid_response_action: | ||||||
|  |           allOf: | ||||||
|  |           - $ref: '#/components/schemas/InvalidResponseActionEnum' | ||||||
|  |           description: Configure how the flow executor should handle an invalid response | ||||||
|  |             to a challenge. RETRY returns the error message and a similar challenge | ||||||
|  |             to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT | ||||||
|  |             restarts the flow while keeping the current context. | ||||||
|       required: |       required: | ||||||
|       - order |       - order | ||||||
|       - stage |       - stage | ||||||
| @ -20048,6 +20211,12 @@ components: | |||||||
|       - api |       - api | ||||||
|       - recovery |       - recovery | ||||||
|       type: string |       type: string | ||||||
|  |     InvalidResponseActionEnum: | ||||||
|  |       enum: | ||||||
|  |       - retry | ||||||
|  |       - restart | ||||||
|  |       - restart_with_context | ||||||
|  |       type: string | ||||||
|     Invitation: |     Invitation: | ||||||
|       type: object |       type: object | ||||||
|       description: Invitation Serializer |       description: Invitation Serializer | ||||||
| @ -24429,7 +24598,7 @@ components: | |||||||
|             will be logged. By default, only execution errors are logged. |             will be logged. By default, only execution errors are logged. | ||||||
|         action: |         action: | ||||||
|           allOf: |           allOf: | ||||||
|           - $ref: '#/components/schemas/EventMatcherPolicyActionEnum' |           - $ref: '#/components/schemas/EventActions' | ||||||
|           description: Match created events with this action type. When left empty, |           description: Match created events with this action type. When left empty, | ||||||
|             all action types will be matched. |             all action types will be matched. | ||||||
|         client_ip: |         client_ip: | ||||||
| @ -24441,6 +24610,29 @@ components: | |||||||
|           - $ref: '#/components/schemas/AppEnum' |           - $ref: '#/components/schemas/AppEnum' | ||||||
|           description: Match events created by selected application. When left empty, |           description: Match events created by selected application. When left empty, | ||||||
|             all applications are matched. |             all applications are matched. | ||||||
|  |     PatchedEventRequest: | ||||||
|  |       type: object | ||||||
|  |       description: Event Serializer | ||||||
|  |       properties: | ||||||
|  |         user: | ||||||
|  |           type: object | ||||||
|  |           additionalProperties: {} | ||||||
|  |         action: | ||||||
|  |           $ref: '#/components/schemas/EventActions' | ||||||
|  |         app: | ||||||
|  |           type: string | ||||||
|  |         context: | ||||||
|  |           type: object | ||||||
|  |           additionalProperties: {} | ||||||
|  |         client_ip: | ||||||
|  |           type: string | ||||||
|  |           nullable: true | ||||||
|  |         expires: | ||||||
|  |           type: string | ||||||
|  |           format: date-time | ||||||
|  |         tenant: | ||||||
|  |           type: object | ||||||
|  |           additionalProperties: {} | ||||||
|     PatchedExpressionPolicyRequest: |     PatchedExpressionPolicyRequest: | ||||||
|       type: object |       type: object | ||||||
|       description: Group Membership Policy Serializer |       description: Group Membership Policy Serializer | ||||||
| @ -24502,6 +24694,13 @@ components: | |||||||
|           minimum: -2147483648 |           minimum: -2147483648 | ||||||
|         policy_engine_mode: |         policy_engine_mode: | ||||||
|           $ref: '#/components/schemas/PolicyEngineMode' |           $ref: '#/components/schemas/PolicyEngineMode' | ||||||
|  |         invalid_response_action: | ||||||
|  |           allOf: | ||||||
|  |           - $ref: '#/components/schemas/InvalidResponseActionEnum' | ||||||
|  |           description: Configure how the flow executor should handle an invalid response | ||||||
|  |             to a challenge. RETRY returns the error message and a similar challenge | ||||||
|  |             to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT | ||||||
|  |             restarts the flow while keeping the current context. | ||||||
|     PatchedGroupRequest: |     PatchedGroupRequest: | ||||||
|       type: object |       type: object | ||||||
|       description: Group Serializer |       description: Group Serializer | ||||||
| @ -25579,6 +25778,9 @@ components: | |||||||
|           type: array |           type: array | ||||||
|           items: |           items: | ||||||
|             $ref: '#/components/schemas/FlowRequest' |             $ref: '#/components/schemas/FlowRequest' | ||||||
|  |         create_users_as_inactive: | ||||||
|  |           type: boolean | ||||||
|  |           description: When set, newly created users are inactive and cannot login. | ||||||
|     PatchedWebAuthnDeviceRequest: |     PatchedWebAuthnDeviceRequest: | ||||||
|       type: object |       type: object | ||||||
|       description: Serializer for WebAuthn authenticator devices |       description: Serializer for WebAuthn authenticator devices | ||||||
| @ -28073,6 +28275,9 @@ components: | |||||||
|           type: array |           type: array | ||||||
|           items: |           items: | ||||||
|             $ref: '#/components/schemas/Flow' |             $ref: '#/components/schemas/Flow' | ||||||
|  |         create_users_as_inactive: | ||||||
|  |           type: boolean | ||||||
|  |           description: When set, newly created users are inactive and cannot login. | ||||||
|       required: |       required: | ||||||
|       - component |       - component | ||||||
|       - name |       - name | ||||||
| @ -28089,6 +28294,9 @@ components: | |||||||
|           type: array |           type: array | ||||||
|           items: |           items: | ||||||
|             $ref: '#/components/schemas/FlowRequest' |             $ref: '#/components/schemas/FlowRequest' | ||||||
|  |         create_users_as_inactive: | ||||||
|  |           type: boolean | ||||||
|  |           description: When set, newly created users are inactive and cannot login. | ||||||
|       required: |       required: | ||||||
|       - name |       - name | ||||||
|     ValidationError: |     ValidationError: | ||||||
|  | |||||||
							
								
								
									
										348
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										348
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -18,24 +18,24 @@ | |||||||
|                 "@lingui/cli": "^3.10.2", |                 "@lingui/cli": "^3.10.2", | ||||||
|                 "@lingui/core": "^3.10.4", |                 "@lingui/core": "^3.10.4", | ||||||
|                 "@lingui/macro": "^3.10.2", |                 "@lingui/macro": "^3.10.2", | ||||||
|                 "@patternfly/patternfly": "^4.108.2", |                 "@patternfly/patternfly": "^4.115.2", | ||||||
|                 "@polymer/iron-form": "^3.0.1", |                 "@polymer/iron-form": "^3.0.1", | ||||||
|                 "@polymer/paper-input": "^3.2.1", |                 "@polymer/paper-input": "^3.2.1", | ||||||
|                 "@rollup/plugin-babel": "^5.3.0", |                 "@rollup/plugin-babel": "^5.3.0", | ||||||
|                 "@rollup/plugin-replace": "^2.4.2", |                 "@rollup/plugin-replace": "^2.4.2", | ||||||
|                 "@rollup/plugin-typescript": "^8.2.1", |                 "@rollup/plugin-typescript": "^8.2.1", | ||||||
|                 "@sentry/browser": "^6.7.2", |                 "@sentry/browser": "^6.8.0", | ||||||
|                 "@sentry/tracing": "^6.7.2", |                 "@sentry/tracing": "^6.8.0", | ||||||
|                 "@types/chart.js": "^2.9.32", |                 "@types/chart.js": "^2.9.32", | ||||||
|                 "@types/codemirror": "5.60.0", |                 "@types/codemirror": "5.60.1", | ||||||
|                 "@types/grecaptcha": "^3.0.2", |                 "@types/grecaptcha": "^3.0.2", | ||||||
|                 "@typescript-eslint/eslint-plugin": "^4.28.0", |                 "@typescript-eslint/eslint-plugin": "^4.28.1", | ||||||
|                 "@typescript-eslint/parser": "^4.28.0", |                 "@typescript-eslint/parser": "^4.28.1", | ||||||
|                 "@webcomponents/webcomponentsjs": "^2.5.0", |                 "@webcomponents/webcomponentsjs": "^2.5.0", | ||||||
|                 "authentik-api": "file:api", |                 "authentik-api": "file:api", | ||||||
|                 "babel-plugin-macros": "^3.1.0", |                 "babel-plugin-macros": "^3.1.0", | ||||||
|                 "base64-js": "^1.5.1", |                 "base64-js": "^1.5.1", | ||||||
|                 "chart.js": "^3.3.2", |                 "chart.js": "^3.4.0", | ||||||
|                 "chartjs-adapter-moment": "^1.0.0", |                 "chartjs-adapter-moment": "^1.0.0", | ||||||
|                 "codemirror": "^5.62.0", |                 "codemirror": "^5.62.0", | ||||||
|                 "construct-style-sheets-polyfill": "^2.4.16", |                 "construct-style-sheets-polyfill": "^2.4.16", | ||||||
| @ -48,7 +48,7 @@ | |||||||
|                 "lit-html": "^1.4.1", |                 "lit-html": "^1.4.1", | ||||||
|                 "moment": "^2.29.1", |                 "moment": "^2.29.1", | ||||||
|                 "rapidoc": "^9.0.0", |                 "rapidoc": "^9.0.0", | ||||||
|                 "rollup": "^2.52.2", |                 "rollup": "^2.52.3", | ||||||
|                 "rollup-plugin-commonjs": "^10.1.0", |                 "rollup-plugin-commonjs": "^10.1.0", | ||||||
|                 "rollup-plugin-copy": "^3.4.0", |                 "rollup-plugin-copy": "^3.4.0", | ||||||
|                 "rollup-plugin-cssimport": "^1.0.2", |                 "rollup-plugin-cssimport": "^1.0.2", | ||||||
| @ -2120,9 +2120,9 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/@patternfly/patternfly": { |         "node_modules/@patternfly/patternfly": { | ||||||
|             "version": "4.108.2", |             "version": "4.115.2", | ||||||
|             "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.108.2.tgz", |             "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.115.2.tgz", | ||||||
|             "integrity": "sha512-z0VB+1CXcH+eoClYQABwapX5FURSvm1nPr6asLWwg/Z4Wuxs0RjZpC6Gb+KRm8nGQwSAcMKZY1jLfPqVnznQnw==" |             "integrity": "sha512-7hbJ4pRmj+rlXclD2F/UwceO6fS+9flGsgHc4eUc7NyTN2GXl6PLcqrjE2CtiKEPV90+KwsGQGJXZj8bz9HweA==" | ||||||
|         }, |         }, | ||||||
|         "node_modules/@polymer/font-roboto": { |         "node_modules/@polymer/font-roboto": { | ||||||
|             "version": "3.0.2", |             "version": "3.0.2", | ||||||
| @ -2314,13 +2314,13 @@ | |||||||
|             "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" |             "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" | ||||||
|         }, |         }, | ||||||
|         "node_modules/@sentry/browser": { |         "node_modules/@sentry/browser": { | ||||||
|             "version": "6.7.2", |             "version": "6.8.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.2.tgz", |             "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.8.0.tgz", | ||||||
|             "integrity": "sha512-Lv0Ne1QcesyGAhVcQDfQa3hDPR/MhPSDTMg3xFi+LxqztchVc4w/ynzR0wCZFb8KIHpTj5SpJHfxpDhXYMtS9g==", |             "integrity": "sha512-nxa71csHlG5sMHUxI4e4xxuCWtbCv/QbBfMsYw7ncJSfCKG3yNlCVh8NJ7NS0rZW/MJUT6S6+r93zw0HetNDOA==", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "@sentry/core": "6.7.2", |                 "@sentry/core": "6.8.0", | ||||||
|                 "@sentry/types": "6.7.2", |                 "@sentry/types": "6.8.0", | ||||||
|                 "@sentry/utils": "6.7.2", |                 "@sentry/utils": "6.8.0", | ||||||
|                 "tslib": "^1.9.3" |                 "tslib": "^1.9.3" | ||||||
|             }, |             }, | ||||||
|             "engines": { |             "engines": { | ||||||
| @ -2333,14 +2333,14 @@ | |||||||
|             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" |             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" | ||||||
|         }, |         }, | ||||||
|         "node_modules/@sentry/core": { |         "node_modules/@sentry/core": { | ||||||
|             "version": "6.7.2", |             "version": "6.8.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.2.tgz", |             "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.8.0.tgz", | ||||||
|             "integrity": "sha512-NTZqwN5nR94yrXmSfekoPs1mIFuKvf8esdIW/DadwSKWAdLJwQTJY9xK/8PQv+SEzd7wiitPAx+mCw2By1xiNQ==", |             "integrity": "sha512-vJzWt/znEB+JqVwtwfjkRrAYRN+ep+l070Ti8GhJnvwU4IDtVlV3T/jVNrj6rl6UChcczaJQMxVxtG5x0crlAA==", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "@sentry/hub": "6.7.2", |                 "@sentry/hub": "6.8.0", | ||||||
|                 "@sentry/minimal": "6.7.2", |                 "@sentry/minimal": "6.8.0", | ||||||
|                 "@sentry/types": "6.7.2", |                 "@sentry/types": "6.8.0", | ||||||
|                 "@sentry/utils": "6.7.2", |                 "@sentry/utils": "6.8.0", | ||||||
|                 "tslib": "^1.9.3" |                 "tslib": "^1.9.3" | ||||||
|             }, |             }, | ||||||
|             "engines": { |             "engines": { | ||||||
| @ -2353,12 +2353,12 @@ | |||||||
|             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" |             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" | ||||||
|         }, |         }, | ||||||
|         "node_modules/@sentry/hub": { |         "node_modules/@sentry/hub": { | ||||||
|             "version": "6.7.2", |             "version": "6.8.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.2.tgz", |             "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.8.0.tgz", | ||||||
|             "integrity": "sha512-05qVW6ymChJsXag4+fYCQokW3AcABIgcqrVYZUBf6GMU/Gbz5SJqpV7y1+njwWvnPZydMncP9LaDVpMKbE7UYQ==", |             "integrity": "sha512-hFrI2Ss1fTov7CH64FJpigqRxH7YvSnGeqxT9Jc1BL7nzW/vgCK+Oh2mOZbosTcrzoDv+lE8ViOnSN3w/fo+rg==", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "@sentry/types": "6.7.2", |                 "@sentry/types": "6.8.0", | ||||||
|                 "@sentry/utils": "6.7.2", |                 "@sentry/utils": "6.8.0", | ||||||
|                 "tslib": "^1.9.3" |                 "tslib": "^1.9.3" | ||||||
|             }, |             }, | ||||||
|             "engines": { |             "engines": { | ||||||
| @ -2371,12 +2371,12 @@ | |||||||
|             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" |             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" | ||||||
|         }, |         }, | ||||||
|         "node_modules/@sentry/minimal": { |         "node_modules/@sentry/minimal": { | ||||||
|             "version": "6.7.2", |             "version": "6.8.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.2.tgz", |             "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.8.0.tgz", | ||||||
|             "integrity": "sha512-jkpwFv2GFHoVl5vnK+9/Q+Ea8eVdbJ3hn3/Dqq9MOLFnVK7ED6MhdHKLT79puGSFj+85OuhM5m2Q44mIhyS5mw==", |             "integrity": "sha512-MRxUKXiiYwKjp8mOQMpTpEuIby1Jh3zRTU0cmGZtfsZ38BC1JOle8xlwC4FdtOH+VvjSYnPBMya5lgNHNPUJDQ==", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "@sentry/hub": "6.7.2", |                 "@sentry/hub": "6.8.0", | ||||||
|                 "@sentry/types": "6.7.2", |                 "@sentry/types": "6.8.0", | ||||||
|                 "tslib": "^1.9.3" |                 "tslib": "^1.9.3" | ||||||
|             }, |             }, | ||||||
|             "engines": { |             "engines": { | ||||||
| @ -2389,14 +2389,14 @@ | |||||||
|             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" |             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" | ||||||
|         }, |         }, | ||||||
|         "node_modules/@sentry/tracing": { |         "node_modules/@sentry/tracing": { | ||||||
|             "version": "6.7.2", |             "version": "6.8.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.2.tgz", |             "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.8.0.tgz", | ||||||
|             "integrity": "sha512-juKlI7FICKONWJFJxDxerj0A+8mNRhmtrdR+OXFqOkqSAy/QXlSFZcA/j//O19k2CfwK1BrvoMcQ/4gnffUOVg==", |             "integrity": "sha512-3gDkQnmOuOjHz5rY7BOatLEUksANU3efR8wuBa2ujsPQvoLSLFuyZpRjPPsxuUHQOqAYIbSNAoDloXECvQeHjw==", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "@sentry/hub": "6.7.2", |                 "@sentry/hub": "6.8.0", | ||||||
|                 "@sentry/minimal": "6.7.2", |                 "@sentry/minimal": "6.8.0", | ||||||
|                 "@sentry/types": "6.7.2", |                 "@sentry/types": "6.8.0", | ||||||
|                 "@sentry/utils": "6.7.2", |                 "@sentry/utils": "6.8.0", | ||||||
|                 "tslib": "^1.9.3" |                 "tslib": "^1.9.3" | ||||||
|             }, |             }, | ||||||
|             "engines": { |             "engines": { | ||||||
| @ -2409,19 +2409,19 @@ | |||||||
|             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" |             "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" | ||||||
|         }, |         }, | ||||||
|         "node_modules/@sentry/types": { |         "node_modules/@sentry/types": { | ||||||
|             "version": "6.7.2", |             "version": "6.8.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.2.tgz", |             "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.8.0.tgz", | ||||||
|             "integrity": "sha512-h21Go/PfstUN+ZV6SbwRSZVg9GXRJWdLfHoO5PSVb3TVEMckuxk8tAE57/u+UZDwX8wu+Xyon2TgsKpiWKxqUg==", |             "integrity": "sha512-PbSxqlh6Fd5thNU5f8EVYBVvX+G7XdPA+ThNb2QvSK8yv3rIf0McHTyF6sIebgJ38OYN7ZFK7vvhC/RgSAfYTA==", | ||||||
|             "engines": { |             "engines": { | ||||||
|                 "node": ">=6" |                 "node": ">=6" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/@sentry/utils": { |         "node_modules/@sentry/utils": { | ||||||
|             "version": "6.7.2", |             "version": "6.8.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.2.tgz", |             "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.8.0.tgz", | ||||||
|             "integrity": "sha512-9COL7aaBbe61Hp5BlArtXZ1o/cxli1NGONLPrVT4fMyeQFmLonhUiy77NdsW19XnvhvaA+2IoV5dg3dnFiF/og==", |             "integrity": "sha512-OYlI2JNrcWKMdvYbWNdQwR4QBVv2V0y5wK0U6f53nArv6RsyO5TzwRu5rMVSIZofUUqjoE5hl27jqnR+vpUrsA==", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "@sentry/types": "6.7.2", |                 "@sentry/types": "6.8.0", | ||||||
|                 "tslib": "^1.9.3" |                 "tslib": "^1.9.3" | ||||||
|             }, |             }, | ||||||
|             "engines": { |             "engines": { | ||||||
| @ -2451,9 +2451,9 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/@types/codemirror": { |         "node_modules/@types/codemirror": { | ||||||
|             "version": "5.60.0", |             "version": "5.60.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.0.tgz", |             "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.1.tgz", | ||||||
|             "integrity": "sha512-xgzXZyCzedLRNC67/Nn8rpBtTFnAsX2C+Q/LGoH6zgcpF/LqdNHJMHEOhqT1bwUcSp6kQdOIuKzRbeW9DYhEhg==", |             "integrity": "sha512-yV14LQ5VvghnW0uSuCw2bEfZC6NvxHQEckl2w3dEk5l0yPGzQh14dCaWvG5KD/2l3cgFSifR+6nIUD7LDLdUTg==", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "@types/tern": "*" |                 "@types/tern": "*" | ||||||
|             } |             } | ||||||
| @ -2579,12 +2579,12 @@ | |||||||
|             "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" |             "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" | ||||||
|         }, |         }, | ||||||
|         "node_modules/@typescript-eslint/eslint-plugin": { |         "node_modules/@typescript-eslint/eslint-plugin": { | ||||||
|             "version": "4.28.0", |             "version": "4.28.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz", |             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.1.tgz", | ||||||
|             "integrity": "sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ==", |             "integrity": "sha512-9yfcNpDaNGQ6/LQOX/KhUFTR1sCKH+PBr234k6hI9XJ0VP5UqGxap0AnNwBnWFk1MNyWBylJH9ZkzBXC+5akZQ==", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "@typescript-eslint/experimental-utils": "4.28.0", |                 "@typescript-eslint/experimental-utils": "4.28.1", | ||||||
|                 "@typescript-eslint/scope-manager": "4.28.0", |                 "@typescript-eslint/scope-manager": "4.28.1", | ||||||
|                 "debug": "^4.3.1", |                 "debug": "^4.3.1", | ||||||
|                 "functional-red-black-tree": "^1.0.1", |                 "functional-red-black-tree": "^1.0.1", | ||||||
|                 "regexpp": "^3.1.0", |                 "regexpp": "^3.1.0", | ||||||
| @ -2609,14 +2609,14 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/@typescript-eslint/experimental-utils": { |         "node_modules/@typescript-eslint/experimental-utils": { | ||||||
|             "version": "4.28.0", |             "version": "4.28.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz", |             "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.1.tgz", | ||||||
|             "integrity": "sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ==", |             "integrity": "sha512-n8/ggadrZ+uyrfrSEchx3jgODdmcx7MzVM2sI3cTpI/YlfSm0+9HEUaWw3aQn2urL2KYlWYMDgn45iLfjDYB+Q==", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "@types/json-schema": "^7.0.7", |                 "@types/json-schema": "^7.0.7", | ||||||
|                 "@typescript-eslint/scope-manager": "4.28.0", |                 "@typescript-eslint/scope-manager": "4.28.1", | ||||||
|                 "@typescript-eslint/types": "4.28.0", |                 "@typescript-eslint/types": "4.28.1", | ||||||
|                 "@typescript-eslint/typescript-estree": "4.28.0", |                 "@typescript-eslint/typescript-estree": "4.28.1", | ||||||
|                 "eslint-scope": "^5.1.1", |                 "eslint-scope": "^5.1.1", | ||||||
|                 "eslint-utils": "^3.0.0" |                 "eslint-utils": "^3.0.0" | ||||||
|             }, |             }, | ||||||
| @ -2649,13 +2649,13 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/@typescript-eslint/parser": { |         "node_modules/@typescript-eslint/parser": { | ||||||
|             "version": "4.28.0", |             "version": "4.28.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.0.tgz", |             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.1.tgz", | ||||||
|             "integrity": "sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A==", |             "integrity": "sha512-UjrMsgnhQIIK82hXGaD+MCN8IfORS1CbMdu7VlZbYa8LCZtbZjJA26De4IPQB7XYZbL8gJ99KWNj0l6WD0guJg==", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "@typescript-eslint/scope-manager": "4.28.0", |                 "@typescript-eslint/scope-manager": "4.28.1", | ||||||
|                 "@typescript-eslint/types": "4.28.0", |                 "@typescript-eslint/types": "4.28.1", | ||||||
|                 "@typescript-eslint/typescript-estree": "4.28.0", |                 "@typescript-eslint/typescript-estree": "4.28.1", | ||||||
|                 "debug": "^4.3.1" |                 "debug": "^4.3.1" | ||||||
|             }, |             }, | ||||||
|             "engines": { |             "engines": { | ||||||
| @ -2675,12 +2675,12 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/@typescript-eslint/scope-manager": { |         "node_modules/@typescript-eslint/scope-manager": { | ||||||
|             "version": "4.28.0", |             "version": "4.28.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz", |             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.1.tgz", | ||||||
|             "integrity": "sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg==", |             "integrity": "sha512-o95bvGKfss6705x7jFGDyS7trAORTy57lwJ+VsYwil/lOUxKQ9tA7Suuq+ciMhJc/1qPwB3XE2DKh9wubW8YYA==", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "@typescript-eslint/types": "4.28.0", |                 "@typescript-eslint/types": "4.28.1", | ||||||
|                 "@typescript-eslint/visitor-keys": "4.28.0" |                 "@typescript-eslint/visitor-keys": "4.28.1" | ||||||
|             }, |             }, | ||||||
|             "engines": { |             "engines": { | ||||||
|                 "node": "^8.10.0 || ^10.13.0 || >=11.10.1" |                 "node": "^8.10.0 || ^10.13.0 || >=11.10.1" | ||||||
| @ -2691,9 +2691,9 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/@typescript-eslint/types": { |         "node_modules/@typescript-eslint/types": { | ||||||
|             "version": "4.28.0", |             "version": "4.28.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.0.tgz", |             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.1.tgz", | ||||||
|             "integrity": "sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA==", |             "integrity": "sha512-4z+knEihcyX7blAGi7O3Fm3O6YRCP+r56NJFMNGsmtdw+NCdpG5SgNz427LS9nQkRVTswZLhz484hakQwB8RRg==", | ||||||
|             "engines": { |             "engines": { | ||||||
|                 "node": "^8.10.0 || ^10.13.0 || >=11.10.1" |                 "node": "^8.10.0 || ^10.13.0 || >=11.10.1" | ||||||
|             }, |             }, | ||||||
| @ -2703,12 +2703,12 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/@typescript-eslint/typescript-estree": { |         "node_modules/@typescript-eslint/typescript-estree": { | ||||||
|             "version": "4.28.0", |             "version": "4.28.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz", |             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.1.tgz", | ||||||
|             "integrity": "sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ==", |             "integrity": "sha512-GhKxmC4sHXxHGJv8e8egAZeTZ6HI4mLU6S7FUzvFOtsk7ZIDN1ksA9r9DyOgNqowA9yAtZXV0Uiap61bIO81FQ==", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "@typescript-eslint/types": "4.28.0", |                 "@typescript-eslint/types": "4.28.1", | ||||||
|                 "@typescript-eslint/visitor-keys": "4.28.0", |                 "@typescript-eslint/visitor-keys": "4.28.1", | ||||||
|                 "debug": "^4.3.1", |                 "debug": "^4.3.1", | ||||||
|                 "globby": "^11.0.3", |                 "globby": "^11.0.3", | ||||||
|                 "is-glob": "^4.0.1", |                 "is-glob": "^4.0.1", | ||||||
| @ -2748,11 +2748,11 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/@typescript-eslint/visitor-keys": { |         "node_modules/@typescript-eslint/visitor-keys": { | ||||||
|             "version": "4.28.0", |             "version": "4.28.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz", |             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.1.tgz", | ||||||
|             "integrity": "sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw==", |             "integrity": "sha512-K4HMrdFqr9PFquPu178SaSb92CaWe2yErXyPumc8cYWxFmhgJsNY9eSePmO05j0JhBvf2Cdhptd6E6Yv9HVHcg==", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "@typescript-eslint/types": "4.28.0", |                 "@typescript-eslint/types": "4.28.1", | ||||||
|                 "eslint-visitor-keys": "^2.0.0" |                 "eslint-visitor-keys": "^2.0.0" | ||||||
|             }, |             }, | ||||||
|             "engines": { |             "engines": { | ||||||
| @ -3316,9 +3316,9 @@ | |||||||
|             "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" |             "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" | ||||||
|         }, |         }, | ||||||
|         "node_modules/chart.js": { |         "node_modules/chart.js": { | ||||||
|             "version": "3.3.2", |             "version": "3.4.0", | ||||||
|             "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.3.2.tgz", |             "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.4.0.tgz", | ||||||
|             "integrity": "sha512-H0hSO7xqTIrwxoACqnSoNromEMfXvfuVnrbuSt2TuXfBDDofbnto4zuZlRtRvC73/b37q3wGAWZyUU41QPvNbA==" |             "integrity": "sha512-mJsRm2apQm5mwz2OgYqGNG4erZh/qljcRZkWSa0kLkFr3UC3e1wKRMgnIh6WdhUrNu0w/JT9PkjLyylqEqHXEQ==" | ||||||
|         }, |         }, | ||||||
|         "node_modules/chartjs-adapter-moment": { |         "node_modules/chartjs-adapter-moment": { | ||||||
|             "version": "1.0.0", |             "version": "1.0.0", | ||||||
| @ -6770,9 +6770,9 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/rollup": { |         "node_modules/rollup": { | ||||||
|             "version": "2.52.2", |             "version": "2.52.3", | ||||||
|             "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz", |             "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.3.tgz", | ||||||
|             "integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==", |             "integrity": "sha512-QF3Sju8Kl2z0osI4unyOLyUudyhOMK6G0AeqJWgfiyigqLAlnNrfBcDWDx+f1cqn+JU2iIYVkDrgQ6/KtwEfrg==", | ||||||
|             "bin": { |             "bin": { | ||||||
|                 "rollup": "dist/bin/rollup" |                 "rollup": "dist/bin/rollup" | ||||||
|             }, |             }, | ||||||
| @ -9482,9 +9482,9 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "@patternfly/patternfly": { |         "@patternfly/patternfly": { | ||||||
|             "version": "4.108.2", |             "version": "4.115.2", | ||||||
|             "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.108.2.tgz", |             "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.115.2.tgz", | ||||||
|             "integrity": "sha512-z0VB+1CXcH+eoClYQABwapX5FURSvm1nPr6asLWwg/Z4Wuxs0RjZpC6Gb+KRm8nGQwSAcMKZY1jLfPqVnznQnw==" |             "integrity": "sha512-7hbJ4pRmj+rlXclD2F/UwceO6fS+9flGsgHc4eUc7NyTN2GXl6PLcqrjE2CtiKEPV90+KwsGQGJXZj8bz9HweA==" | ||||||
|         }, |         }, | ||||||
|         "@polymer/font-roboto": { |         "@polymer/font-roboto": { | ||||||
|             "version": "3.0.2", |             "version": "3.0.2", | ||||||
| @ -9669,13 +9669,13 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "@sentry/browser": { |         "@sentry/browser": { | ||||||
|             "version": "6.7.2", |             "version": "6.8.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.2.tgz", |             "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.8.0.tgz", | ||||||
|             "integrity": "sha512-Lv0Ne1QcesyGAhVcQDfQa3hDPR/MhPSDTMg3xFi+LxqztchVc4w/ynzR0wCZFb8KIHpTj5SpJHfxpDhXYMtS9g==", |             "integrity": "sha512-nxa71csHlG5sMHUxI4e4xxuCWtbCv/QbBfMsYw7ncJSfCKG3yNlCVh8NJ7NS0rZW/MJUT6S6+r93zw0HetNDOA==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "@sentry/core": "6.7.2", |                 "@sentry/core": "6.8.0", | ||||||
|                 "@sentry/types": "6.7.2", |                 "@sentry/types": "6.8.0", | ||||||
|                 "@sentry/utils": "6.7.2", |                 "@sentry/utils": "6.8.0", | ||||||
|                 "tslib": "^1.9.3" |                 "tslib": "^1.9.3" | ||||||
|             }, |             }, | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
| @ -9687,14 +9687,14 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "@sentry/core": { |         "@sentry/core": { | ||||||
|             "version": "6.7.2", |             "version": "6.8.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.2.tgz", |             "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.8.0.tgz", | ||||||
|             "integrity": "sha512-NTZqwN5nR94yrXmSfekoPs1mIFuKvf8esdIW/DadwSKWAdLJwQTJY9xK/8PQv+SEzd7wiitPAx+mCw2By1xiNQ==", |             "integrity": "sha512-vJzWt/znEB+JqVwtwfjkRrAYRN+ep+l070Ti8GhJnvwU4IDtVlV3T/jVNrj6rl6UChcczaJQMxVxtG5x0crlAA==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "@sentry/hub": "6.7.2", |                 "@sentry/hub": "6.8.0", | ||||||
|                 "@sentry/minimal": "6.7.2", |                 "@sentry/minimal": "6.8.0", | ||||||
|                 "@sentry/types": "6.7.2", |                 "@sentry/types": "6.8.0", | ||||||
|                 "@sentry/utils": "6.7.2", |                 "@sentry/utils": "6.8.0", | ||||||
|                 "tslib": "^1.9.3" |                 "tslib": "^1.9.3" | ||||||
|             }, |             }, | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
| @ -9706,12 +9706,12 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "@sentry/hub": { |         "@sentry/hub": { | ||||||
|             "version": "6.7.2", |             "version": "6.8.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.2.tgz", |             "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.8.0.tgz", | ||||||
|             "integrity": "sha512-05qVW6ymChJsXag4+fYCQokW3AcABIgcqrVYZUBf6GMU/Gbz5SJqpV7y1+njwWvnPZydMncP9LaDVpMKbE7UYQ==", |             "integrity": "sha512-hFrI2Ss1fTov7CH64FJpigqRxH7YvSnGeqxT9Jc1BL7nzW/vgCK+Oh2mOZbosTcrzoDv+lE8ViOnSN3w/fo+rg==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "@sentry/types": "6.7.2", |                 "@sentry/types": "6.8.0", | ||||||
|                 "@sentry/utils": "6.7.2", |                 "@sentry/utils": "6.8.0", | ||||||
|                 "tslib": "^1.9.3" |                 "tslib": "^1.9.3" | ||||||
|             }, |             }, | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
| @ -9723,12 +9723,12 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "@sentry/minimal": { |         "@sentry/minimal": { | ||||||
|             "version": "6.7.2", |             "version": "6.8.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.2.tgz", |             "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.8.0.tgz", | ||||||
|             "integrity": "sha512-jkpwFv2GFHoVl5vnK+9/Q+Ea8eVdbJ3hn3/Dqq9MOLFnVK7ED6MhdHKLT79puGSFj+85OuhM5m2Q44mIhyS5mw==", |             "integrity": "sha512-MRxUKXiiYwKjp8mOQMpTpEuIby1Jh3zRTU0cmGZtfsZ38BC1JOle8xlwC4FdtOH+VvjSYnPBMya5lgNHNPUJDQ==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "@sentry/hub": "6.7.2", |                 "@sentry/hub": "6.8.0", | ||||||
|                 "@sentry/types": "6.7.2", |                 "@sentry/types": "6.8.0", | ||||||
|                 "tslib": "^1.9.3" |                 "tslib": "^1.9.3" | ||||||
|             }, |             }, | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
| @ -9740,14 +9740,14 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "@sentry/tracing": { |         "@sentry/tracing": { | ||||||
|             "version": "6.7.2", |             "version": "6.8.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.2.tgz", |             "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.8.0.tgz", | ||||||
|             "integrity": "sha512-juKlI7FICKONWJFJxDxerj0A+8mNRhmtrdR+OXFqOkqSAy/QXlSFZcA/j//O19k2CfwK1BrvoMcQ/4gnffUOVg==", |             "integrity": "sha512-3gDkQnmOuOjHz5rY7BOatLEUksANU3efR8wuBa2ujsPQvoLSLFuyZpRjPPsxuUHQOqAYIbSNAoDloXECvQeHjw==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "@sentry/hub": "6.7.2", |                 "@sentry/hub": "6.8.0", | ||||||
|                 "@sentry/minimal": "6.7.2", |                 "@sentry/minimal": "6.8.0", | ||||||
|                 "@sentry/types": "6.7.2", |                 "@sentry/types": "6.8.0", | ||||||
|                 "@sentry/utils": "6.7.2", |                 "@sentry/utils": "6.8.0", | ||||||
|                 "tslib": "^1.9.3" |                 "tslib": "^1.9.3" | ||||||
|             }, |             }, | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
| @ -9759,16 +9759,16 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "@sentry/types": { |         "@sentry/types": { | ||||||
|             "version": "6.7.2", |             "version": "6.8.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.2.tgz", |             "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.8.0.tgz", | ||||||
|             "integrity": "sha512-h21Go/PfstUN+ZV6SbwRSZVg9GXRJWdLfHoO5PSVb3TVEMckuxk8tAE57/u+UZDwX8wu+Xyon2TgsKpiWKxqUg==" |             "integrity": "sha512-PbSxqlh6Fd5thNU5f8EVYBVvX+G7XdPA+ThNb2QvSK8yv3rIf0McHTyF6sIebgJ38OYN7ZFK7vvhC/RgSAfYTA==" | ||||||
|         }, |         }, | ||||||
|         "@sentry/utils": { |         "@sentry/utils": { | ||||||
|             "version": "6.7.2", |             "version": "6.8.0", | ||||||
|             "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.2.tgz", |             "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.8.0.tgz", | ||||||
|             "integrity": "sha512-9COL7aaBbe61Hp5BlArtXZ1o/cxli1NGONLPrVT4fMyeQFmLonhUiy77NdsW19XnvhvaA+2IoV5dg3dnFiF/og==", |             "integrity": "sha512-OYlI2JNrcWKMdvYbWNdQwR4QBVv2V0y5wK0U6f53nArv6RsyO5TzwRu5rMVSIZofUUqjoE5hl27jqnR+vpUrsA==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "@sentry/types": "6.7.2", |                 "@sentry/types": "6.8.0", | ||||||
|                 "tslib": "^1.9.3" |                 "tslib": "^1.9.3" | ||||||
|             }, |             }, | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
| @ -9797,9 +9797,9 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "@types/codemirror": { |         "@types/codemirror": { | ||||||
|             "version": "5.60.0", |             "version": "5.60.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.0.tgz", |             "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.1.tgz", | ||||||
|             "integrity": "sha512-xgzXZyCzedLRNC67/Nn8rpBtTFnAsX2C+Q/LGoH6zgcpF/LqdNHJMHEOhqT1bwUcSp6kQdOIuKzRbeW9DYhEhg==", |             "integrity": "sha512-yV14LQ5VvghnW0uSuCw2bEfZC6NvxHQEckl2w3dEk5l0yPGzQh14dCaWvG5KD/2l3cgFSifR+6nIUD7LDLdUTg==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "@types/tern": "*" |                 "@types/tern": "*" | ||||||
|             } |             } | ||||||
| @ -9925,12 +9925,12 @@ | |||||||
|             "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" |             "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" | ||||||
|         }, |         }, | ||||||
|         "@typescript-eslint/eslint-plugin": { |         "@typescript-eslint/eslint-plugin": { | ||||||
|             "version": "4.28.0", |             "version": "4.28.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz", |             "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.1.tgz", | ||||||
|             "integrity": "sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ==", |             "integrity": "sha512-9yfcNpDaNGQ6/LQOX/KhUFTR1sCKH+PBr234k6hI9XJ0VP5UqGxap0AnNwBnWFk1MNyWBylJH9ZkzBXC+5akZQ==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "@typescript-eslint/experimental-utils": "4.28.0", |                 "@typescript-eslint/experimental-utils": "4.28.1", | ||||||
|                 "@typescript-eslint/scope-manager": "4.28.0", |                 "@typescript-eslint/scope-manager": "4.28.1", | ||||||
|                 "debug": "^4.3.1", |                 "debug": "^4.3.1", | ||||||
|                 "functional-red-black-tree": "^1.0.1", |                 "functional-red-black-tree": "^1.0.1", | ||||||
|                 "regexpp": "^3.1.0", |                 "regexpp": "^3.1.0", | ||||||
| @ -9939,14 +9939,14 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "@typescript-eslint/experimental-utils": { |         "@typescript-eslint/experimental-utils": { | ||||||
|             "version": "4.28.0", |             "version": "4.28.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz", |             "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.1.tgz", | ||||||
|             "integrity": "sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ==", |             "integrity": "sha512-n8/ggadrZ+uyrfrSEchx3jgODdmcx7MzVM2sI3cTpI/YlfSm0+9HEUaWw3aQn2urL2KYlWYMDgn45iLfjDYB+Q==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "@types/json-schema": "^7.0.7", |                 "@types/json-schema": "^7.0.7", | ||||||
|                 "@typescript-eslint/scope-manager": "4.28.0", |                 "@typescript-eslint/scope-manager": "4.28.1", | ||||||
|                 "@typescript-eslint/types": "4.28.0", |                 "@typescript-eslint/types": "4.28.1", | ||||||
|                 "@typescript-eslint/typescript-estree": "4.28.0", |                 "@typescript-eslint/typescript-estree": "4.28.1", | ||||||
|                 "eslint-scope": "^5.1.1", |                 "eslint-scope": "^5.1.1", | ||||||
|                 "eslint-utils": "^3.0.0" |                 "eslint-utils": "^3.0.0" | ||||||
|             }, |             }, | ||||||
| @ -9962,37 +9962,37 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "@typescript-eslint/parser": { |         "@typescript-eslint/parser": { | ||||||
|             "version": "4.28.0", |             "version": "4.28.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.0.tgz", |             "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.1.tgz", | ||||||
|             "integrity": "sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A==", |             "integrity": "sha512-UjrMsgnhQIIK82hXGaD+MCN8IfORS1CbMdu7VlZbYa8LCZtbZjJA26De4IPQB7XYZbL8gJ99KWNj0l6WD0guJg==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "@typescript-eslint/scope-manager": "4.28.0", |                 "@typescript-eslint/scope-manager": "4.28.1", | ||||||
|                 "@typescript-eslint/types": "4.28.0", |                 "@typescript-eslint/types": "4.28.1", | ||||||
|                 "@typescript-eslint/typescript-estree": "4.28.0", |                 "@typescript-eslint/typescript-estree": "4.28.1", | ||||||
|                 "debug": "^4.3.1" |                 "debug": "^4.3.1" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "@typescript-eslint/scope-manager": { |         "@typescript-eslint/scope-manager": { | ||||||
|             "version": "4.28.0", |             "version": "4.28.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz", |             "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.1.tgz", | ||||||
|             "integrity": "sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg==", |             "integrity": "sha512-o95bvGKfss6705x7jFGDyS7trAORTy57lwJ+VsYwil/lOUxKQ9tA7Suuq+ciMhJc/1qPwB3XE2DKh9wubW8YYA==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "@typescript-eslint/types": "4.28.0", |                 "@typescript-eslint/types": "4.28.1", | ||||||
|                 "@typescript-eslint/visitor-keys": "4.28.0" |                 "@typescript-eslint/visitor-keys": "4.28.1" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "@typescript-eslint/types": { |         "@typescript-eslint/types": { | ||||||
|             "version": "4.28.0", |             "version": "4.28.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.0.tgz", |             "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.1.tgz", | ||||||
|             "integrity": "sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA==" |             "integrity": "sha512-4z+knEihcyX7blAGi7O3Fm3O6YRCP+r56NJFMNGsmtdw+NCdpG5SgNz427LS9nQkRVTswZLhz484hakQwB8RRg==" | ||||||
|         }, |         }, | ||||||
|         "@typescript-eslint/typescript-estree": { |         "@typescript-eslint/typescript-estree": { | ||||||
|             "version": "4.28.0", |             "version": "4.28.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz", |             "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.1.tgz", | ||||||
|             "integrity": "sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ==", |             "integrity": "sha512-GhKxmC4sHXxHGJv8e8egAZeTZ6HI4mLU6S7FUzvFOtsk7ZIDN1ksA9r9DyOgNqowA9yAtZXV0Uiap61bIO81FQ==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "@typescript-eslint/types": "4.28.0", |                 "@typescript-eslint/types": "4.28.1", | ||||||
|                 "@typescript-eslint/visitor-keys": "4.28.0", |                 "@typescript-eslint/visitor-keys": "4.28.1", | ||||||
|                 "debug": "^4.3.1", |                 "debug": "^4.3.1", | ||||||
|                 "globby": "^11.0.3", |                 "globby": "^11.0.3", | ||||||
|                 "is-glob": "^4.0.1", |                 "is-glob": "^4.0.1", | ||||||
| @ -10016,11 +10016,11 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "@typescript-eslint/visitor-keys": { |         "@typescript-eslint/visitor-keys": { | ||||||
|             "version": "4.28.0", |             "version": "4.28.1", | ||||||
|             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz", |             "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.1.tgz", | ||||||
|             "integrity": "sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw==", |             "integrity": "sha512-K4HMrdFqr9PFquPu178SaSb92CaWe2yErXyPumc8cYWxFmhgJsNY9eSePmO05j0JhBvf2Cdhptd6E6Yv9HVHcg==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "@typescript-eslint/types": "4.28.0", |                 "@typescript-eslint/types": "4.28.1", | ||||||
|                 "eslint-visitor-keys": "^2.0.0" |                 "eslint-visitor-keys": "^2.0.0" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
| @ -10461,9 +10461,9 @@ | |||||||
|             "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" |             "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" | ||||||
|         }, |         }, | ||||||
|         "chart.js": { |         "chart.js": { | ||||||
|             "version": "3.3.2", |             "version": "3.4.0", | ||||||
|             "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.3.2.tgz", |             "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.4.0.tgz", | ||||||
|             "integrity": "sha512-H0hSO7xqTIrwxoACqnSoNromEMfXvfuVnrbuSt2TuXfBDDofbnto4zuZlRtRvC73/b37q3wGAWZyUU41QPvNbA==" |             "integrity": "sha512-mJsRm2apQm5mwz2OgYqGNG4erZh/qljcRZkWSa0kLkFr3UC3e1wKRMgnIh6WdhUrNu0w/JT9PkjLyylqEqHXEQ==" | ||||||
|         }, |         }, | ||||||
|         "chartjs-adapter-moment": { |         "chartjs-adapter-moment": { | ||||||
|             "version": "1.0.0", |             "version": "1.0.0", | ||||||
| @ -13200,9 +13200,9 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "rollup": { |         "rollup": { | ||||||
|             "version": "2.52.2", |             "version": "2.52.3", | ||||||
|             "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz", |             "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.3.tgz", | ||||||
|             "integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==", |             "integrity": "sha512-QF3Sju8Kl2z0osI4unyOLyUudyhOMK6G0AeqJWgfiyigqLAlnNrfBcDWDx+f1cqn+JU2iIYVkDrgQ6/KtwEfrg==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "fsevents": "~2.3.2" |                 "fsevents": "~2.3.2" | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -47,24 +47,24 @@ | |||||||
|         "@lingui/cli": "^3.10.2", |         "@lingui/cli": "^3.10.2", | ||||||
|         "@lingui/core": "^3.10.4", |         "@lingui/core": "^3.10.4", | ||||||
|         "@lingui/macro": "^3.10.2", |         "@lingui/macro": "^3.10.2", | ||||||
|         "@patternfly/patternfly": "^4.108.2", |         "@patternfly/patternfly": "^4.115.2", | ||||||
|         "@polymer/iron-form": "^3.0.1", |         "@polymer/iron-form": "^3.0.1", | ||||||
|         "@polymer/paper-input": "^3.2.1", |         "@polymer/paper-input": "^3.2.1", | ||||||
|         "@rollup/plugin-babel": "^5.3.0", |         "@rollup/plugin-babel": "^5.3.0", | ||||||
|         "@rollup/plugin-replace": "^2.4.2", |         "@rollup/plugin-replace": "^2.4.2", | ||||||
|         "@rollup/plugin-typescript": "^8.2.1", |         "@rollup/plugin-typescript": "^8.2.1", | ||||||
|         "@sentry/browser": "^6.7.2", |         "@sentry/browser": "^6.8.0", | ||||||
|         "@sentry/tracing": "^6.7.2", |         "@sentry/tracing": "^6.8.0", | ||||||
|         "@types/chart.js": "^2.9.32", |         "@types/chart.js": "^2.9.32", | ||||||
|         "@types/codemirror": "5.60.0", |         "@types/codemirror": "5.60.1", | ||||||
|         "@types/grecaptcha": "^3.0.2", |         "@types/grecaptcha": "^3.0.2", | ||||||
|         "@typescript-eslint/eslint-plugin": "^4.28.0", |         "@typescript-eslint/eslint-plugin": "^4.28.1", | ||||||
|         "@typescript-eslint/parser": "^4.28.0", |         "@typescript-eslint/parser": "^4.28.1", | ||||||
|         "@webcomponents/webcomponentsjs": "^2.5.0", |         "@webcomponents/webcomponentsjs": "^2.5.0", | ||||||
|         "authentik-api": "file:api", |         "authentik-api": "file:api", | ||||||
|         "babel-plugin-macros": "^3.1.0", |         "babel-plugin-macros": "^3.1.0", | ||||||
|         "base64-js": "^1.5.1", |         "base64-js": "^1.5.1", | ||||||
|         "chart.js": "^3.3.2", |         "chart.js": "^3.4.0", | ||||||
|         "chartjs-adapter-moment": "^1.0.0", |         "chartjs-adapter-moment": "^1.0.0", | ||||||
|         "codemirror": "^5.62.0", |         "codemirror": "^5.62.0", | ||||||
|         "construct-style-sheets-polyfill": "^2.4.16", |         "construct-style-sheets-polyfill": "^2.4.16", | ||||||
| @ -77,7 +77,7 @@ | |||||||
|         "lit-html": "^1.4.1", |         "lit-html": "^1.4.1", | ||||||
|         "moment": "^2.29.1", |         "moment": "^2.29.1", | ||||||
|         "rapidoc": "^9.0.0", |         "rapidoc": "^9.0.0", | ||||||
|         "rollup": "^2.52.2", |         "rollup": "^2.52.3", | ||||||
|         "rollup-plugin-commonjs": "^10.1.0", |         "rollup-plugin-commonjs": "^10.1.0", | ||||||
|         "rollup-plugin-copy": "^3.4.0", |         "rollup-plugin-copy": "^3.4.0", | ||||||
|         "rollup-plugin-cssimport": "^1.0.2", |         "rollup-plugin-cssimport": "^1.0.2", | ||||||
|  | |||||||
| @ -139,6 +139,7 @@ body { | |||||||
|     /* Card */ |     /* Card */ | ||||||
|     .pf-c-card { |     .pf-c-card { | ||||||
|         --pf-c-card--BackgroundColor: var(--ak-dark-background-light); |         --pf-c-card--BackgroundColor: var(--ak-dark-background-light); | ||||||
|  |         color: var(--ak-dark-foreground); | ||||||
|     } |     } | ||||||
|     .pf-c-card__title, |     .pf-c-card__title, | ||||||
|     .pf-c-card__body { |     .pf-c-card__body { | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success"; | |||||||
| export const ERROR_CLASS = "pf-m-danger"; | export const ERROR_CLASS = "pf-m-danger"; | ||||||
| export const PROGRESS_CLASS = "pf-m-in-progress"; | export const PROGRESS_CLASS = "pf-m-in-progress"; | ||||||
| export const CURRENT_CLASS = "pf-m-current"; | export const CURRENT_CLASS = "pf-m-current"; | ||||||
| export const VERSION = "2021.6.2"; | export const VERSION = "2021.6.3"; | ||||||
| export const PAGE_SIZE = 20; | export const PAGE_SIZE = 20; | ||||||
| export const EVENT_REFRESH = "ak-refresh"; | export const EVENT_REFRESH = "ak-refresh"; | ||||||
| export const EVENT_NOTIFICATION_TOGGLE = "ak-notification-toggle"; | export const EVENT_NOTIFICATION_TOGGLE = "ak-notification-toggle"; | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| import { t } from "@lingui/macro"; | import { t } from "@lingui/macro"; | ||||||
| import { PlexAuthenticationChallenge } from "authentik-api"; | import { PlexAuthenticationChallenge, PlexAuthenticationChallengeResponseRequest } from "authentik-api"; | ||||||
| import PFLogin from "@patternfly/patternfly/components/Login/login.css"; | import PFLogin from "@patternfly/patternfly/components/Login/login.css"; | ||||||
| import PFForm from "@patternfly/patternfly/components/Form/form.css"; | import PFForm from "@patternfly/patternfly/components/Form/form.css"; | ||||||
| import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; | import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; | ||||||
| @ -15,7 +15,6 @@ import { DEFAULT_CONFIG } from "../../../api/Config"; | |||||||
| import { SourcesApi } from "authentik-api"; | import { SourcesApi } from "authentik-api"; | ||||||
| import { showMessage } from "../../../elements/messages/MessageContainer"; | import { showMessage } from "../../../elements/messages/MessageContainer"; | ||||||
| import { MessageLevel } from "../../../elements/messages/Message"; | import { MessageLevel } from "../../../elements/messages/Message"; | ||||||
| import { PlexAuthenticationChallengeResponseRequest } from "authentik-api/dist/models/PlexAuthenticationChallengeResponseRequest"; |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @customElement("ak-flow-sources-plex") | @customElement("ak-flow-sources-plex") | ||||||
|  | |||||||
| @ -11,9 +11,8 @@ import { BaseStage } from "../base"; | |||||||
| import "../../../elements/forms/FormElement"; | import "../../../elements/forms/FormElement"; | ||||||
| import "../../../elements/EmptyState"; | import "../../../elements/EmptyState"; | ||||||
| import "../../FormStatic"; | import "../../FormStatic"; | ||||||
| import { AuthenticatorDuoChallenge, StagesApi } from "authentik-api"; | import { AuthenticatorDuoChallenge, AuthenticatorDuoChallengeResponseRequest, StagesApi } from "authentik-api"; | ||||||
| import { DEFAULT_CONFIG } from "../../../api/Config"; | import { DEFAULT_CONFIG } from "../../../api/Config"; | ||||||
| import { AuthenticatorDuoChallengeResponseRequest } from "authentik-api/dist/models/AuthenticatorDuoChallengeResponseRequest"; |  | ||||||
| import { ifDefined } from "lit-html/directives/if-defined"; | import { ifDefined } from "lit-html/directives/if-defined"; | ||||||
|  |  | ||||||
| @customElement("ak-stage-authenticator-duo") | @customElement("ak-stage-authenticator-duo") | ||||||
|  | |||||||
| @ -11,8 +11,7 @@ import { BaseStage } from "../base"; | |||||||
| import "../../../elements/forms/FormElement"; | import "../../../elements/forms/FormElement"; | ||||||
| import "../../../elements/EmptyState"; | import "../../../elements/EmptyState"; | ||||||
| import "../../FormStatic"; | import "../../FormStatic"; | ||||||
| import { AuthenticatorStaticChallenge } from "authentik-api"; | import { AuthenticatorStaticChallenge, AuthenticatorStaticChallengeResponseRequest } from "authentik-api"; | ||||||
| import { AuthenticatorStaticChallengeResponseRequest } from "authentik-api/dist/models/AuthenticatorStaticChallengeResponseRequest"; |  | ||||||
| import { ifDefined } from "lit-html/directives/if-defined"; | import { ifDefined } from "lit-html/directives/if-defined"; | ||||||
|  |  | ||||||
| export const STATIC_TOKEN_STYLE = css` | export const STATIC_TOKEN_STYLE = css` | ||||||
|  | |||||||
| @ -12,8 +12,7 @@ import "./AuthenticatorValidateStageWebAuthn"; | |||||||
| import "./AuthenticatorValidateStageCode"; | import "./AuthenticatorValidateStageCode"; | ||||||
| import "./AuthenticatorValidateStageDuo"; | import "./AuthenticatorValidateStageDuo"; | ||||||
| import { PasswordManagerPrefill } from "../identification/IdentificationStage"; | import { PasswordManagerPrefill } from "../identification/IdentificationStage"; | ||||||
| import { AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; | import { AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; | ||||||
| import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge"; |  | ||||||
|  |  | ||||||
| export enum DeviceClasses { | export enum DeviceClasses { | ||||||
|     STATIC = "static", |     STATIC = "static", | ||||||
|  | |||||||
| @ -13,8 +13,7 @@ import "../../../elements/forms/FormElement"; | |||||||
| import "../../../elements/EmptyState"; | import "../../../elements/EmptyState"; | ||||||
| import { PasswordManagerPrefill } from "../identification/IdentificationStage"; | import { PasswordManagerPrefill } from "../identification/IdentificationStage"; | ||||||
| import "../../FormStatic"; | import "../../FormStatic"; | ||||||
| import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge"; | import { AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; | ||||||
| import { AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; |  | ||||||
| import { ifDefined } from "lit-html/directives/if-defined"; | import { ifDefined } from "lit-html/directives/if-defined"; | ||||||
|  |  | ||||||
| @customElement("ak-stage-authenticator-validate-code") | @customElement("ak-stage-authenticator-validate-code") | ||||||
|  | |||||||
| @ -12,8 +12,7 @@ import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage"; | |||||||
| import "../../../elements/forms/FormElement"; | import "../../../elements/forms/FormElement"; | ||||||
| import "../../../elements/EmptyState"; | import "../../../elements/EmptyState"; | ||||||
| import "../../FormStatic"; | import "../../FormStatic"; | ||||||
| import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge"; | import { AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; | ||||||
| import { AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; |  | ||||||
| import { ifDefined } from "lit-html/directives/if-defined"; | import { ifDefined } from "lit-html/directives/if-defined"; | ||||||
|  |  | ||||||
| @customElement("ak-stage-authenticator-validate-duo") | @customElement("ak-stage-authenticator-validate-duo") | ||||||
|  | |||||||
| @ -11,8 +11,7 @@ import { PFSize } from "../../../elements/Spinner"; | |||||||
| import { transformAssertionForServer, transformCredentialRequestOptions } from "../authenticator_webauthn/utils"; | import { transformAssertionForServer, transformCredentialRequestOptions } from "../authenticator_webauthn/utils"; | ||||||
| import { BaseStage } from "../base"; | import { BaseStage } from "../base"; | ||||||
| import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage"; | import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage"; | ||||||
| import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge"; | import { AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; | ||||||
| import { AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; |  | ||||||
|  |  | ||||||
| @customElement("ak-stage-authenticator-validate-webauthn") | @customElement("ak-stage-authenticator-validate-webauthn") | ||||||
| export class AuthenticatorValidateStageWebAuthn extends BaseStage<AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest> { | export class AuthenticatorValidateStageWebAuthn extends BaseStage<AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest> { | ||||||
|  | |||||||
| @ -9,8 +9,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; | |||||||
| import AKGlobal from "../../../authentik.css"; | import AKGlobal from "../../../authentik.css"; | ||||||
| import { BaseStage } from "../base"; | import { BaseStage } from "../base"; | ||||||
| import "../../../elements/EmptyState"; | import "../../../elements/EmptyState"; | ||||||
| import { AutosubmitChallenge } from "authentik-api"; | import { AutosubmitChallenge, AutoSubmitChallengeResponseRequest } from "authentik-api"; | ||||||
| import { AutoSubmitChallengeResponseRequest } from "authentik-api/dist/models/AutoSubmitChallengeResponseRequest"; |  | ||||||
|  |  | ||||||
| @customElement("ak-stage-autosubmit") | @customElement("ak-stage-autosubmit") | ||||||
| export class AutosubmitStage extends BaseStage<AutosubmitChallenge, AutoSubmitChallengeResponseRequest> { | export class AutosubmitStage extends BaseStage<AutosubmitChallenge, AutoSubmitChallengeResponseRequest> { | ||||||
|  | |||||||
| @ -698,6 +698,10 @@ msgstr "Configure how long refresh tokens and their id_tokens are valid for." | |||||||
| msgid "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected." | msgid "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected." | ||||||
| msgstr "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected." | msgstr "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected." | ||||||
|  |  | ||||||
|  | #: src/pages/flows/StageBindingForm.ts | ||||||
|  | msgid "Configure how the flow executor should handle an invalid response to a challenge." | ||||||
|  | msgstr "Configure how the flow executor should handle an invalid response to a challenge." | ||||||
|  |  | ||||||
| #: src/pages/providers/oauth2/OAuth2ProviderForm.ts | #: src/pages/providers/oauth2/OAuth2ProviderForm.ts | ||||||
| msgid "Configure how the issuer field of the ID Token should be filled." | msgid "Configure how the issuer field of the ID Token should be filled." | ||||||
| msgstr "Configure how the issuer field of the ID Token should be filled." | msgstr "Configure how the issuer field of the ID Token should be filled." | ||||||
| @ -941,6 +945,10 @@ msgstr "Create User" | |||||||
| msgid "Create provider" | msgid "Create provider" | ||||||
| msgstr "Create provider" | msgstr "Create provider" | ||||||
|  |  | ||||||
|  | #: src/pages/stages/user_write/UserWriteStageForm.ts | ||||||
|  | msgid "Create users as inactive" | ||||||
|  | msgstr "Create users as inactive" | ||||||
|  |  | ||||||
| #: src/pages/applications/ApplicationForm.ts | #: src/pages/applications/ApplicationForm.ts | ||||||
| #: src/pages/flows/BoundStagesList.ts | #: src/pages/flows/BoundStagesList.ts | ||||||
| #: src/pages/outposts/ServiceConnectionListPage.ts | #: src/pages/outposts/ServiceConnectionListPage.ts | ||||||
| @ -1372,8 +1380,8 @@ msgid "Evaluate policies before the Stage is present to the user." | |||||||
| msgstr "Evaluate policies before the Stage is present to the user." | msgstr "Evaluate policies before the Stage is present to the user." | ||||||
|  |  | ||||||
| #: src/pages/flows/StageBindingForm.ts | #: src/pages/flows/StageBindingForm.ts | ||||||
| msgid "Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with this option disabled, policies are **not** evaluated." | msgid "Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with both options disabled, policies are **not** evaluated." | ||||||
| msgstr "Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with this option disabled, policies are **not** evaluated." | msgstr "Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with both options disabled, policies are **not** evaluated." | ||||||
|  |  | ||||||
| #: src/pages/events/EventListPage.ts | #: src/pages/events/EventListPage.ts | ||||||
| msgid "Event Log" | msgid "Event Log" | ||||||
| @ -1451,9 +1459,14 @@ msgid "Explicit Consent" | |||||||
| msgstr "Explicit Consent" | msgstr "Explicit Consent" | ||||||
|  |  | ||||||
| #: src/pages/flows/FlowListPage.ts | #: src/pages/flows/FlowListPage.ts | ||||||
|  | #: src/pages/flows/FlowViewPage.ts | ||||||
| msgid "Export" | msgid "Export" | ||||||
| msgstr "Export" | msgstr "Export" | ||||||
|  |  | ||||||
|  | #: src/pages/flows/FlowViewPage.ts | ||||||
|  | msgid "Export flow" | ||||||
|  | msgstr "Export flow" | ||||||
|  |  | ||||||
| #: src/pages/events/EventInfo.ts | #: src/pages/events/EventInfo.ts | ||||||
| #: src/pages/policies/expression/ExpressionPolicyForm.ts | #: src/pages/policies/expression/ExpressionPolicyForm.ts | ||||||
| #: src/pages/property-mappings/PropertyMappingLDAPForm.ts | #: src/pages/property-mappings/PropertyMappingLDAPForm.ts | ||||||
| @ -1876,6 +1889,10 @@ msgstr "Internal host" | |||||||
| msgid "Internal host SSL Validation" | msgid "Internal host SSL Validation" | ||||||
| msgstr "Internal host SSL Validation" | msgstr "Internal host SSL Validation" | ||||||
|  |  | ||||||
|  | #: src/pages/flows/StageBindingForm.ts | ||||||
|  | msgid "Invalid response action" | ||||||
|  | msgstr "Invalid response action" | ||||||
|  |  | ||||||
| #: src/pages/flows/FlowForm.ts | #: src/pages/flows/FlowForm.ts | ||||||
| msgid "Invalidation" | msgid "Invalidation" | ||||||
| msgstr "Invalidation" | msgstr "Invalidation" | ||||||
| @ -2138,6 +2155,10 @@ msgstr "Logs" | |||||||
| msgid "Long-running operations which authentik executes in the background." | msgid "Long-running operations which authentik executes in the background." | ||||||
| msgstr "Long-running operations which authentik executes in the background." | msgstr "Long-running operations which authentik executes in the background." | ||||||
|  |  | ||||||
|  | #: src/pages/stages/user_write/UserWriteStageForm.ts | ||||||
|  | msgid "Mark newly created users as inactive." | ||||||
|  | msgstr "Mark newly created users as inactive." | ||||||
|  |  | ||||||
| #: src/pages/policies/event_matcher/EventMatcherPolicyForm.ts | #: src/pages/policies/event_matcher/EventMatcherPolicyForm.ts | ||||||
| msgid "Match created events with this action type. When left empty, all action types will be matched." | msgid "Match created events with this action type. When left empty, all action types will be matched." | ||||||
| msgstr "Match created events with this action type. When left empty, all action types will be matched." | msgstr "Match created events with this action type. When left empty, all action types will be matched." | ||||||
| @ -2842,6 +2863,18 @@ msgstr "Public key, acquired from https://www.google.com/recaptcha/intro/v3.html | |||||||
| msgid "Publisher" | msgid "Publisher" | ||||||
| msgstr "Publisher" | msgstr "Publisher" | ||||||
|  |  | ||||||
|  | #: src/pages/flows/StageBindingForm.ts | ||||||
|  | msgid "RESTART restarts the flow from the beginning, while keeping the flow context." | ||||||
|  | msgstr "RESTART restarts the flow from the beginning, while keeping the flow context." | ||||||
|  |  | ||||||
|  | #: src/pages/flows/StageBindingForm.ts | ||||||
|  | msgid "RESTART restarts the flow from the beginning." | ||||||
|  | msgstr "RESTART restarts the flow from the beginning." | ||||||
|  |  | ||||||
|  | #: src/pages/flows/StageBindingForm.ts | ||||||
|  | msgid "RETRY returns the error message and a similar challenge to the executor." | ||||||
|  | msgstr "RETRY returns the error message and a similar challenge to the executor." | ||||||
|  |  | ||||||
| #: src/pages/providers/oauth2/OAuth2ProviderForm.ts | #: src/pages/providers/oauth2/OAuth2ProviderForm.ts | ||||||
| msgid "RS256 (Asymmetric Encryption)" | msgid "RS256 (Asymmetric Encryption)" | ||||||
| msgstr "RS256 (Asymmetric Encryption)" | msgstr "RS256 (Asymmetric Encryption)" | ||||||
| @ -3359,6 +3392,7 @@ msgstr "Stage used to validate any authenticator. This stage should be used duri | |||||||
| #: src/pages/stages/password/PasswordStageForm.ts | #: src/pages/stages/password/PasswordStageForm.ts | ||||||
| #: src/pages/stages/prompt/PromptStageForm.ts | #: src/pages/stages/prompt/PromptStageForm.ts | ||||||
| #: src/pages/stages/user_login/UserLoginStageForm.ts | #: src/pages/stages/user_login/UserLoginStageForm.ts | ||||||
|  | #: src/pages/stages/user_write/UserWriteStageForm.ts | ||||||
| msgid "Stage-specific settings" | msgid "Stage-specific settings" | ||||||
| msgstr "Stage-specific settings" | msgstr "Stage-specific settings" | ||||||
|  |  | ||||||
| @ -3816,6 +3850,16 @@ msgstr "The external URL you'll authenticate at. Can be the same domain as authe | |||||||
| msgid "The following objects use {objName}" | msgid "The following objects use {objName}" | ||||||
| msgstr "The following objects use {objName}" | msgstr "The following objects use {objName}" | ||||||
|  |  | ||||||
|  | #: src/pages/policies/reputation/ReputationPolicyForm.ts | ||||||
|  | msgid "" | ||||||
|  | "The policy passes when the reputation score is above the threshold, and\n" | ||||||
|  | "doesn't pass when either or both of the selected options are equal or less than the\n" | ||||||
|  | "threshold." | ||||||
|  | msgstr "" | ||||||
|  | "The policy passes when the reputation score is above the threshold, and\n" | ||||||
|  | "doesn't pass when either or both of the selected options are equal or less than the\n" | ||||||
|  | "threshold." | ||||||
|  |  | ||||||
| #: src/pages/policies/dummy/DummyPolicyForm.ts | #: src/pages/policies/dummy/DummyPolicyForm.ts | ||||||
| msgid "The policy takes a random time to execute. This controls the minimum time it will take." | msgid "The policy takes a random time to execute. This controls the minimum time it will take." | ||||||
| msgstr "The policy takes a random time to execute. This controls the minimum time it will take." | msgstr "The policy takes a random time to execute. This controls the minimum time it will take." | ||||||
|  | |||||||
| @ -692,6 +692,10 @@ msgstr "" | |||||||
| msgid "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected." | msgid "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #:  | ||||||
|  | msgid "Configure how the flow executor should handle an invalid response to a challenge." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #:  | #:  | ||||||
| msgid "Configure how the issuer field of the ID Token should be filled." | msgid "Configure how the issuer field of the ID Token should be filled." | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -935,6 +939,10 @@ msgstr "" | |||||||
| msgid "Create provider" | msgid "Create provider" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #:  | ||||||
|  | msgid "Create users as inactive" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #:  | #:  | ||||||
| #:  | #:  | ||||||
| #:  | #:  | ||||||
| @ -1364,7 +1372,7 @@ msgid "Evaluate policies before the Stage is present to the user." | |||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #:  | #:  | ||||||
| msgid "Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with this option disabled, policies are **not** evaluated." | msgid "Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with both options disabled, policies are **not** evaluated." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #:  | #:  | ||||||
| @ -1442,10 +1450,15 @@ msgstr "" | |||||||
| msgid "Explicit Consent" | msgid "Explicit Consent" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #:  | ||||||
| #:  | #:  | ||||||
| msgid "Export" | msgid "Export" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #:  | ||||||
|  | msgid "Export flow" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #:  | #:  | ||||||
| #:  | #:  | ||||||
| #:  | #:  | ||||||
| @ -1868,6 +1881,10 @@ msgstr "" | |||||||
| msgid "Internal host SSL Validation" | msgid "Internal host SSL Validation" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #:  | ||||||
|  | msgid "Invalid response action" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #:  | #:  | ||||||
| msgid "Invalidation" | msgid "Invalidation" | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -2130,6 +2147,10 @@ msgstr "" | |||||||
| msgid "Long-running operations which authentik executes in the background." | msgid "Long-running operations which authentik executes in the background." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #:  | ||||||
|  | msgid "Mark newly created users as inactive." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #:  | #:  | ||||||
| msgid "Match created events with this action type. When left empty, all action types will be matched." | msgid "Match created events with this action type. When left empty, all action types will be matched." | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -2834,6 +2855,18 @@ msgstr "" | |||||||
| msgid "Publisher" | msgid "Publisher" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #:  | ||||||
|  | msgid "RESTART restarts the flow from the beginning, while keeping the flow context." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #:  | ||||||
|  | msgid "RESTART restarts the flow from the beginning." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #:  | ||||||
|  | msgid "RETRY returns the error message and a similar challenge to the executor." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #:  | #:  | ||||||
| msgid "RS256 (Asymmetric Encryption)" | msgid "RS256 (Asymmetric Encryption)" | ||||||
| msgstr "" | msgstr "" | ||||||
| @ -3351,6 +3384,7 @@ msgstr "" | |||||||
| #:  | #:  | ||||||
| #:  | #:  | ||||||
| #:  | #:  | ||||||
|  | #:  | ||||||
| msgid "Stage-specific settings" | msgid "Stage-specific settings" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @ -3808,6 +3842,13 @@ msgstr "" | |||||||
| msgid "The following objects use {objName}" | msgid "The following objects use {objName}" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #:  | ||||||
|  | msgid "" | ||||||
|  | "The policy passes when the reputation score is above the threshold, and\n" | ||||||
|  | "doesn't pass when either or both of the selected options are equal or less than the\n" | ||||||
|  | "threshold." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #:  | #:  | ||||||
| msgid "The policy takes a random time to execute. This controls the minimum time it will take." | msgid "The policy takes a random time to execute. This controls the minimum time it will take." | ||||||
| msgstr "" | msgstr "" | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import { t } from "@lingui/macro"; | import { t } from "@lingui/macro"; | ||||||
| import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | ||||||
| import { until } from "lit-html/directives/until"; | import { until } from "lit-html/directives/until"; | ||||||
| import { EventMatcherPolicyActionEnum, FlowsApi } from "authentik-api"; | import { EventActions, FlowsApi } from "authentik-api"; | ||||||
| import "../../elements/Spinner"; | import "../../elements/Spinner"; | ||||||
| import "../../elements/Expand"; | import "../../elements/Expand"; | ||||||
| import { PFSize } from "../../elements/Spinner"; | import { PFSize } from "../../elements/Spinner"; | ||||||
| @ -189,14 +189,14 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)} | |||||||
|             return html`<ak-spinner size=${PFSize.Medium}></ak-spinner>`; |             return html`<ak-spinner size=${PFSize.Medium}></ak-spinner>`; | ||||||
|         } |         } | ||||||
|         switch (this.event?.action) { |         switch (this.event?.action) { | ||||||
|         case EventMatcherPolicyActionEnum.ModelCreated: |         case EventActions.ModelCreated: | ||||||
|         case EventMatcherPolicyActionEnum.ModelUpdated: |         case EventActions.ModelUpdated: | ||||||
|         case EventMatcherPolicyActionEnum.ModelDeleted: |         case EventActions.ModelDeleted: | ||||||
|             return html` |             return html` | ||||||
|                 <h3>${t`Affected model:`}</h3> |                 <h3>${t`Affected model:`}</h3> | ||||||
|                 ${this.getModelInfo(this.event.context?.model as EventModel)} |                 ${this.getModelInfo(this.event.context?.model as EventModel)} | ||||||
|                 `; |                 `; | ||||||
|         case EventMatcherPolicyActionEnum.AuthorizeApplication: |         case EventActions.AuthorizeApplication: | ||||||
|             return html`<div class="pf-l-flex"> |             return html`<div class="pf-l-flex"> | ||||||
|                     <div class="pf-l-flex__item"> |                     <div class="pf-l-flex__item"> | ||||||
|                         <h3>${t`Authorized application:`}</h3> |                         <h3>${t`Authorized application:`}</h3> | ||||||
| @ -213,17 +213,17 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)} | |||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <ak-expand>${this.defaultResponse()}</ak-expand>`; |                 <ak-expand>${this.defaultResponse()}</ak-expand>`; | ||||||
|         case EventMatcherPolicyActionEnum.EmailSent: |         case EventActions.EmailSent: | ||||||
|             return html`<h3>${t`Email info:`}</h3> |             return html`<h3>${t`Email info:`}</h3> | ||||||
|                 ${this.getEmailInfo(this.event.context)} |                 ${this.getEmailInfo(this.event.context)} | ||||||
|                 <ak-expand> |                 <ak-expand> | ||||||
|                     <iframe srcdoc=${this.event.context.body}></iframe> |                     <iframe srcdoc=${this.event.context.body}></iframe> | ||||||
|                 </ak-expand>`; |                 </ak-expand>`; | ||||||
|         case EventMatcherPolicyActionEnum.SecretView: |         case EventActions.SecretView: | ||||||
|             return html` |             return html` | ||||||
|                 <h3>${t`Secret:`}</h3> |                 <h3>${t`Secret:`}</h3> | ||||||
|                 ${this.getModelInfo(this.event.context.secret as EventModel)}`; |                 ${this.getModelInfo(this.event.context.secret as EventModel)}`; | ||||||
|         case EventMatcherPolicyActionEnum.SystemException: |         case EventActions.SystemException: | ||||||
|             return html` |             return html` | ||||||
|                 <a |                 <a | ||||||
|                     class="pf-c-button pf-m-primary" |                     class="pf-c-button pf-m-primary" | ||||||
| @ -240,7 +240,7 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)} | |||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <ak-expand>${this.defaultResponse()}</ak-expand>`; |                 <ak-expand>${this.defaultResponse()}</ak-expand>`; | ||||||
|         case EventMatcherPolicyActionEnum.PropertyMappingException: |         case EventActions.PropertyMappingException: | ||||||
|             return html`<div class="pf-l-flex"> |             return html`<div class="pf-l-flex"> | ||||||
|                     <div class="pf-l-flex__item"> |                     <div class="pf-l-flex__item"> | ||||||
|                         <h3>${t`Exception`}</h3> |                         <h3>${t`Exception`}</h3> | ||||||
| @ -252,7 +252,7 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)} | |||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <ak-expand>${this.defaultResponse()}</ak-expand>`; |                 <ak-expand>${this.defaultResponse()}</ak-expand>`; | ||||||
|         case EventMatcherPolicyActionEnum.PolicyException: |         case EventActions.PolicyException: | ||||||
|             return html`<div class="pf-l-flex"> |             return html`<div class="pf-l-flex"> | ||||||
|                     <div class="pf-l-flex__item"> |                     <div class="pf-l-flex__item"> | ||||||
|                         <h3>${t`Binding`}</h3> |                         <h3>${t`Binding`}</h3> | ||||||
| @ -271,7 +271,7 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)} | |||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <ak-expand>${this.defaultResponse()}</ak-expand>`; |                 <ak-expand>${this.defaultResponse()}</ak-expand>`; | ||||||
|         case EventMatcherPolicyActionEnum.PolicyExecution: |         case EventActions.PolicyExecution: | ||||||
|             return html`<div class="pf-l-flex"> |             return html`<div class="pf-l-flex"> | ||||||
|                     <div class="pf-l-flex__item"> |                     <div class="pf-l-flex__item"> | ||||||
|                         <h3>${t`Binding`}</h3> |                         <h3>${t`Binding`}</h3> | ||||||
| @ -299,10 +299,10 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)} | |||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <ak-expand>${this.defaultResponse()}</ak-expand>`; |                 <ak-expand>${this.defaultResponse()}</ak-expand>`; | ||||||
|         case EventMatcherPolicyActionEnum.ConfigurationError: |         case EventActions.ConfigurationError: | ||||||
|             return html`<h3>${this.event.context.message}</h3> |             return html`<h3>${this.event.context.message}</h3> | ||||||
|                 <ak-expand>${this.defaultResponse()}</ak-expand>`; |                 <ak-expand>${this.defaultResponse()}</ak-expand>`; | ||||||
|         case EventMatcherPolicyActionEnum.UpdateAvailable: |         case EventActions.UpdateAvailable: | ||||||
|             return html`<h3>${t`New version available!`}</h3> |             return html`<h3>${t`New version available!`}</h3> | ||||||
|                 <a |                 <a | ||||||
|                     target="_blank" |                     target="_blank" | ||||||
| @ -311,7 +311,7 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)} | |||||||
|                 </a>`; |                 </a>`; | ||||||
|         // Action types which typically don't record any extra context. |         // Action types which typically don't record any extra context. | ||||||
|         // If context is not empty, we fall to the default response. |         // If context is not empty, we fall to the default response. | ||||||
|         case EventMatcherPolicyActionEnum.Login: |         case EventActions.Login: | ||||||
|             if ("using_source" in this.event.context) { |             if ("using_source" in this.event.context) { | ||||||
|                 return html`<div class="pf-l-flex"> |                 return html`<div class="pf-l-flex"> | ||||||
|                     <div class="pf-l-flex__item"> |                     <div class="pf-l-flex__item"> | ||||||
| @ -321,11 +321,11 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)} | |||||||
|                 </div>`; |                 </div>`; | ||||||
|             } |             } | ||||||
|             return this.defaultResponse(); |             return this.defaultResponse(); | ||||||
|         case EventMatcherPolicyActionEnum.LoginFailed: |         case EventActions.LoginFailed: | ||||||
|             return html` |             return html` | ||||||
|                 <h3>${t`Attempted to log in as ${this.event.context.username}`}</h3> |                 <h3>${t`Attempted to log in as ${this.event.context.username}`}</h3> | ||||||
|                 <ak-expand>${this.defaultResponse()}</ak-expand>`; |                 <ak-expand>${this.defaultResponse()}</ak-expand>`; | ||||||
|         case EventMatcherPolicyActionEnum.Logout: |         case EventActions.Logout: | ||||||
|             if (this.event.context === {}) { |             if (this.event.context === {}) { | ||||||
|                 return html`<span>${t`No additional data available.`}</span>`; |                 return html`<span>${t`No additional data available.`}</span>`; | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| import { t } from "@lingui/macro"; | import { t } from "@lingui/macro"; | ||||||
| import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | ||||||
| import { EventsApi } from "authentik-api"; | import { EventsApi } from "authentik-api"; | ||||||
| import { DEFAULT_CONFIG } from "../../api/Config"; | import { DEFAULT_CONFIG } from "../../api/Config"; | ||||||
| import { EventWithContext } from "../../api/Events"; | import { EventWithContext } from "../../api/Events"; | ||||||
| @ -27,11 +27,7 @@ export class EventInfoPage extends LitElement { | |||||||
|     event!: EventWithContext; |     event!: EventWithContext; | ||||||
|  |  | ||||||
|     static get styles(): CSSResult[] { |     static get styles(): CSSResult[] { | ||||||
|         return [PFBase, PFPage, PFContent, PFCard, AKGlobal].concat(css` |         return [PFBase, PFPage, PFContent, PFCard, AKGlobal]; | ||||||
|             .pf-c-card { |  | ||||||
|                 color: var(--ak-dark-foreground); |  | ||||||
|             } |  | ||||||
|         `); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     render(): TemplateResult { |     render(): TemplateResult { | ||||||
|  | |||||||
| @ -51,9 +51,9 @@ export class FlowViewPage extends LitElement { | |||||||
|             return html``; |             return html``; | ||||||
|         } |         } | ||||||
|         return html`<ak-page-header |         return html`<ak-page-header | ||||||
|             icon="pf-icon pf-icon-process-automation" |                 icon="pf-icon pf-icon-process-automation" | ||||||
|             header=${this.flow.name} |                 header=${this.flow.name} | ||||||
|             description=${this.flow.title}> |                 description=${this.flow.title}> | ||||||
|             </ak-page-header> |             </ak-page-header> | ||||||
|             <ak-tabs> |             <ak-tabs> | ||||||
|                 <div slot="page-overview" data-tab-title="${t`Flow Overview`}" class="pf-c-page__main-section pf-m-no-padding-mobile"> |                 <div slot="page-overview" data-tab-title="${t`Flow Overview`}" class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
| @ -69,7 +69,7 @@ export class FlowViewPage extends LitElement { | |||||||
|                                         <dd class="pf-c-description-list__description"> |                                         <dd class="pf-c-description-list__description"> | ||||||
|                                             <div class="pf-c-description-list__text"> |                                             <div class="pf-c-description-list__text"> | ||||||
|                                                 <button |                                                 <button | ||||||
|                                                     class="pf-c-button pf-m-secondary" |                                                     class="pf-c-button pf-m-primary" | ||||||
|                                                     @click=${() => { |                                                     @click=${() => { | ||||||
|                                                     new FlowsApi(DEFAULT_CONFIG).flowsInstancesExecuteRetrieve({ |                                                     new FlowsApi(DEFAULT_CONFIG).flowsInstancesExecuteRetrieve({ | ||||||
|                                                         slug: this.flow.slug |                                                         slug: this.flow.slug | ||||||
| @ -82,6 +82,16 @@ export class FlowViewPage extends LitElement { | |||||||
|                                                 </button> |                                                 </button> | ||||||
|                                             </div> |                                             </div> | ||||||
|                                         </dd> |                                         </dd> | ||||||
|  |                                         <dt class="pf-c-description-list__term"> | ||||||
|  |                                             <span class="pf-c-description-list__text">${t`Export flow`}</span> | ||||||
|  |                                         </dt> | ||||||
|  |                                         <dd class="pf-c-description-list__description"> | ||||||
|  |                                             <div class="pf-c-description-list__text"> | ||||||
|  |                                                 <a class="pf-c-button pf-m-secondary" href=${this.flow.exportUrl}> | ||||||
|  |                                                     ${t`Export`} | ||||||
|  |                                                 </a> | ||||||
|  |                                             </div> | ||||||
|  |                                         </dd> | ||||||
|                                     </div> |                                     </div> | ||||||
|                                 </dl> |                                 </dl> | ||||||
|                             </div> |                             </div> | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { FlowsApi, FlowStageBinding, PolicyEngineMode, Stage, StagesApi } from "authentik-api"; | import { FlowsApi, FlowStageBinding, InvalidResponseActionEnum, PolicyEngineMode, Stage, StagesApi } from "authentik-api"; | ||||||
| import { t } from "@lingui/macro"; | import { t } from "@lingui/macro"; | ||||||
| import { customElement, property } from "lit-element"; | import { customElement, property } from "lit-element"; | ||||||
| import { html, TemplateResult } from "lit-html"; | import { html, TemplateResult } from "lit-html"; | ||||||
| @ -123,7 +123,7 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> { | |||||||
|                     </label> |                     </label> | ||||||
|                 </div> |                 </div> | ||||||
|                 <p class="pf-c-form__helper-text"> |                 <p class="pf-c-form__helper-text"> | ||||||
|                     ${t`Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with this option disabled, policies are **not** evaluated.`} |                     ${t`Evaluate policies during the Flow planning process. Disable this for input-based policies. Should be used in conjunction with 'Re-evaluate policies', as with both options disabled, policies are **not** evaluated.`} | ||||||
|                 </p> |                 </p> | ||||||
|             </ak-form-element-horizontal> |             </ak-form-element-horizontal> | ||||||
|             <ak-form-element-horizontal name="reEvaluatePolicies"> |             <ak-form-element-horizontal name="reEvaluatePolicies"> | ||||||
| @ -135,6 +135,23 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> { | |||||||
|                 </div> |                 </div> | ||||||
|                 <p class="pf-c-form__helper-text">${t`Evaluate policies before the Stage is present to the user.`}</p> |                 <p class="pf-c-form__helper-text">${t`Evaluate policies before the Stage is present to the user.`}</p> | ||||||
|             </ak-form-element-horizontal> |             </ak-form-element-horizontal> | ||||||
|  |             <ak-form-element-horizontal | ||||||
|  |                 label=${t`Invalid response action`} | ||||||
|  |                 ?required=${true} | ||||||
|  |                 name="invalidResponseAction"> | ||||||
|  |                 <select class="pf-c-form-control"> | ||||||
|  |                     <option value=${InvalidResponseActionEnum.Retry} ?selected=${this.instance?.invalidResponseAction === InvalidResponseActionEnum.Retry}> | ||||||
|  |                         ${t`RETRY returns the error message and a similar challenge to the executor.`} | ||||||
|  |                     </option> | ||||||
|  |                     <option value=${InvalidResponseActionEnum.Restart} ?selected=${this.instance?.invalidResponseAction === InvalidResponseActionEnum.Restart}> | ||||||
|  |                         ${t`RESTART restarts the flow from the beginning.`} | ||||||
|  |                     </option> | ||||||
|  |                     <option value=${InvalidResponseActionEnum.RestartWithContext} ?selected=${this.instance?.invalidResponseAction === InvalidResponseActionEnum.RestartWithContext}> | ||||||
|  |                         ${t`RESTART restarts the flow from the beginning, while keeping the flow context.`} | ||||||
|  |                     </option> | ||||||
|  |                 </select> | ||||||
|  |                 <p class="pf-c-form__helper-text">${t`Configure how the flow executor should handle an invalid response to a challenge.`}</p> | ||||||
|  |             </ak-form-element-horizontal> | ||||||
|             <ak-form-element-horizontal |             <ak-form-element-horizontal | ||||||
|                 label=${t`Policy engine mode`} |                 label=${t`Policy engine mode`} | ||||||
|                 ?required=${true} |                 ?required=${true} | ||||||
|  | |||||||
| @ -44,6 +44,11 @@ export class ReputationPolicyForm extends ModelForm<ReputationPolicy, string> { | |||||||
|             <div class="form-help-text"> |             <div class="form-help-text"> | ||||||
|                 ${t`Allows/denys requests based on the users and/or the IPs reputation.`} |                 ${t`Allows/denys requests based on the users and/or the IPs reputation.`} | ||||||
|             </div> |             </div> | ||||||
|  |             <div class="form-help-text"> | ||||||
|  |                 ${t`The policy passes when the reputation score is above the threshold, and | ||||||
|  |                 doesn't pass when either or both of the selected options are equal or less than the | ||||||
|  |                 threshold.`} | ||||||
|  |             </div> | ||||||
|             <ak-form-element-horizontal |             <ak-form-element-horizontal | ||||||
|                 label=${t`Name`} |                 label=${t`Name`} | ||||||
|                 ?required=${true} |                 ?required=${true} | ||||||
|  | |||||||
| @ -102,7 +102,7 @@ export class LDAPProviderViewPage extends LitElement { | |||||||
|                                         </span> |                                         </span> | ||||||
|                                         <ak-provider-ldap-form |                                         <ak-provider-ldap-form | ||||||
|                                             slot="form" |                                             slot="form" | ||||||
|                                             .instancePk=${this.provider.pk || 0}> |                                             .instancePk=${this.provider.pk}> | ||||||
|                                         </ak-provider-ldap-form> |                                         </ak-provider-ldap-form> | ||||||
|                                         <button slot="trigger" class="pf-c-button pf-m-primary"> |                                         <button slot="trigger" class="pf-c-button pf-m-primary"> | ||||||
|                                             ${t`Edit`} |                                             ${t`Edit`} | ||||||
|  | |||||||
| @ -5,7 +5,9 @@ import { html, TemplateResult } from "lit-html"; | |||||||
| import { DEFAULT_CONFIG } from "../../../api/Config"; | import { DEFAULT_CONFIG } from "../../../api/Config"; | ||||||
| import { ifDefined } from "lit-html/directives/if-defined"; | import { ifDefined } from "lit-html/directives/if-defined"; | ||||||
| import "../../../elements/forms/HorizontalFormElement"; | import "../../../elements/forms/HorizontalFormElement"; | ||||||
|  | import "../../../elements/forms/FormGroup"; | ||||||
| import { ModelForm } from "../../../elements/forms/ModelForm"; | import { ModelForm } from "../../../elements/forms/ModelForm"; | ||||||
|  | import { first } from "../../../utils"; | ||||||
|  |  | ||||||
| @customElement("ak-stage-user-write-form") | @customElement("ak-stage-user-write-form") | ||||||
| export class UserWriteStageForm extends ModelForm<UserWriteStage, string> { | export class UserWriteStageForm extends ModelForm<UserWriteStage, string> { | ||||||
| @ -49,6 +51,22 @@ export class UserWriteStageForm extends ModelForm<UserWriteStage, string> { | |||||||
|                 name="name"> |                 name="name"> | ||||||
|                 <input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required> |                 <input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required> | ||||||
|             </ak-form-element-horizontal> |             </ak-form-element-horizontal> | ||||||
|  |             <ak-form-group .expanded=${true}> | ||||||
|  |                 <span slot="header"> | ||||||
|  |                     ${t`Stage-specific settings`} | ||||||
|  |                 </span> | ||||||
|  |                 <div slot="body" class="pf-c-form"> | ||||||
|  |                     <ak-form-element-horizontal name="createUsersAsInactive"> | ||||||
|  |                         <div class="pf-c-check"> | ||||||
|  |                             <input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.createUsersAsInactive, true)}> | ||||||
|  |                             <label class="pf-c-check__label"> | ||||||
|  |                                 ${t`Create users as inactive`} | ||||||
|  |                             </label> | ||||||
|  |                         </div> | ||||||
|  |                         <p class="pf-c-form__helper-text">${t`Mark newly created users as inactive.`}</p> | ||||||
|  |                     </ak-form-element-horizontal> | ||||||
|  |                 </div> | ||||||
|  |             </ak-form-group> | ||||||
|         </form>`; |         </form>`; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | |||||||
| @ -150,7 +150,7 @@ export class TenantForm extends ModelForm<Tenant, string> { | |||||||
|                             <option value="" ?selected=${this.instance?.flowUnenrollment === undefined}>---------</option> |                             <option value="" ?selected=${this.instance?.flowUnenrollment === undefined}>---------</option> | ||||||
|                             ${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({ |                             ${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({ | ||||||
|                                 ordering: "pk", |                                 ordering: "pk", | ||||||
|                                 designation: FlowsInstancesListDesignationEnum.Recovery, |                                 designation: FlowsInstancesListDesignationEnum.Unenrollment, | ||||||
|                             }).then(flows => { |                             }).then(flows => { | ||||||
|                                 return flows.results.map(flow => { |                                 return flows.results.map(flow => { | ||||||
|                                     const selected = this.instance?.flowUnenrollment === flow.pk; |                                     const selected = this.instance?.flowUnenrollment === flow.pk; | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| import { t } from "@lingui/macro"; | import { t } from "@lingui/macro"; | ||||||
| import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element"; | import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | ||||||
|  |  | ||||||
| import PFPage from "@patternfly/patternfly/components/Page/page.css"; | import PFPage from "@patternfly/patternfly/components/Page/page.css"; | ||||||
| import PFContent from "@patternfly/patternfly/components/Content/content.css"; | import PFContent from "@patternfly/patternfly/components/Content/content.css"; | ||||||
| @ -27,6 +27,7 @@ import "./settings/UserSettingsAuthenticatorTOTP"; | |||||||
| import "./settings/UserSettingsAuthenticatorWebAuthn"; | import "./settings/UserSettingsAuthenticatorWebAuthn"; | ||||||
| import "./settings/UserSettingsPassword"; | import "./settings/UserSettingsPassword"; | ||||||
| import "./settings/SourceSettingsOAuth"; | import "./settings/SourceSettingsOAuth"; | ||||||
|  | import { EVENT_REFRESH } from "../../constants"; | ||||||
|  |  | ||||||
| @customElement("ak-user-settings") | @customElement("ak-user-settings") | ||||||
| export class UserSettingsPage extends LitElement { | export class UserSettingsPage extends LitElement { | ||||||
| @ -35,6 +36,24 @@ export class UserSettingsPage extends LitElement { | |||||||
|         return [PFBase, PFPage, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, PFForm, PFFormControl, AKGlobal]; |         return [PFBase, PFPage, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, PFForm, PFFormControl, AKGlobal]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @property({attribute: false}) | ||||||
|  |     userSettings?: Promise<UserSetting[]>; | ||||||
|  |  | ||||||
|  |     @property({attribute: false}) | ||||||
|  |     sourceSettings?: Promise<UserSetting[]>; | ||||||
|  |  | ||||||
|  |     constructor() { | ||||||
|  |         super(); | ||||||
|  |         this.addEventListener(EVENT_REFRESH, () => { | ||||||
|  |             this.firstUpdated(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     firstUpdated(): void { | ||||||
|  |         this.userSettings = new StagesApi(DEFAULT_CONFIG).stagesAllUserSettingsList(); | ||||||
|  |         this.sourceSettings = new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettingsList(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     renderStageSettings(stage: UserSetting): TemplateResult { |     renderStageSettings(stage: UserSetting): TemplateResult { | ||||||
|         switch (stage.component) { |         switch (stage.component) { | ||||||
|             case "ak-user-settings-authenticator-webauthn": |             case "ak-user-settings-authenticator-webauthn": | ||||||
| @ -82,14 +101,14 @@ export class UserSettingsPage extends LitElement { | |||||||
|                     <section slot="page-tokens" data-tab-title="${t`Tokens`}" class="pf-c-page__main-section pf-m-no-padding-mobile"> |                     <section slot="page-tokens" data-tab-title="${t`Tokens`}" class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|                         <ak-user-token-list></ak-user-token-list> |                         <ak-user-token-list></ak-user-token-list> | ||||||
|                     </section> |                     </section> | ||||||
|                     ${until(new StagesApi(DEFAULT_CONFIG).stagesAllUserSettingsList().then((stages) => { |                     ${until(this.userSettings?.then((stages) => { | ||||||
|                         return stages.map((stage) => { |                         return stages.map((stage) => { | ||||||
|                             return html`<section slot="page-${stage.objectUid}" data-tab-title="${ifDefined(stage.title)}" class="pf-c-page__main-section pf-m-no-padding-mobile"> |                             return html`<section slot="page-${stage.objectUid}" data-tab-title="${ifDefined(stage.title)}" class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|                                 ${this.renderStageSettings(stage)} |                                 ${this.renderStageSettings(stage)} | ||||||
|                             </section>`; |                             </section>`; | ||||||
|                         }); |                         }); | ||||||
|                     }))} |                     }))} | ||||||
|                     ${until(new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettingsList().then((source) => { |                     ${until(this.sourceSettings?.then((source) => { | ||||||
|                         return source.map((stage) => { |                         return source.map((stage) => { | ||||||
|                             return html`<section slot="page-${stage.objectUid}" data-tab-title="${ifDefined(stage.title)}" class="pf-c-page__main-section pf-m-no-padding-mobile"> |                             return html`<section slot="page-${stage.objectUid}" data-tab-title="${ifDefined(stage.title)}" class="pf-c-page__main-section pf-m-no-padding-mobile"> | ||||||
|                                 ${this.renderSourceSettings(stage)} |                                 ${this.renderSourceSettings(stage)} | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ import { customElement, html, TemplateResult } from "lit-element"; | |||||||
| import { until } from "lit-html/directives/until"; | import { until } from "lit-html/directives/until"; | ||||||
| import { DEFAULT_CONFIG } from "../../../api/Config"; | import { DEFAULT_CONFIG } from "../../../api/Config"; | ||||||
| import { BaseUserSettings } from "./BaseUserSettings"; | import { BaseUserSettings } from "./BaseUserSettings"; | ||||||
|  | import { EVENT_REFRESH } from "../../../constants"; | ||||||
|  |  | ||||||
| @customElement("ak-user-settings-authenticator-duo") | @customElement("ak-user-settings-authenticator-duo") | ||||||
| export class UserSettingsAuthenticatorDuo extends BaseUserSettings { | export class UserSettingsAuthenticatorDuo extends BaseUserSettings { | ||||||
| @ -27,7 +28,12 @@ export class UserSettingsAuthenticatorDuo extends BaseUserSettings { | |||||||
|                             return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsDuoDestroy({ |                             return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsDuoDestroy({ | ||||||
|                                 id: devices.results[0].pk || 0 |                                 id: devices.results[0].pk || 0 | ||||||
|                             }).then(() => { |                             }).then(() => { | ||||||
|                                 this.requestUpdate(); |                                 this.dispatchEvent( | ||||||
|  |                                     new CustomEvent(EVENT_REFRESH, { | ||||||
|  |                                         bubbles: true, | ||||||
|  |                                         composed: true, | ||||||
|  |                                     }) | ||||||
|  |                                 ); | ||||||
|                             }); |                             }); | ||||||
|                         }); |                         }); | ||||||
|                     }}> |                     }}> | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ import { until } from "lit-html/directives/until"; | |||||||
| import { DEFAULT_CONFIG } from "../../../api/Config"; | import { DEFAULT_CONFIG } from "../../../api/Config"; | ||||||
| import { STATIC_TOKEN_STYLE } from "../../../flows/stages/authenticator_static/AuthenticatorStaticStage"; | import { STATIC_TOKEN_STYLE } from "../../../flows/stages/authenticator_static/AuthenticatorStaticStage"; | ||||||
| import { BaseUserSettings } from "./BaseUserSettings"; | import { BaseUserSettings } from "./BaseUserSettings"; | ||||||
|  | import { EVENT_REFRESH } from "../../../constants"; | ||||||
|  |  | ||||||
| @customElement("ak-user-settings-authenticator-static") | @customElement("ak-user-settings-authenticator-static") | ||||||
| export class UserSettingsAuthenticatorStatic extends BaseUserSettings { | export class UserSettingsAuthenticatorStatic extends BaseUserSettings { | ||||||
| @ -42,7 +43,12 @@ export class UserSettingsAuthenticatorStatic extends BaseUserSettings { | |||||||
|                             return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsStaticDestroy({ |                             return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsStaticDestroy({ | ||||||
|                                 id: devices.results[0].pk || 0 |                                 id: devices.results[0].pk || 0 | ||||||
|                             }).then(() => { |                             }).then(() => { | ||||||
|                                 this.requestUpdate(); |                                 this.dispatchEvent( | ||||||
|  |                                     new CustomEvent(EVENT_REFRESH, { | ||||||
|  |                                         bubbles: true, | ||||||
|  |                                         composed: true, | ||||||
|  |                                     }) | ||||||
|  |                                 ); | ||||||
|                             }); |                             }); | ||||||
|                         }); |                         }); | ||||||
|                     }}> |                     }}> | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ import { customElement, html, TemplateResult } from "lit-element"; | |||||||
| import { until } from "lit-html/directives/until"; | import { until } from "lit-html/directives/until"; | ||||||
| import { DEFAULT_CONFIG } from "../../../api/Config"; | import { DEFAULT_CONFIG } from "../../../api/Config"; | ||||||
| import { BaseUserSettings } from "./BaseUserSettings"; | import { BaseUserSettings } from "./BaseUserSettings"; | ||||||
|  | import { EVENT_REFRESH } from "../../../constants"; | ||||||
|  |  | ||||||
| @customElement("ak-user-settings-authenticator-totp") | @customElement("ak-user-settings-authenticator-totp") | ||||||
| export class UserSettingsAuthenticatorTOTP extends BaseUserSettings { | export class UserSettingsAuthenticatorTOTP extends BaseUserSettings { | ||||||
| @ -27,7 +28,12 @@ export class UserSettingsAuthenticatorTOTP extends BaseUserSettings { | |||||||
|                             return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsTotpDestroy({ |                             return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsTotpDestroy({ | ||||||
|                                 id: devices.results[0].pk || 0 |                                 id: devices.results[0].pk || 0 | ||||||
|                             }).then(() => { |                             }).then(() => { | ||||||
|                                 this.requestUpdate(); |                                 this.dispatchEvent( | ||||||
|  |                                     new CustomEvent(EVENT_REFRESH, { | ||||||
|  |                                         bubbles: true, | ||||||
|  |                                         composed: true, | ||||||
|  |                                     }) | ||||||
|  |                                 ); | ||||||
|                             }); |                             }); | ||||||
|                         }); |                         }); | ||||||
|                     }}> |                     }}> | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ import "../../../elements/forms/Form"; | |||||||
| import "../../../elements/forms/ModalForm"; | import "../../../elements/forms/ModalForm"; | ||||||
| import "../../../elements/forms/HorizontalFormElement"; | import "../../../elements/forms/HorizontalFormElement"; | ||||||
| import { ifDefined } from "lit-html/directives/if-defined"; | import { ifDefined } from "lit-html/directives/if-defined"; | ||||||
|  | import { EVENT_REFRESH } from "../../../constants"; | ||||||
|  |  | ||||||
| @customElement("ak-user-settings-authenticator-webauthn") | @customElement("ak-user-settings-authenticator-webauthn") | ||||||
| export class UserSettingsAuthenticatorWebAuthn extends BaseUserSettings { | export class UserSettingsAuthenticatorWebAuthn extends BaseUserSettings { | ||||||
| @ -28,7 +29,12 @@ export class UserSettingsAuthenticatorWebAuthn extends BaseUserSettings { | |||||||
|                 return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsWebauthnDestroy({ |                 return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsWebauthnDestroy({ | ||||||
|                     id: device.pk || 0 |                     id: device.pk || 0 | ||||||
|                 }).then(() => { |                 }).then(() => { | ||||||
|                     this.requestUpdate(); |                     this.dispatchEvent( | ||||||
|  |                         new CustomEvent(EVENT_REFRESH, { | ||||||
|  |                             bubbles: true, | ||||||
|  |                             composed: true, | ||||||
|  |                         }) | ||||||
|  |                     ); | ||||||
|                 }); |                 }); | ||||||
|             }}> |             }}> | ||||||
|             <button slot="trigger" class="pf-c-button pf-m-danger"> |             <button slot="trigger" class="pf-c-button pf-m-danger"> | ||||||
|  | |||||||
| @ -12,11 +12,11 @@ This installation method is for test-setups and small-scale productive setups. | |||||||
|  |  | ||||||
| ## Preparation | ## Preparation | ||||||
|  |  | ||||||
| Download the latest `docker-compose.yml` from [here](https://raw.githubusercontent.com/goauthentik/authentik/version/2021.6.2/docker-compose.yml). Place it in a directory of your choice. | Download the latest `docker-compose.yml` from [here](https://raw.githubusercontent.com/goauthentik/authentik/version/2021.6.3/docker-compose.yml). Place it in a directory of your choice. | ||||||
|  |  | ||||||
| To optionally enable error-reporting, run `echo AUTHENTIK_ERROR_REPORTING__ENABLED=true >> .env` | To optionally enable error-reporting, run `echo AUTHENTIK_ERROR_REPORTING__ENABLED=true >> .env` | ||||||
|  |  | ||||||
| To optionally deploy a different version run `echo AUTHENTIK_TAG=2021.6.2 >> .env` | To optionally deploy a different version run `echo AUTHENTIK_TAG=2021.6.3 >> .env` | ||||||
|  |  | ||||||
| If this is a fresh authentik install run the following commands to generate a password: | If this is a fresh authentik install run the following commands to generate a password: | ||||||
|  |  | ||||||
|  | |||||||
| @ -39,6 +39,8 @@ Set the Field `Username attribute` to `http://schemas.goauthentik.io/2021/02/sam | |||||||
|  |  | ||||||
| Set the Field `SP entity ID` to `https://authentik.company/application/saml/zabbix/sso/binding/redirect/` | Set the Field `SP entity ID` to `https://authentik.company/application/saml/zabbix/sso/binding/redirect/` | ||||||
|  |  | ||||||
|  | Set the Field `SP name ID format` to `urn:oasis:names:tc:SAML:2.0:nameid-format:transient` | ||||||
|  |  | ||||||
| Check the box for `Case sensitive login`. | Check the box for `Case sensitive login`. | ||||||
|  |  | ||||||
| For the `SAML Service Provider Certificate` and `SAML Service Provider Private Key`, you can either use custom certificates, or use the self-signed pair generated by authentik. | For the `SAML Service Provider Certificate` and `SAML Service Provider Private Key`, you can either use custom certificates, or use the self-signed pair generated by authentik. | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ version: "3.5" | |||||||
|  |  | ||||||
| services: | services: | ||||||
|   authentik_proxy: |   authentik_proxy: | ||||||
|     image: ghcr.io/goauthentik/proxy:2021.6.2 |     image: ghcr.io/goauthentik/proxy:2021.6.3 | ||||||
|     ports: |     ports: | ||||||
|       - 4180:4180 |       - 4180:4180 | ||||||
|       - 4443:4443 |       - 4443:4443 | ||||||
| @ -21,7 +21,7 @@ services: | |||||||
|       AUTHENTIK_TOKEN: token-generated-by-authentik |       AUTHENTIK_TOKEN: token-generated-by-authentik | ||||||
|   # Or, for the LDAP Outpost |   # Or, for the LDAP Outpost | ||||||
|   authentik_proxy: |   authentik_proxy: | ||||||
|     image: ghcr.io/goauthentik/ldap:2021.6.2 |     image: ghcr.io/goauthentik/ldap:2021.6.3 | ||||||
|     ports: |     ports: | ||||||
|       - 389:3389 |       - 389:3389 | ||||||
|     environment: |     environment: | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ metadata: | |||||||
|     app.kubernetes.io/instance: __OUTPOST_NAME__ |     app.kubernetes.io/instance: __OUTPOST_NAME__ | ||||||
|     app.kubernetes.io/managed-by: goauthentik.io |     app.kubernetes.io/managed-by: goauthentik.io | ||||||
|     app.kubernetes.io/name: authentik-proxy |     app.kubernetes.io/name: authentik-proxy | ||||||
|     app.kubernetes.io/version: 2021.6.2 |     app.kubernetes.io/version: 2021.6.3 | ||||||
|   name: authentik-outpost-api |   name: authentik-outpost-api | ||||||
| stringData: | stringData: | ||||||
|   authentik_host: "__AUTHENTIK_URL__" |   authentik_host: "__AUTHENTIK_URL__" | ||||||
| @ -29,7 +29,7 @@ metadata: | |||||||
|     app.kubernetes.io/instance: __OUTPOST_NAME__ |     app.kubernetes.io/instance: __OUTPOST_NAME__ | ||||||
|     app.kubernetes.io/managed-by: goauthentik.io |     app.kubernetes.io/managed-by: goauthentik.io | ||||||
|     app.kubernetes.io/name: authentik-proxy |     app.kubernetes.io/name: authentik-proxy | ||||||
|     app.kubernetes.io/version: 2021.6.2 |     app.kubernetes.io/version: 2021.6.3 | ||||||
|   name: authentik-outpost |   name: authentik-outpost | ||||||
| spec: | spec: | ||||||
|   ports: |   ports: | ||||||
| @ -54,7 +54,7 @@ metadata: | |||||||
|     app.kubernetes.io/instance: __OUTPOST_NAME__ |     app.kubernetes.io/instance: __OUTPOST_NAME__ | ||||||
|     app.kubernetes.io/managed-by: goauthentik.io |     app.kubernetes.io/managed-by: goauthentik.io | ||||||
|     app.kubernetes.io/name: authentik-proxy |     app.kubernetes.io/name: authentik-proxy | ||||||
|     app.kubernetes.io/version: 2021.6.2 |     app.kubernetes.io/version: 2021.6.3 | ||||||
|   name: authentik-outpost |   name: authentik-outpost | ||||||
| spec: | spec: | ||||||
|   selector: |   selector: | ||||||
| @ -62,14 +62,14 @@ spec: | |||||||
|       app.kubernetes.io/instance: __OUTPOST_NAME__ |       app.kubernetes.io/instance: __OUTPOST_NAME__ | ||||||
|       app.kubernetes.io/managed-by: goauthentik.io |       app.kubernetes.io/managed-by: goauthentik.io | ||||||
|       app.kubernetes.io/name: authentik-proxy |       app.kubernetes.io/name: authentik-proxy | ||||||
|       app.kubernetes.io/version: 2021.6.2 |       app.kubernetes.io/version: 2021.6.3 | ||||||
|   template: |   template: | ||||||
|     metadata: |     metadata: | ||||||
|       labels: |       labels: | ||||||
|         app.kubernetes.io/instance: __OUTPOST_NAME__ |         app.kubernetes.io/instance: __OUTPOST_NAME__ | ||||||
|         app.kubernetes.io/managed-by: goauthentik.io |         app.kubernetes.io/managed-by: goauthentik.io | ||||||
|         app.kubernetes.io/name: authentik-proxy |         app.kubernetes.io/name: authentik-proxy | ||||||
|         app.kubernetes.io/version: 2021.6.2 |         app.kubernetes.io/version: 2021.6.3 | ||||||
|     spec: |     spec: | ||||||
|       containers: |       containers: | ||||||
|         - env: |         - env: | ||||||
| @ -88,7 +88,7 @@ spec: | |||||||
|               secretKeyRef: |               secretKeyRef: | ||||||
|                 key: authentik_host_insecure |                 key: authentik_host_insecure | ||||||
|                 name: authentik-outpost-api |                 name: authentik-outpost-api | ||||||
|         image: ghcr.io/goauthentik/proxy:2021.6.2 |         image: ghcr.io/goauthentik/proxy:2021.6.3 | ||||||
|         name: proxy |         name: proxy | ||||||
|         ports: |         ports: | ||||||
|           - containerPort: 4180 |           - containerPort: 4180 | ||||||
| @ -110,7 +110,7 @@ metadata: | |||||||
|     app.kubernetes.io/instance: __OUTPOST_NAME__ |     app.kubernetes.io/instance: __OUTPOST_NAME__ | ||||||
|     app.kubernetes.io/managed-by: goauthentik.io |     app.kubernetes.io/managed-by: goauthentik.io | ||||||
|     app.kubernetes.io/name: authentik-proxy |     app.kubernetes.io/name: authentik-proxy | ||||||
|     app.kubernetes.io/version: 2021.6.2 |     app.kubernetes.io/version: 2021.6.3 | ||||||
|   name: authentik-outpost |   name: authentik-outpost | ||||||
| spec: | spec: | ||||||
|   rules: |   rules: | ||||||
|  | |||||||
| @ -57,7 +57,7 @@ import TabItem from '@theme/TabItem'; | |||||||
|     location @akprox_signin { |     location @akprox_signin { | ||||||
|       internal; |       internal; | ||||||
|       add_header Set-Cookie $auth_cookie; |       add_header Set-Cookie $auth_cookie; | ||||||
|       return 302 /akprox/start?rd=$escaped_request_uri; |       return 302 /akprox/start?rd=$request_uri; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     location / { |     location / { | ||||||
|  | |||||||
| @ -91,12 +91,54 @@ slug: "2021.6" | |||||||
| - recovery: fix error when creating multiple keys for the same user | - recovery: fix error when creating multiple keys for the same user | ||||||
| - stages/authenticator_duo: fix error when enrolling an existing user | - stages/authenticator_duo: fix error when enrolling an existing user | ||||||
| - stages/authenticator_duo: make Duo-admin viewset writeable | - stages/authenticator_duo: make Duo-admin viewset writeable | ||||||
| - website/docs: Add a note about Protocol Overwrite (#1031) |  | ||||||
| - website/docs: add changelog for release candidates |  | ||||||
| - website/docs: add docs for flow executor |  | ||||||
| - website/docs: add wekan (#1032) |  | ||||||
| - website/docs: remove migrate command | - website/docs: remove migrate command | ||||||
|  |  | ||||||
|  | ## Fixed in 2021.6.2 | ||||||
|  |  | ||||||
|  | - core: add support for custom urls for avatars | ||||||
|  | - core: deepmerge user.group_attributes, use group_attributes for user settings | ||||||
|  | - core: fix PropertyMapping's globals not matching Expression policy | ||||||
|  | - core: remove default flow background from default css, set static in base_full and dynamically in if/flow | ||||||
|  | - crypto: catch error when loading private key | ||||||
|  | - flows: make flow plan cache timeout configurable | ||||||
|  | - outposts: fix port and inner_port being mixed on docker controller | ||||||
|  | - outposts/proxy: fix additionalHeaders not being set properly | ||||||
|  | - policies: don't use policy cache when checking application access | ||||||
|  | - policies: make policy result cache timeout configurable | ||||||
|  | - root: allow loading local /static files without debug flag | ||||||
|  | - root: make general cache timeouts configurable | ||||||
|  | - root: remove old traefik labels | ||||||
|  | - root: save temporary database dump in /tmp | ||||||
|  | - root: set outposts.docker_image_base to gh-master for tests | ||||||
|  | - stages/authenticator_validate: fix error when using not_configured_action=configure | ||||||
|  | - tenants: fix tenant not being queried correctly when using accessing over a child domain | ||||||
|  | - web/admin: fix tenant's default flag not being saved | ||||||
|  | - web/admin: handle elements in slot=form not being forms | ||||||
|  | - web/admin: sort inputs on authenticator validation stage form | ||||||
|  |  | ||||||
|  | ## Fixed in 2021.6.3 | ||||||
|  |  | ||||||
|  | - api: use partition instead of split for token | ||||||
|  | - core: fix flow background not correctly loading on initial draw | ||||||
|  | - events: add ability to create events via API | ||||||
|  | - events: ignore notification non-existent in transport | ||||||
|  | - events: only create SYSTEM_EXCEPTION event when error would've been sent to sentry | ||||||
|  | - expressions: fix regex_match result being inverted | ||||||
|  | - flows: add FlowStageBinding to flow plan instead of just stage | ||||||
|  | - flows: add invalid_response_action to configure how the FlowExecutor should handle invalid responses | ||||||
|  | - flows: handle possible errors with FlowPlans received from cache | ||||||
|  | - outposts: check docker container ports match | ||||||
|  | - outposts/ldap: fixed IsActive and IsSuperuser returning swapped incorrect values (#1078) | ||||||
|  | - providers/oauth2: fix exp of JWT when not using seconds | ||||||
|  | - sources/ldap: improve error handling when checking for password complexity on non-ad setups | ||||||
|  | - stages/authenticator_duo: fix component not being set in API | ||||||
|  | - stages/prompt: ensure hidden and static fields keep the value they had set | ||||||
|  | - stages/user_write: add flag to create new users as inactive | ||||||
|  | - tenants: include all default flows in current_tenant | ||||||
|  | - web/admin: fix deletion of authenticator not reloading the state correctly | ||||||
|  | - web/admin: fix only recovery flows being selectable for unenrollment flow in tenant form | ||||||
|  | - web/admin: fix text color on pf-c-card | ||||||
|  |  | ||||||
| ## Upgrading | ## Upgrading | ||||||
|  |  | ||||||
| This release does not introduce any new requirements. | This release does not introduce any new requirements. | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								website/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								website/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -20,7 +20,7 @@ | |||||||
|                 "react-toggle": "^4.1.2" |                 "react-toggle": "^4.1.2" | ||||||
|             }, |             }, | ||||||
|             "devDependencies": { |             "devDependencies": { | ||||||
|                 "prettier": "2.3.1" |                 "prettier": "2.3.2" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/@algolia/autocomplete-core": { |         "node_modules/@algolia/autocomplete-core": { | ||||||
| @ -10376,9 +10376,9 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/prettier": { |         "node_modules/prettier": { | ||||||
|             "version": "2.3.1", |             "version": "2.3.2", | ||||||
|             "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", |             "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", | ||||||
|             "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", |             "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", | ||||||
|             "dev": true, |             "dev": true, | ||||||
|             "bin": { |             "bin": { | ||||||
|                 "prettier": "bin-prettier.js" |                 "prettier": "bin-prettier.js" | ||||||
| @ -22886,9 +22886,9 @@ | |||||||
|             "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" |             "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" | ||||||
|         }, |         }, | ||||||
|         "prettier": { |         "prettier": { | ||||||
|             "version": "2.3.1", |             "version": "2.3.2", | ||||||
|             "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", |             "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", | ||||||
|             "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", |             "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", | ||||||
|             "dev": true |             "dev": true | ||||||
|         }, |         }, | ||||||
|         "pretty-error": { |         "pretty-error": { | ||||||
|  | |||||||
| @ -35,6 +35,6 @@ | |||||||
|         ] |         ] | ||||||
|     }, |     }, | ||||||
|     "devDependencies": { |     "devDependencies": { | ||||||
|         "prettier": "2.3.1" |         "prettier": "2.3.2" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -145,7 +145,9 @@ | |||||||
|                 "name": "default-enrollment-user-write" |                 "name": "default-enrollment-user-write" | ||||||
|             }, |             }, | ||||||
|             "model": "authentik_stages_user_write.userwritestage", |             "model": "authentik_stages_user_write.userwritestage", | ||||||
|             "attrs": {} |             "attrs": { | ||||||
|  |                 "create_users_as_inactive": true | ||||||
|  |             } | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "identifiers": { |             "identifiers": { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	