diff --git a/Pipfile.lock b/Pipfile.lock index 59747ff6b9..ccb1fd41c6 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -76,11 +76,11 @@ }, "asgiref": { "hashes": [ - "sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee", - "sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78" + "sha256:05914d0fa65a21711e732adc6572edad6c8da5f1435c3f0c060689ced5e85195", + "sha256:d36fa91dd90e3aa3c81a6bd426ccc8fb20bd3d22b0cf14a12800289e9c3e2563" ], "markers": "python_version >= '3.6'", - "version": "==3.3.4" + "version": "==3.4.0" }, "async-timeout": { "hashes": [ @@ -122,19 +122,19 @@ }, "boto3": { "hashes": [ - "sha256:2c2f70608934b03f9c08f4cd185de223b5abd18245dd4d4800e1fbc2a2523e31", - "sha256:fccfa81cda69bb2317ed97e7149d7d84d19e6ec3bfbe3f721139e7ac0c407c73" + "sha256:6300e9ee9a404038113250bd218e2c4827f5e676efb14e77de2ad2dcb67679bc", + "sha256:be4714f0475c1f5183eea09ddbf568ced6fa41b0fc9976f2698b8442e1b17303" ], "index": "pypi", - "version": "==1.17.98" + "version": "==1.17.102" }, "botocore": { "hashes": [ - "sha256:b2a49de4ee04b690142c8e7240f0f5758e3f7673dd39cf398efe893bf5e11c3f", - "sha256:b955b23fe2fbdbbc8e66f37fe2970de6b5d8169f940b200bcf434751709d38f6" + "sha256:2f57f7ceed1598d96cc497aeb45317db5d3b21a5aafea4732d0e561d0fc2a8fa", + "sha256:bdf08a4f7f01ead00d386848f089c08270499711447569c18d0db60023619c06" ], "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": { "hashes": [ @@ -165,11 +165,11 @@ }, "celery": { "hashes": [ - "sha256:54436cd97b031bf2e08064223240e2a83d601d9414bcb1b702f94c6c33c29485", - "sha256:b5399d76cf70d5cfac3ec993f8796ec1aa90d4cef55972295751f384758a80d7" + "sha256:8d9a3de9162965e97f8e8cc584c67aad83b3f7a267584fa47701ed11c3e0d4b0", + "sha256:9dab2170b4038f7bf10ef2861dbf486ddf1d20592290a1040f7b7a1259705d42" ], "index": "pypi", - "version": "==5.1.1" + "version": "==5.1.2" }, "certifi": { "hashes": [ @@ -948,10 +948,30 @@ }, "pyrsistent": { "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'", - "version": "==0.17.3" + "markers": "python_version >= '3.6'", + "version": "==0.18.0" }, "python-dateutil": { "hashes": [ @@ -1167,11 +1187,11 @@ "secure" ], "hashes": [ - "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", - "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" + "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", + "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" ], "index": "pypi", - "version": "==1.26.5" + "version": "==1.26.6" }, "uvicorn": { "extras": [ @@ -1565,7 +1585,7 @@ "sha256:83510593e07e433b77bd5bff0f6f607dbafa06d1a89022616f02d8b699cfcd56", "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" }, "lazy-object-proxy": { @@ -1838,11 +1858,11 @@ "secure" ], "hashes": [ - "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", - "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" + "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", + "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" ], "index": "pypi", - "version": "==1.26.5" + "version": "==1.26.6" }, "wrapt": { "hashes": [ diff --git a/authentik/api/authentication.py b/authentik/api/authentication.py index 1d38fc9e55..ee423bf595 100644 --- a/authentik/api/authentication.py +++ b/authentik/api/authentication.py @@ -19,7 +19,7 @@ def token_from_header(raw_header: bytes) -> Optional[Token]: auth_credentials = raw_header.decode() if auth_credentials == "" or " " not in auth_credentials: return None - auth_type, auth_credentials = auth_credentials.split() + auth_type, _, auth_credentials = auth_credentials.partition(" ") if auth_type.lower() not in ["basic", "bearer"]: LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower()) raise AuthenticationFailed("Unsupported authentication type") diff --git a/authentik/core/sources/flow_manager.py b/authentik/core/sources/flow_manager.py index 1730648229..399f249338 100644 --- a/authentik/core/sources/flow_manager.py +++ b/authentik/core/sources/flow_manager.py @@ -213,7 +213,7 @@ class SourceFlowManager: planner = FlowPlanner(flow) plan = planner.plan(self.request, kwargs) for stage in self.get_stages_to_append(flow): - plan.append(stage) + plan.append_stage(stage=stage) self.request.session[SESSION_KEY_PLAN] = plan return redirect_with_qs( "authentik_core:if-flow", diff --git a/authentik/core/templates/if/flow.html b/authentik/core/templates/if/flow.html index b1435d2f6f..8b1a381a7c 100644 --- a/authentik/core/templates/if/flow.html +++ b/authentik/core/templates/if/flow.html @@ -13,7 +13,7 @@ {% endblock %} diff --git a/authentik/core/templates/login/base_full.html b/authentik/core/templates/login/base_full.html index 8750fb341a..cfeecb855d 100644 --- a/authentik/core/templates/login/base_full.html +++ b/authentik/core/templates/login/base_full.html @@ -10,7 +10,7 @@ {% block head %} {% endblock %} diff --git a/authentik/events/api/event.py b/authentik/events/api/event.py index 10069999e7..570559a426 100644 --- a/authentik/events/api/event.py +++ b/authentik/events/api/event.py @@ -6,11 +6,11 @@ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter, extend_schema from guardian.shortcuts import get_objects_for_user 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.response import Response 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.events.models import Event, EventAction @@ -19,11 +19,6 @@ from authentik.events.models import Event, EventAction class EventSerializer(ModelSerializer): """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: model = Event @@ -96,7 +91,7 @@ class EventsFilter(django_filters.FilterSet): fields = ["action", "client_ip", "username"] -class EventViewSet(ReadOnlyModelViewSet): +class EventViewSet(ModelViewSet): """Event Read-Only Viewset""" queryset = Event.objects.all() diff --git a/authentik/events/middleware.py b/authentik/events/middleware.py index 684121171c..1543fb8efe 100644 --- a/authentik/events/middleware.py +++ b/authentik/events/middleware.py @@ -13,6 +13,7 @@ from authentik.core.models import User from authentik.events.models import Event, EventAction, Notification from authentik.events.signals import EventNewThread from authentik.events.utils import model_to_dict +from authentik.lib.sentry import before_send from authentik.lib.utils.errors import exception_to_string @@ -62,12 +63,13 @@ class AuditMiddleware: if settings.DEBUG: return - thread = EventNewThread( - EventAction.SYSTEM_EXCEPTION, - request, - message=exception_to_string(exception), - ) - thread.run() + if before_send({}, {"exc_info": (None, exception, None)}) is not None: + thread = EventNewThread( + EventAction.SYSTEM_EXCEPTION, + request, + message=exception_to_string(exception), + ) + thread.run() @staticmethod # pylint: disable=unused-argument diff --git a/authentik/events/tasks.py b/authentik/events/tasks.py index 6ce94fa904..8ab4f8662e 100644 --- a/authentik/events/tasks.py +++ b/authentik/events/tasks.py @@ -105,7 +105,11 @@ def notification_transport( """Send notification over specified transport""" self.save_on_success = False 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( pk=transport_pk ) diff --git a/authentik/flows/api/bindings.py b/authentik/flows/api/bindings.py index 74b97ca994..13fd04887c 100644 --- a/authentik/flows/api/bindings.py +++ b/authentik/flows/api/bindings.py @@ -25,6 +25,7 @@ class FlowStageBindingSerializer(ModelSerializer): "re_evaluate_policies", "order", "policy_engine_mode", + "invalid_response_action", ] diff --git a/authentik/flows/markers.py b/authentik/flows/markers.py index 1d710cd4fc..e545cf89be 100644 --- a/authentik/flows/markers.py +++ b/authentik/flows/markers.py @@ -5,8 +5,7 @@ from typing import TYPE_CHECKING, Optional from django.http.request import HttpRequest from structlog.stdlib import get_logger -from authentik.core.models import User -from authentik.flows.models import Stage +from authentik.flows.models import FlowStageBinding from authentik.policies.engine import PolicyEngine from authentik.policies.models import PolicyBinding @@ -22,11 +21,14 @@ class StageMarker: # pylint: disable=unused-argument def process( - self, plan: "FlowPlan", stage: Stage, http_request: Optional[HttpRequest] - ) -> Optional[Stage]: + self, + plan: "FlowPlan", + binding: FlowStageBinding, + http_request: HttpRequest, + ) -> Optional[FlowStageBinding]: """Process callback for this marker. This should be overridden by sub-classes. If a stage should be removed, return None.""" - return stage + return binding @dataclass @@ -34,24 +36,34 @@ class ReevaluateMarker(StageMarker): """Reevaluate Marker, forces stage's policies to be evaluated again.""" binding: PolicyBinding - user: User def process( - self, plan: "FlowPlan", stage: Stage, http_request: Optional[HttpRequest] - ) -> Optional[Stage]: + self, + plan: "FlowPlan", + binding: FlowStageBinding, + http_request: HttpRequest, + ) -> Optional[FlowStageBinding]: """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 - if http_request: - engine.request.set_http_request(http_request) + engine.request.set_http_request(http_request) engine.request.context = plan.context engine.build() result = engine.result if result.passing: - return stage + return binding LOGGER.warning( - "f(plan_inst)[re-eval marker]: stage failed re-evaluation", - stage=stage, + "f(plan_inst)[re-eval marker]: binding failed re-evaluation", + binding=binding, messages=result.messages, ) return None diff --git a/authentik/flows/migrations/0018_oob_flows.py b/authentik/flows/migrations/0018_oob_flows.py index c6caee84bb..9058dccd41 100644 --- a/authentik/flows/migrations/0018_oob_flows.py +++ b/authentik/flows/migrations/0018_oob_flows.py @@ -135,7 +135,7 @@ class Migration(migrations.Migration): dependencies = [ ("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_password", "0002_passwordstage_change_flow"), ("authentik_policies", "0001_initial"), diff --git a/authentik/flows/migrations/0021_flowstagebinding_invalid_response_action.py b/authentik/flows/migrations/0021_flowstagebinding_invalid_response_action.py new file mode 100644 index 0000000000..1c0add77ff --- /dev/null +++ b/authentik/flows/migrations/0021_flowstagebinding_invalid_response_action.py @@ -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.", + ), + ), + ] diff --git a/authentik/flows/models.py b/authentik/flows/models.py index 6e9663882e..df713dba23 100644 --- a/authentik/flows/models.py +++ b/authentik/flows/models.py @@ -27,6 +27,14 @@ class NotConfiguredAction(models.TextChoices): 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): """Designation of what a Flow should be used for. At a later point, this 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."), ) + 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() objects = InheritanceManager() diff --git a/authentik/flows/planner.py b/authentik/flows/planner.py index 9edb719fa8..ed13736017 100644 --- a/authentik/flows/planner.py +++ b/authentik/flows/planner.py @@ -52,33 +52,41 @@ class FlowPlan: 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) 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""" - 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()) - 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""" - self.stages.insert(1, stage) + self.bindings.insert(1, FlowStageBinding(stage=stage, order=0)) 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""" if not self.has_stages: return None - stage = self.stages[0] + binding = self.bindings[0] marker = self.markers[0] if marker.__class__ is not StageMarker: - LOGGER.debug("f(plan_inst): stage has marker", stage=stage, marker=marker) - marked_stage = marker.process(self, stage, http_request) + LOGGER.debug( + "f(plan_inst): stage has marker", binding=binding, marker=marker + ) + marked_stage = marker.process(self, binding, http_request) if not marked_stage: - LOGGER.debug("f(plan_inst): marker returned none, next stage", stage=stage) - self.stages.remove(stage) + LOGGER.debug( + "f(plan_inst): marker returned none, next stage", binding=binding + ) + self.bindings.remove(binding) self.markers.remove(marker) if not self.has_stages: return None @@ -89,12 +97,12 @@ class FlowPlan: def pop(self): """Pop next pending stage from bottom of list""" self.markers.pop(0) - self.stages.pop(0) + self.bindings.pop(0) @property def has_stages(self) -> bool: """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: @@ -161,7 +169,7 @@ class FlowPlanner: plan = self._build_plan(user, request, default_context) cache.set(cache_key(self.flow, user), plan, CACHE_TIMEOUT) 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() return plan @@ -216,9 +224,9 @@ class FlowPlanner: "f(plan): stage has re-evaluate marker", stage=binding.stage, ) - marker = ReevaluateMarker(binding=binding, user=user) + marker = ReevaluateMarker(binding=binding) if stage: - plan.append(stage, marker) + plan.append(binding, marker) HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug) self._logger.debug( "f(plan): finished building", diff --git a/authentik/flows/stage.py b/authentik/flows/stage.py index 8502a42c62..93461ce423 100644 --- a/authentik/flows/stage.py +++ b/authentik/flows/stage.py @@ -16,6 +16,7 @@ from authentik.flows.challenge import ( HttpChallengeResponse, WithUserInfoChallenge, ) +from authentik.flows.models import InvalidResponseAction from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.views import FlowExecutorView @@ -69,7 +70,13 @@ class ChallengeStageView(StageView): """Return a challenge for the frontend to solve""" challenge = self._get_challenge(*args, **kwargs) 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) # pylint: disable=unused-argument @@ -77,6 +84,21 @@ class ChallengeStageView(StageView): """Handle challenge response""" challenge: ChallengeResponse = self.get_response_instance(data=request.data) 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_valid(challenge) @@ -126,5 +148,10 @@ class ChallengeStageView(StageView): ) challenge_response.initial_data["response_errors"] = full_errors 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) diff --git a/authentik/flows/tests/test_planner.py b/authentik/flows/tests/test_planner.py index 9eb60a1128..8e185da58b 100644 --- a/authentik/flows/tests/test_planner.py +++ b/authentik/flows/tests/test_planner.py @@ -182,8 +182,8 @@ class TestFlowPlanner(TestCase): planner = FlowPlanner(flow) plan = planner.plan(request) - self.assertEqual(plan.stages[0], binding.stage) - self.assertEqual(plan.stages[1], binding2.stage) + self.assertEqual(plan.bindings[0], binding) + self.assertEqual(plan.bindings[1], binding2) self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[1], ReevaluateMarker) diff --git a/authentik/flows/tests/test_views.py b/authentik/flows/tests/test_views.py index fee5ff2592..3ccf58f0dd 100644 --- a/authentik/flows/tests/test_views.py +++ b/authentik/flows/tests/test_views.py @@ -11,15 +11,23 @@ from authentik.core.models import User from authentik.flows.challenge import ChallengeTypes from authentik.flows.exceptions import FlowNonApplicableException 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.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageView from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_PLAN, FlowExecutorView from authentik.lib.config import CONFIG from authentik.policies.dummy.models import DummyPolicy from authentik.policies.models import PolicyBinding +from authentik.policies.reputation.models import ReputationPolicy from authentik.policies.types import PolicyResult +from authentik.stages.deny.models import DenyStage 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_TRUE = MagicMock(return_value=PolicyResult(True)) @@ -52,8 +60,9 @@ class TestFlowExecutor(TestCase): designation=FlowDesignation.AUTHENTICATION, ) stage = DummyStage.objects.create(name="dummy") + binding = FlowStageBinding(target=flow, stage=stage, order=0) 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[SESSION_KEY_PLAN] = plan @@ -163,7 +172,7 @@ class TestFlowExecutor(TestCase): # Check that two stages are in plan session = self.client.session 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 response = self.client.post(exec_url) # Second request redirects to the same URL @@ -172,7 +181,7 @@ class TestFlowExecutor(TestCase): # Check that two stages are in plan session = self.client.session plan: FlowPlan = session[SESSION_KEY_PLAN] - self.assertEqual(len(plan.stages), 1) + self.assertEqual(len(plan.bindings), 1) @patch( "authentik.flows.views.to_stage_response", @@ -213,8 +222,8 @@ class TestFlowExecutor(TestCase): plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] - self.assertEqual(plan.stages[0], binding.stage) - self.assertEqual(plan.stages[1], binding2.stage) + self.assertEqual(plan.bindings[0], binding) + self.assertEqual(plan.bindings[1], binding2) self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[1], ReevaluateMarker) @@ -267,9 +276,9 @@ class TestFlowExecutor(TestCase): self.assertEqual(response.status_code, 200) plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] - self.assertEqual(plan.stages[0], binding.stage) - self.assertEqual(plan.stages[1], binding2.stage) - self.assertEqual(plan.stages[2], binding3.stage) + self.assertEqual(plan.bindings[0], binding) + self.assertEqual(plan.bindings[1], binding2) + self.assertEqual(plan.bindings[2], binding3) self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[1], ReevaluateMarker) @@ -281,8 +290,8 @@ class TestFlowExecutor(TestCase): plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] - self.assertEqual(plan.stages[0], binding2.stage) - self.assertEqual(plan.stages[1], binding3.stage) + self.assertEqual(plan.bindings[0], binding2) + self.assertEqual(plan.bindings[1], binding3) self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[1], StageMarker) @@ -338,9 +347,9 @@ class TestFlowExecutor(TestCase): self.assertEqual(response.status_code, 200) plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] - self.assertEqual(plan.stages[0], binding.stage) - self.assertEqual(plan.stages[1], binding2.stage) - self.assertEqual(plan.stages[2], binding3.stage) + self.assertEqual(plan.bindings[0], binding) + self.assertEqual(plan.bindings[1], binding2) + self.assertEqual(plan.bindings[2], binding3) self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[1], ReevaluateMarker) @@ -352,8 +361,8 @@ class TestFlowExecutor(TestCase): plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] - self.assertEqual(plan.stages[0], binding2.stage) - self.assertEqual(plan.stages[1], binding3.stage) + self.assertEqual(plan.bindings[0], binding2) + self.assertEqual(plan.bindings[1], binding3) self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[1], StageMarker) @@ -364,7 +373,7 @@ class TestFlowExecutor(TestCase): 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) @@ -438,10 +447,10 @@ class TestFlowExecutor(TestCase): plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] - self.assertEqual(plan.stages[0], binding.stage) - self.assertEqual(plan.stages[1], binding2.stage) - self.assertEqual(plan.stages[2], binding3.stage) - self.assertEqual(plan.stages[3], binding4.stage) + self.assertEqual(plan.bindings[0], binding) + self.assertEqual(plan.bindings[1], binding2) + self.assertEqual(plan.bindings[2], binding3) + self.assertEqual(plan.bindings[3], binding4) self.assertIsInstance(plan.markers[0], StageMarker) self.assertIsInstance(plan.markers[1], ReevaluateMarker) @@ -512,3 +521,78 @@ class TestFlowExecutor(TestCase): stage_view = StageView(executor) 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, + }, + ) diff --git a/authentik/flows/views.py b/authentik/flows/views.py index 499338d97d..39c9a16626 100644 --- a/authentik/flows/views.py +++ b/authentik/flows/views.py @@ -4,6 +4,7 @@ from typing import Any, Optional from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin +from django.core.cache import cache from django.http import Http404, HttpRequest, HttpResponse, HttpResponseRedirect from django.http.request import QueryDict from django.shortcuts import get_object_or_404, redirect @@ -37,7 +38,13 @@ from authentik.flows.challenge import ( WithUserInfoChallenge, ) 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 ( PLAN_CONTEXT_PENDING_USER, PLAN_CONTEXT_REDIRECT, @@ -107,6 +114,7 @@ class FlowExecutorView(APIView): flow: Flow plan: Optional[FlowPlan] = None + current_binding: FlowStageBinding current_stage: Stage current_stage_view: View @@ -159,11 +167,12 @@ class FlowExecutorView(APIView): request.session[SESSION_KEY_GET] = QueryDict(request.GET.get("query", "")) # We don't save the Plan after getting the next stage # as it hasn't been successfully passed yet - next_stage = self.plan.next(self.request) - if not next_stage: + next_binding = self.plan.next(self.request) + if not next_binding: self._logger.debug("f(exec): no more stages, flow is done.") return self._flow_done() - self.current_stage = next_stage + self.current_binding = next_binding + self.current_stage = next_binding.stage self._logger.debug( "f(exec): Current stage", current_stage=self.current_stage, @@ -268,8 +277,31 @@ class FlowExecutorView(APIView): planner = FlowPlanner(self.flow) plan = planner.plan(self.request) 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 + 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: """User Successfully passed all stages""" # 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.request.session[SESSION_KEY_PLAN] = self.plan - if self.plan.stages: + if self.plan.bindings: self._logger.debug( "f(exec): Continuing with next stage", - remaining=len(self.plan.stages), + remaining=len(self.plan.bindings), ) kwargs = self.kwargs kwargs.update({"flow_slug": self.flow.slug}) diff --git a/authentik/outposts/controllers/docker.py b/authentik/outposts/controllers/docker.py index c852764717..aa7a6781e5 100644 --- a/authentik/outposts/controllers/docker.py +++ b/authentik/outposts/controllers/docker.py @@ -53,6 +53,27 @@ class DockerController(BaseController): return True 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]: container_name = f"authentik-proxy-{self.outpost.uuid.hex}" try: @@ -98,6 +119,11 @@ class DockerController(BaseController): ) self.down() 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 if self._comp_env(container): self.logger.info("Container has outdated config, re-creating...") diff --git a/authentik/outposts/models.py b/authentik/outposts/models.py index ff80960a7b..ba21fdbc32 100644 --- a/authentik/outposts/models.py +++ b/authentik/outposts/models.py @@ -406,7 +406,10 @@ class Outpost(ManagedModel): def get_required_objects(self) -> Iterable[Union[models.Model, str]]: """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 ( Provider.objects.filter(outpost=self).select_related().select_subclasses() ): diff --git a/authentik/policies/reputation/models.py b/authentik/policies/reputation/models.py index 305a33832f..5a52241e2b 100644 --- a/authentik/policies/reputation/models.py +++ b/authentik/policies/reputation/models.py @@ -33,21 +33,21 @@ class ReputationPolicy(Policy): def passes(self, request: PolicyRequest) -> PolicyResult: remote_ip = get_client_ip(request.http_request) - passing = True + passing = False if self.check_ip: 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) if self.check_username: 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( "Score for Username", username=request.user.username, score=score, passing=passing, ) - return PolicyResult(passing) + return PolicyResult(bool(passing)) class Meta: diff --git a/authentik/providers/oauth2/models.py b/authentik/providers/oauth2/models.py index a9b82bf84a..5ee94bfe2b 100644 --- a/authentik/providers/oauth2/models.py +++ b/authentik/providers/oauth2/models.py @@ -474,7 +474,7 @@ class RefreshToken(ExpiringModel, BaseGrantModel): now = int(time.time()) iat_time = now 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 auth_events = Event.objects.filter( diff --git a/authentik/providers/oauth2/views/authorize.py b/authentik/providers/oauth2/views/authorize.py index 5319ce40de..635c7d307d 100644 --- a/authentik/providers/oauth2/views/authorize.py +++ b/authentik/providers/oauth2/views/authorize.py @@ -374,9 +374,9 @@ class OAuthFulfillmentStage(StageView): query_fragment["code"] = code.code query_fragment["token_type"] = "bearer" - query_fragment["expires_in"] = timedelta_from_string( - self.provider.token_validity - ).seconds + query_fragment["expires_in"] = int( + timedelta_from_string(self.provider.token_validity).total_seconds() + ) query_fragment["state"] = self.params.state if self.params.state else "" return query_fragment @@ -468,14 +468,14 @@ class AuthorizationFlowInitView(PolicyAccessView): # OpenID clients can specify a `prompt` parameter, and if its set to consent we # need to inject a consent stage 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 stage = ConsentStage( name="OAuth2 Provider In-memory consent stage", mode=ConsentMode.ALWAYS_REQUIRE, ) - plan.append(stage) - plan.append(in_memory_stage(OAuthFulfillmentStage)) + plan.append_stage(stage) + plan.append_stage(in_memory_stage(OAuthFulfillmentStage)) self.request.session[SESSION_KEY_PLAN] = plan return redirect_with_qs( "authentik_core:if-flow", diff --git a/authentik/providers/oauth2/views/token.py b/authentik/providers/oauth2/views/token.py index c0d85a345c..7c78eed164 100644 --- a/authentik/providers/oauth2/views/token.py +++ b/authentik/providers/oauth2/views/token.py @@ -215,9 +215,11 @@ class TokenView(View): "access_token": refresh_token.access_token, "refresh_token": refresh_token.refresh_token, "token_type": "bearer", - "expires_in": timedelta_from_string( - self.params.provider.token_validity - ).seconds, + "expires_in": int( + timedelta_from_string( + self.params.provider.token_validity + ).total_seconds() + ), "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, "refresh_token": refresh_token.refresh_token, "token_type": "bearer", - "expires_in": timedelta_from_string( - refresh_token.provider.token_validity - ).seconds, + "expires_in": int( + timedelta_from_string( + refresh_token.provider.token_validity + ).total_seconds() + ), "id_token": self.params.provider.encode(refresh_token.id_token.to_dict()), } diff --git a/authentik/providers/saml/views/sso.py b/authentik/providers/saml/views/sso.py index 4eb76af688..732d582aa8 100644 --- a/authentik/providers/saml/views/sso.py +++ b/authentik/providers/saml/views/sso.py @@ -79,7 +79,7 @@ class SAMLSSOView(PolicyAccessView): PLAN_CONTEXT_CONSENT_PERMISSIONS: [], }, ) - plan.append(in_memory_stage(SAMLFlowFinalView)) + plan.append_stage(in_memory_stage(SAMLFlowFinalView)) request.session[SESSION_KEY_PLAN] = plan return redirect_with_qs( "authentik_core:if-flow", diff --git a/authentik/root/settings.py b/authentik/root/settings.py index f832299e81..d9977031ca 100644 --- a/authentik/root/settings.py +++ b/authentik/root/settings.py @@ -153,6 +153,7 @@ SPECTACULAR_SETTINGS = { "url": "https://github.com/goauthentik/authentik/blob/master/LICENSE", }, "ENUM_NAME_OVERRIDES": { + "EventActions": "authentik.events.models.EventAction", "ChallengeChoices": "authentik.flows.challenge.ChallengeTypes", "FlowDesignationEnum": "authentik.flows.models.FlowDesignation", "PolicyEngineMode": "authentik.policies.models.PolicyEngineMode", diff --git a/authentik/sources/saml/views.py b/authentik/sources/saml/views.py index 2685e3df44..d85496d3fd 100644 --- a/authentik/sources/saml/views.py +++ b/authentik/sources/saml/views.py @@ -90,7 +90,7 @@ class InitiateView(View): planner.allow_empty_flows = True plan = planner.plan(self.request, kwargs) for stage in stages_to_append: - plan.append(stage) + plan.append_stage(stage) self.request.session[SESSION_KEY_PLAN] = plan return redirect_with_qs( "authentik_core:if-flow", diff --git a/authentik/stages/authenticator_duo/stage.py b/authentik/stages/authenticator_duo/stage.py index abca61479d..96c5908c62 100644 --- a/authentik/stages/authenticator_duo/stage.py +++ b/authentik/stages/authenticator_duo/stage.py @@ -63,7 +63,7 @@ class AuthenticatorDuoStageView(ChallengeStageView): "type": ChallengeTypes.NATIVE.value, "activation_barcode": enroll["activation_barcode"], "activation_code": enroll["activation_code"], - "stage_uuid": stage.stage_uuid, + "stage_uuid": str(stage.stage_uuid), } ) diff --git a/authentik/stages/authenticator_validate/stage.py b/authentik/stages/authenticator_validate/stage.py index 6312bfbc44..719115512b 100644 --- a/authentik/stages/authenticator_validate/stage.py +++ b/authentik/stages/authenticator_validate/stage.py @@ -148,7 +148,7 @@ class AuthenticatorValidateStageView(ChallengeStageView): stage = Stage.objects.get_subclass(pk=stage.configuration_stage.pk) # plan.insert inserts at 1 index, so when stage_ok pops 0, # the configuration stage is next - self.executor.plan.insert(stage) + self.executor.plan.insert_stage(stage) return self.executor.stage_ok() return super().get(request, *args, **kwargs) diff --git a/authentik/stages/captcha/tests.py b/authentik/stages/captcha/tests.py index 3579438e58..8c863e2ee2 100644 --- a/authentik/stages/captcha/tests.py +++ b/authentik/stages/captcha/tests.py @@ -36,12 +36,14 @@ class TestCaptchaStage(TestCase): public_key=RECAPTCHA_PUBLIC_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): """Test valid captcha""" 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[SESSION_KEY_PLAN] = plan diff --git a/authentik/stages/consent/tests.py b/authentik/stages/consent/tests.py index d395af6a07..728b61649b 100644 --- a/authentik/stages/consent/tests.py +++ b/authentik/stages/consent/tests.py @@ -39,9 +39,11 @@ class TestConsentStage(TestCase): stage = ConsentStage.objects.create( 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[SESSION_KEY_PLAN] = plan session.save() @@ -69,11 +71,11 @@ class TestConsentStage(TestCase): designation=FlowDesignation.AUTHENTICATION, ) 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( flow_pk=flow.pk.hex, - stages=[stage], + bindings=[binding], markers=[StageMarker()], context={PLAN_CONTEXT_APPLICATION: self.application}, ) @@ -110,11 +112,11 @@ class TestConsentStage(TestCase): stage = ConsentStage.objects.create( 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( flow_pk=flow.pk.hex, - stages=[stage], + bindings=[binding], markers=[StageMarker()], context={PLAN_CONTEXT_APPLICATION: self.application}, ) diff --git a/authentik/stages/deny/tests.py b/authentik/stages/deny/tests.py index 0df3d9bfd4..9a15181ceb 100644 --- a/authentik/stages/deny/tests.py +++ b/authentik/stages/deny/tests.py @@ -26,12 +26,14 @@ class TestUserDenyStage(TestCase): designation=FlowDesignation.AUTHENTICATION, ) 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): """Test with a valid pending user and backend""" 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[SESSION_KEY_PLAN] = plan diff --git a/authentik/stages/email/tests/test_sending.py b/authentik/stages/email/tests/test_sending.py index 9b12201f5d..5467999b4b 100644 --- a/authentik/stages/email/tests/test_sending.py +++ b/authentik/stages/email/tests/test_sending.py @@ -34,12 +34,14 @@ class TestEmailStageSending(TestCase): self.stage = EmailStage.objects.create( 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): """Test with pending user""" 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 session = self.client.session @@ -67,7 +69,7 @@ class TestEmailStageSending(TestCase): def test_send_error(self): """Test error during sending (sending will be retried)""" 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 session = self.client.session diff --git a/authentik/stages/email/tests/test_stage.py b/authentik/stages/email/tests/test_stage.py index ae499b05b6..541e217508 100644 --- a/authentik/stages/email/tests/test_stage.py +++ b/authentik/stages/email/tests/test_stage.py @@ -35,12 +35,14 @@ class TestEmailStage(TestCase): self.stage = EmailStage.objects.create( 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): """Test with pending user""" 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 session = self.client.session @@ -56,7 +58,7 @@ class TestEmailStage(TestCase): def test_without_user(self): """Test without pending user""" 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[SESSION_KEY_PLAN] = plan @@ -71,7 +73,7 @@ class TestEmailStage(TestCase): def test_pending_user(self): """Test with pending user""" 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 session = self.client.session @@ -102,7 +104,7 @@ class TestEmailStage(TestCase): # Make sure token exists self.test_pending_user() 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[SESSION_KEY_PLAN] = plan diff --git a/authentik/stages/identification/stage.py b/authentik/stages/identification/stage.py index dc50b46244..88248e76cb 100644 --- a/authentik/stages/identification/stage.py +++ b/authentik/stages/identification/stage.py @@ -85,6 +85,18 @@ class IdentificationChallengeResponse(ChallengeResponse): identification_failed.send( 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.") self.pre_user = pre_user if not current_stage.password_stage: diff --git a/authentik/stages/invitation/tests.py b/authentik/stages/invitation/tests.py index 509e5fcb84..72e9f93bf9 100644 --- a/authentik/stages/invitation/tests.py +++ b/authentik/stages/invitation/tests.py @@ -35,7 +35,9 @@ class TestUserLoginStage(TestCase): designation=FlowDesignation.AUTHENTICATION, ) 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( "authentik.flows.views.to_stage_response", @@ -44,7 +46,7 @@ class TestUserLoginStage(TestCase): def test_without_invitation_fail(self): """Test without any invitation, continue_flow_without_invitation not set.""" 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_AUTHENTICATION_BACKEND] = BACKEND_DJANGO @@ -75,7 +77,7 @@ class TestUserLoginStage(TestCase): self.stage.continue_flow_without_invitation = True self.stage.save() 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_AUTHENTICATION_BACKEND] = BACKEND_DJANGO @@ -103,7 +105,7 @@ class TestUserLoginStage(TestCase): def test_with_invitation_get(self): """Test with invitation, check data in session""" 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[SESSION_KEY_PLAN] = plan @@ -143,7 +145,7 @@ class TestUserLoginStage(TestCase): ) 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} session = self.client.session diff --git a/authentik/stages/password/tests.py b/authentik/stages/password/tests.py index 8463604216..5a653451f1 100644 --- a/authentik/stages/password/tests.py +++ b/authentik/stages/password/tests.py @@ -39,7 +39,9 @@ class TestPasswordStage(TestCase): self.stage = PasswordStage.objects.create( 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( "authentik.flows.views.to_stage_response", @@ -48,7 +50,7 @@ class TestPasswordStage(TestCase): def test_without_user(self): """Test without user""" 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[SESSION_KEY_PLAN] = plan @@ -84,7 +86,7 @@ class TestPasswordStage(TestCase): ) 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[SESSION_KEY_PLAN] = plan @@ -101,7 +103,7 @@ class TestPasswordStage(TestCase): def test_valid_password(self): """Test with a valid pending user and valid password""" 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 session = self.client.session @@ -129,7 +131,7 @@ class TestPasswordStage(TestCase): def test_invalid_password(self): """Test with a valid pending user and invalid password""" 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 session = self.client.session @@ -148,7 +150,7 @@ class TestPasswordStage(TestCase): def test_invalid_password_lockout(self): """Test with a valid pending user and invalid password (trigger logout counter)""" 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 session = self.client.session @@ -189,7 +191,7 @@ class TestPasswordStage(TestCase): """Test with a valid pending user and valid password. Backend is patched to return PermissionError""" 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 session = self.client.session diff --git a/authentik/stages/prompt/stage.py b/authentik/stages/prompt/stage.py index 960d2dac46..7479ca4a26 100644 --- a/authentik/stages/prompt/stage.py +++ b/authentik/stages/prompt/stage.py @@ -90,6 +90,14 @@ class PromptChallengeResponse(ChallengeResponse): raise ValidationError(_("Passwords don't match.")) 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 password_fields: QuerySet[Prompt] = self.stage.fields.filter( type=FieldTypes.PASSWORD diff --git a/authentik/stages/prompt/tests.py b/authentik/stages/prompt/tests.py index 125b54bcc3..cc33e2acdf 100644 --- a/authentik/stages/prompt/tests.py +++ b/authentik/stages/prompt/tests.py @@ -78,6 +78,12 @@ class TestPromptStage(TestCase): required=True, 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.fields.set( [ @@ -88,6 +94,7 @@ class TestPromptStage(TestCase): password2_prompt, number_prompt, hidden_prompt, + static_prompt, ] ) self.stage.save() @@ -100,14 +107,17 @@ class TestPromptStage(TestCase): password2_prompt.field_key: "test", number_prompt.field_key: 3, 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): """Test render of form, check if all prompts are rendered correctly""" 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[SESSION_KEY_PLAN] = plan @@ -125,7 +135,7 @@ class TestPromptStage(TestCase): def test_valid_challenge_with_policy(self) -> PromptChallengeResponse: """Test challenge_response validation""" 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_policy = ExpressionPolicy.objects.create( @@ -142,7 +152,7 @@ class TestPromptStage(TestCase): def test_invalid_challenge(self) -> PromptChallengeResponse: """Test challenge_response validation""" 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_policy = ExpressionPolicy.objects.create( @@ -159,7 +169,7 @@ class TestPromptStage(TestCase): def test_valid_challenge_request(self): """Test a request with valid challenge_response data""" 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[SESSION_KEY_PLAN] = plan @@ -196,7 +206,7 @@ class TestPromptStage(TestCase): def test_invalid_password(self): """Test challenge_response validation""" 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" challenge_response = PromptChallengeResponse( @@ -215,7 +225,7 @@ class TestPromptStage(TestCase): def test_invalid_username(self): """Test challenge_response validation""" 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" 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") diff --git a/authentik/stages/user_delete/tests.py b/authentik/stages/user_delete/tests.py index 897194398b..e1e357d618 100644 --- a/authentik/stages/user_delete/tests.py +++ b/authentik/stages/user_delete/tests.py @@ -30,7 +30,9 @@ class TestUserDeleteStage(TestCase): designation=FlowDesignation.AUTHENTICATION, ) 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( "authentik.flows.views.to_stage_response", @@ -39,7 +41,7 @@ class TestUserDeleteStage(TestCase): def test_no_user(self): """Test without user set""" 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[SESSION_KEY_PLAN] = plan @@ -66,7 +68,7 @@ class TestUserDeleteStage(TestCase): def test_user_delete_get(self): """Test Form render""" 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 session = self.client.session diff --git a/authentik/stages/user_login/tests.py b/authentik/stages/user_login/tests.py index f5538e9b78..ebcb90569f 100644 --- a/authentik/stages/user_login/tests.py +++ b/authentik/stages/user_login/tests.py @@ -30,12 +30,14 @@ class TestUserLoginStage(TestCase): designation=FlowDesignation.AUTHENTICATION, ) 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): """Test with a valid pending user and backend""" 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 session = self.client.session @@ -61,7 +63,7 @@ class TestUserLoginStage(TestCase): self.stage.session_duration = "seconds=2" self.stage.save() 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 session = self.client.session @@ -92,7 +94,7 @@ class TestUserLoginStage(TestCase): def test_without_user(self): """Test a plan without any pending user, resulting in a denied""" 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[SESSION_KEY_PLAN] = plan diff --git a/authentik/stages/user_logout/tests.py b/authentik/stages/user_logout/tests.py index 9f706ba5d0..2472fc7ccc 100644 --- a/authentik/stages/user_logout/tests.py +++ b/authentik/stages/user_logout/tests.py @@ -28,12 +28,14 @@ class TestUserLogoutStage(TestCase): designation=FlowDesignation.AUTHENTICATION, ) 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): """Test with a valid pending user and backend""" 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_AUTHENTICATION_BACKEND] = BACKEND_DJANGO diff --git a/authentik/stages/user_write/api.py b/authentik/stages/user_write/api.py index 5acaa6ae19..9abac9ef2e 100644 --- a/authentik/stages/user_write/api.py +++ b/authentik/stages/user_write/api.py @@ -12,7 +12,7 @@ class UserWriteStageSerializer(StageSerializer): class Meta: model = UserWriteStage - fields = StageSerializer.Meta.fields + fields = StageSerializer.Meta.fields + ["create_users_as_inactive"] class UserWriteStageViewSet(UsedByMixin, ModelViewSet): diff --git a/authentik/stages/user_write/migrations/0003_userwritestage_create_users_as_inactive.py b/authentik/stages/user_write/migrations/0003_userwritestage_create_users_as_inactive.py new file mode 100644 index 0000000000..5ca4102914 --- /dev/null +++ b/authentik/stages/user_write/migrations/0003_userwritestage_create_users_as_inactive.py @@ -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.", + ), + ), + ] diff --git a/authentik/stages/user_write/models.py b/authentik/stages/user_write/models.py index eb37a89f64..be5b8d1d02 100644 --- a/authentik/stages/user_write/models.py +++ b/authentik/stages/user_write/models.py @@ -1,6 +1,7 @@ """write stage models""" from typing import Type +from django.db import models from django.utils.translation import gettext_lazy as _ from django.views import View 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, 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 def serializer(self) -> BaseSerializer: from authentik.stages.user_write.api import UserWriteStageSerializer diff --git a/authentik/stages/user_write/stage.py b/authentik/stages/user_write/stage.py index 61f3eb2756..554cd36d63 100644 --- a/authentik/stages/user_write/stage.py +++ b/authentik/stages/user_write/stage.py @@ -35,7 +35,9 @@ class UserWriteStageView(StageView): data = self.executor.plan.context[PLAN_CONTEXT_PROMPT] user_created = False 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[ PLAN_CONTEXT_AUTHENTICATION_BACKEND ] = class_to_path(ModelBackend) diff --git a/authentik/stages/user_write/tests.py b/authentik/stages/user_write/tests.py index 4d3b2767d1..bd8fea12cb 100644 --- a/authentik/stages/user_write/tests.py +++ b/authentik/stages/user_write/tests.py @@ -37,7 +37,9 @@ class TestUserWriteStage(TestCase): designation=FlowDesignation.AUTHENTICATION, ) 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") def test_user_create(self): @@ -48,7 +50,7 @@ class TestUserWriteStage(TestCase): ) 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] = { "username": "test-user", @@ -92,7 +94,7 @@ class TestUserWriteStage(TestCase): for _ in range(8) ) 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( username="unittest", email="test@beryju.org" @@ -135,7 +137,7 @@ class TestUserWriteStage(TestCase): def test_without_data(self): """Test without data results in error""" 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[SESSION_KEY_PLAN] = plan @@ -167,7 +169,7 @@ class TestUserWriteStage(TestCase): def test_blank_username(self): """Test with blank username results in error""" 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 plan.context[PLAN_CONTEXT_PROMPT] = { @@ -204,7 +206,7 @@ class TestUserWriteStage(TestCase): def test_duplicate_data(self): """Test with duplicate data, should trigger error""" 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 plan.context[PLAN_CONTEXT_PROMPT] = { diff --git a/authentik/tenants/api.py b/authentik/tenants/api.py index e5b7cc9576..673373f8da 100644 --- a/authentik/tenants/api.py +++ b/authentik/tenants/api.py @@ -54,6 +54,9 @@ class CurrentTenantSerializer(PassiveSerializer): 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) diff --git a/authentik/tenants/tests.py b/authentik/tenants/tests.py index 15fbfd7a89..4eab392c49 100644 --- a/authentik/tenants/tests.py +++ b/authentik/tenants/tests.py @@ -20,6 +20,8 @@ class TestTenants(TestCase): "branding_title": "authentik", "matched_domain": "authentik-default", "ui_footer_links": CONFIG.y("footer_links"), + "flow_authentication": "default-authentication-flow", + "flow_invalidation": "default-invalidation-flow", }, ) diff --git a/internal/outpost/ldap/instance_search.go b/internal/outpost/ldap/instance_search.go index c385068a05..0e459e03e7 100644 --- a/internal/outpost/ldap/instance_search.go +++ b/internal/outpost/ldap/instance_search.go @@ -99,15 +99,15 @@ func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry { } if *u.IsActive { - attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}}) + attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}}) } else { - attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}}) + attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}}) } if u.IsSuperuser { - attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"inactive"}}) - } else { 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)}) diff --git a/schema.yml b/schema.yml index a0982f0186..aa0a6127cf 100644 --- a/schema.yml +++ b/schema.yml @@ -3572,6 +3572,37 @@ paths: $ref: '#/components/schemas/ValidationError' '403': $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}/: get: operationId: events_events_retrieve @@ -3600,6 +3631,106 @@ paths: $ref: '#/components/schemas/ValidationError' '403': $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/: get: operationId: events_events_actions_list @@ -4441,6 +4572,18 @@ paths: schema: type: string 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 name: order schema: @@ -18759,6 +18902,12 @@ components: name: Documentation - href: https://goauthentik.io/ name: authentik Website + flow_authentication: + type: string + flow_invalidation: + type: string + flow_recovery: + type: string flow_unenrollment: type: string required: @@ -19242,7 +19391,7 @@ components: type: object additionalProperties: {} action: - type: string + $ref: '#/components/schemas/EventActions' app: type: string context: @@ -19266,6 +19415,34 @@ components: - app - created - 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: type: object description: Event Matcher Policy Serializer @@ -19296,7 +19473,7 @@ components: readOnly: true action: allOf: - - $ref: '#/components/schemas/EventMatcherPolicyActionEnum' + - $ref: '#/components/schemas/EventActions' description: Match created events with this action type. When left empty, all action types will be matched. client_ip: @@ -19314,34 +19491,6 @@ components: - pk - verbose_name - 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: type: object description: Event Matcher Policy Serializer @@ -19355,7 +19504,7 @@ components: will be logged. By default, only execution errors are logged. action: allOf: - - $ref: '#/components/schemas/EventMatcherPolicyActionEnum' + - $ref: '#/components/schemas/EventActions' description: Match created events with this action type. When left empty, all action types will be matched. client_ip: @@ -19375,7 +19524,7 @@ components: type: object additionalProperties: {} action: - type: string + $ref: '#/components/schemas/EventActions' app: type: string context: @@ -19673,6 +19822,13 @@ components: minimum: -2147483648 policy_engine_mode: $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: - order - pk @@ -19703,6 +19859,13 @@ components: minimum: -2147483648 policy_engine_mode: $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: - order - stage @@ -20048,6 +20211,12 @@ components: - api - recovery type: string + InvalidResponseActionEnum: + enum: + - retry + - restart + - restart_with_context + type: string Invitation: type: object description: Invitation Serializer @@ -24445,7 +24614,7 @@ components: will be logged. By default, only execution errors are logged. action: allOf: - - $ref: '#/components/schemas/EventMatcherPolicyActionEnum' + - $ref: '#/components/schemas/EventActions' description: Match created events with this action type. When left empty, all action types will be matched. client_ip: @@ -24457,6 +24626,29 @@ components: - $ref: '#/components/schemas/AppEnum' description: Match events created by selected application. When left empty, 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: type: object description: Group Membership Policy Serializer @@ -24518,6 +24710,13 @@ components: minimum: -2147483648 policy_engine_mode: $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: type: object description: Group Serializer @@ -25603,6 +25802,9 @@ components: type: array items: $ref: '#/components/schemas/FlowRequest' + create_users_as_inactive: + type: boolean + description: When set, newly created users are inactive and cannot login. PatchedWebAuthnDeviceRequest: type: object description: Serializer for WebAuthn authenticator devices @@ -28097,6 +28299,9 @@ components: type: array items: $ref: '#/components/schemas/Flow' + create_users_as_inactive: + type: boolean + description: When set, newly created users are inactive and cannot login. required: - component - name @@ -28113,6 +28318,9 @@ components: type: array items: $ref: '#/components/schemas/FlowRequest' + create_users_as_inactive: + type: boolean + description: When set, newly created users are inactive and cannot login. required: - name ValidationError: diff --git a/web/package-lock.json b/web/package-lock.json index 2cbb3c2487..b65f46cd4d 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -18,24 +18,24 @@ "@lingui/cli": "^3.10.2", "@lingui/core": "^3.10.4", "@lingui/macro": "^3.10.2", - "@patternfly/patternfly": "^4.108.2", + "@patternfly/patternfly": "^4.115.2", "@polymer/iron-form": "^3.0.1", "@polymer/paper-input": "^3.2.1", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-replace": "^2.4.2", "@rollup/plugin-typescript": "^8.2.1", - "@sentry/browser": "^6.7.2", - "@sentry/tracing": "^6.7.2", + "@sentry/browser": "^6.8.0", + "@sentry/tracing": "^6.8.0", "@types/chart.js": "^2.9.32", - "@types/codemirror": "5.60.0", + "@types/codemirror": "5.60.1", "@types/grecaptcha": "^3.0.2", - "@typescript-eslint/eslint-plugin": "^4.28.0", - "@typescript-eslint/parser": "^4.28.0", + "@typescript-eslint/eslint-plugin": "^4.28.1", + "@typescript-eslint/parser": "^4.28.1", "@webcomponents/webcomponentsjs": "^2.5.0", "authentik-api": "file:api", "babel-plugin-macros": "^3.1.0", "base64-js": "^1.5.1", - "chart.js": "^3.3.2", + "chart.js": "^3.4.0", "chartjs-adapter-moment": "^1.0.0", "codemirror": "^5.62.0", "construct-style-sheets-polyfill": "^2.4.16", @@ -48,7 +48,7 @@ "lit-html": "^1.4.1", "moment": "^2.29.1", "rapidoc": "^9.0.0", - "rollup": "^2.52.2", + "rollup": "^2.52.3", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-copy": "^3.4.0", "rollup-plugin-cssimport": "^1.0.2", @@ -2120,9 +2120,9 @@ } }, "node_modules/@patternfly/patternfly": { - "version": "4.108.2", - "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.108.2.tgz", - "integrity": "sha512-z0VB+1CXcH+eoClYQABwapX5FURSvm1nPr6asLWwg/Z4Wuxs0RjZpC6Gb+KRm8nGQwSAcMKZY1jLfPqVnznQnw==" + "version": "4.115.2", + "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.115.2.tgz", + "integrity": "sha512-7hbJ4pRmj+rlXclD2F/UwceO6fS+9flGsgHc4eUc7NyTN2GXl6PLcqrjE2CtiKEPV90+KwsGQGJXZj8bz9HweA==" }, "node_modules/@polymer/font-roboto": { "version": "3.0.2", @@ -2314,13 +2314,13 @@ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" }, "node_modules/@sentry/browser": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.2.tgz", - "integrity": "sha512-Lv0Ne1QcesyGAhVcQDfQa3hDPR/MhPSDTMg3xFi+LxqztchVc4w/ynzR0wCZFb8KIHpTj5SpJHfxpDhXYMtS9g==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.8.0.tgz", + "integrity": "sha512-nxa71csHlG5sMHUxI4e4xxuCWtbCv/QbBfMsYw7ncJSfCKG3yNlCVh8NJ7NS0rZW/MJUT6S6+r93zw0HetNDOA==", "dependencies": { - "@sentry/core": "6.7.2", - "@sentry/types": "6.7.2", - "@sentry/utils": "6.7.2", + "@sentry/core": "6.8.0", + "@sentry/types": "6.8.0", + "@sentry/utils": "6.8.0", "tslib": "^1.9.3" }, "engines": { @@ -2333,14 +2333,14 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/core": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.2.tgz", - "integrity": "sha512-NTZqwN5nR94yrXmSfekoPs1mIFuKvf8esdIW/DadwSKWAdLJwQTJY9xK/8PQv+SEzd7wiitPAx+mCw2By1xiNQ==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.8.0.tgz", + "integrity": "sha512-vJzWt/znEB+JqVwtwfjkRrAYRN+ep+l070Ti8GhJnvwU4IDtVlV3T/jVNrj6rl6UChcczaJQMxVxtG5x0crlAA==", "dependencies": { - "@sentry/hub": "6.7.2", - "@sentry/minimal": "6.7.2", - "@sentry/types": "6.7.2", - "@sentry/utils": "6.7.2", + "@sentry/hub": "6.8.0", + "@sentry/minimal": "6.8.0", + "@sentry/types": "6.8.0", + "@sentry/utils": "6.8.0", "tslib": "^1.9.3" }, "engines": { @@ -2353,12 +2353,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/hub": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.2.tgz", - "integrity": "sha512-05qVW6ymChJsXag4+fYCQokW3AcABIgcqrVYZUBf6GMU/Gbz5SJqpV7y1+njwWvnPZydMncP9LaDVpMKbE7UYQ==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.8.0.tgz", + "integrity": "sha512-hFrI2Ss1fTov7CH64FJpigqRxH7YvSnGeqxT9Jc1BL7nzW/vgCK+Oh2mOZbosTcrzoDv+lE8ViOnSN3w/fo+rg==", "dependencies": { - "@sentry/types": "6.7.2", - "@sentry/utils": "6.7.2", + "@sentry/types": "6.8.0", + "@sentry/utils": "6.8.0", "tslib": "^1.9.3" }, "engines": { @@ -2371,12 +2371,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/minimal": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.2.tgz", - "integrity": "sha512-jkpwFv2GFHoVl5vnK+9/Q+Ea8eVdbJ3hn3/Dqq9MOLFnVK7ED6MhdHKLT79puGSFj+85OuhM5m2Q44mIhyS5mw==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.8.0.tgz", + "integrity": "sha512-MRxUKXiiYwKjp8mOQMpTpEuIby1Jh3zRTU0cmGZtfsZ38BC1JOle8xlwC4FdtOH+VvjSYnPBMya5lgNHNPUJDQ==", "dependencies": { - "@sentry/hub": "6.7.2", - "@sentry/types": "6.7.2", + "@sentry/hub": "6.8.0", + "@sentry/types": "6.8.0", "tslib": "^1.9.3" }, "engines": { @@ -2389,14 +2389,14 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/tracing": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.2.tgz", - "integrity": "sha512-juKlI7FICKONWJFJxDxerj0A+8mNRhmtrdR+OXFqOkqSAy/QXlSFZcA/j//O19k2CfwK1BrvoMcQ/4gnffUOVg==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.8.0.tgz", + "integrity": "sha512-3gDkQnmOuOjHz5rY7BOatLEUksANU3efR8wuBa2ujsPQvoLSLFuyZpRjPPsxuUHQOqAYIbSNAoDloXECvQeHjw==", "dependencies": { - "@sentry/hub": "6.7.2", - "@sentry/minimal": "6.7.2", - "@sentry/types": "6.7.2", - "@sentry/utils": "6.7.2", + "@sentry/hub": "6.8.0", + "@sentry/minimal": "6.8.0", + "@sentry/types": "6.8.0", + "@sentry/utils": "6.8.0", "tslib": "^1.9.3" }, "engines": { @@ -2409,19 +2409,19 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/types": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.2.tgz", - "integrity": "sha512-h21Go/PfstUN+ZV6SbwRSZVg9GXRJWdLfHoO5PSVb3TVEMckuxk8tAE57/u+UZDwX8wu+Xyon2TgsKpiWKxqUg==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.8.0.tgz", + "integrity": "sha512-PbSxqlh6Fd5thNU5f8EVYBVvX+G7XdPA+ThNb2QvSK8yv3rIf0McHTyF6sIebgJ38OYN7ZFK7vvhC/RgSAfYTA==", "engines": { "node": ">=6" } }, "node_modules/@sentry/utils": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.2.tgz", - "integrity": "sha512-9COL7aaBbe61Hp5BlArtXZ1o/cxli1NGONLPrVT4fMyeQFmLonhUiy77NdsW19XnvhvaA+2IoV5dg3dnFiF/og==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.8.0.tgz", + "integrity": "sha512-OYlI2JNrcWKMdvYbWNdQwR4QBVv2V0y5wK0U6f53nArv6RsyO5TzwRu5rMVSIZofUUqjoE5hl27jqnR+vpUrsA==", "dependencies": { - "@sentry/types": "6.7.2", + "@sentry/types": "6.8.0", "tslib": "^1.9.3" }, "engines": { @@ -2451,9 +2451,9 @@ } }, "node_modules/@types/codemirror": { - "version": "5.60.0", - "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.0.tgz", - "integrity": "sha512-xgzXZyCzedLRNC67/Nn8rpBtTFnAsX2C+Q/LGoH6zgcpF/LqdNHJMHEOhqT1bwUcSp6kQdOIuKzRbeW9DYhEhg==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.1.tgz", + "integrity": "sha512-yV14LQ5VvghnW0uSuCw2bEfZC6NvxHQEckl2w3dEk5l0yPGzQh14dCaWvG5KD/2l3cgFSifR+6nIUD7LDLdUTg==", "dependencies": { "@types/tern": "*" } @@ -2579,12 +2579,12 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz", - "integrity": "sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.1.tgz", + "integrity": "sha512-9yfcNpDaNGQ6/LQOX/KhUFTR1sCKH+PBr234k6hI9XJ0VP5UqGxap0AnNwBnWFk1MNyWBylJH9ZkzBXC+5akZQ==", "dependencies": { - "@typescript-eslint/experimental-utils": "4.28.0", - "@typescript-eslint/scope-manager": "4.28.0", + "@typescript-eslint/experimental-utils": "4.28.1", + "@typescript-eslint/scope-manager": "4.28.1", "debug": "^4.3.1", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.1.0", @@ -2609,14 +2609,14 @@ } }, "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz", - "integrity": "sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.1.tgz", + "integrity": "sha512-n8/ggadrZ+uyrfrSEchx3jgODdmcx7MzVM2sI3cTpI/YlfSm0+9HEUaWw3aQn2urL2KYlWYMDgn45iLfjDYB+Q==", "dependencies": { "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.28.0", - "@typescript-eslint/types": "4.28.0", - "@typescript-eslint/typescript-estree": "4.28.0", + "@typescript-eslint/scope-manager": "4.28.1", + "@typescript-eslint/types": "4.28.1", + "@typescript-eslint/typescript-estree": "4.28.1", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" }, @@ -2649,13 +2649,13 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.0.tgz", - "integrity": "sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.1.tgz", + "integrity": "sha512-UjrMsgnhQIIK82hXGaD+MCN8IfORS1CbMdu7VlZbYa8LCZtbZjJA26De4IPQB7XYZbL8gJ99KWNj0l6WD0guJg==", "dependencies": { - "@typescript-eslint/scope-manager": "4.28.0", - "@typescript-eslint/types": "4.28.0", - "@typescript-eslint/typescript-estree": "4.28.0", + "@typescript-eslint/scope-manager": "4.28.1", + "@typescript-eslint/types": "4.28.1", + "@typescript-eslint/typescript-estree": "4.28.1", "debug": "^4.3.1" }, "engines": { @@ -2675,12 +2675,12 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz", - "integrity": "sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.1.tgz", + "integrity": "sha512-o95bvGKfss6705x7jFGDyS7trAORTy57lwJ+VsYwil/lOUxKQ9tA7Suuq+ciMhJc/1qPwB3XE2DKh9wubW8YYA==", "dependencies": { - "@typescript-eslint/types": "4.28.0", - "@typescript-eslint/visitor-keys": "4.28.0" + "@typescript-eslint/types": "4.28.1", + "@typescript-eslint/visitor-keys": "4.28.1" }, "engines": { "node": "^8.10.0 || ^10.13.0 || >=11.10.1" @@ -2691,9 +2691,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.0.tgz", - "integrity": "sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.1.tgz", + "integrity": "sha512-4z+knEihcyX7blAGi7O3Fm3O6YRCP+r56NJFMNGsmtdw+NCdpG5SgNz427LS9nQkRVTswZLhz484hakQwB8RRg==", "engines": { "node": "^8.10.0 || ^10.13.0 || >=11.10.1" }, @@ -2703,12 +2703,12 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz", - "integrity": "sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.1.tgz", + "integrity": "sha512-GhKxmC4sHXxHGJv8e8egAZeTZ6HI4mLU6S7FUzvFOtsk7ZIDN1ksA9r9DyOgNqowA9yAtZXV0Uiap61bIO81FQ==", "dependencies": { - "@typescript-eslint/types": "4.28.0", - "@typescript-eslint/visitor-keys": "4.28.0", + "@typescript-eslint/types": "4.28.1", + "@typescript-eslint/visitor-keys": "4.28.1", "debug": "^4.3.1", "globby": "^11.0.3", "is-glob": "^4.0.1", @@ -2748,11 +2748,11 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz", - "integrity": "sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.1.tgz", + "integrity": "sha512-K4HMrdFqr9PFquPu178SaSb92CaWe2yErXyPumc8cYWxFmhgJsNY9eSePmO05j0JhBvf2Cdhptd6E6Yv9HVHcg==", "dependencies": { - "@typescript-eslint/types": "4.28.0", + "@typescript-eslint/types": "4.28.1", "eslint-visitor-keys": "^2.0.0" }, "engines": { @@ -3316,9 +3316,9 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "node_modules/chart.js": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.3.2.tgz", - "integrity": "sha512-H0hSO7xqTIrwxoACqnSoNromEMfXvfuVnrbuSt2TuXfBDDofbnto4zuZlRtRvC73/b37q3wGAWZyUU41QPvNbA==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.4.0.tgz", + "integrity": "sha512-mJsRm2apQm5mwz2OgYqGNG4erZh/qljcRZkWSa0kLkFr3UC3e1wKRMgnIh6WdhUrNu0w/JT9PkjLyylqEqHXEQ==" }, "node_modules/chartjs-adapter-moment": { "version": "1.0.0", @@ -6770,9 +6770,9 @@ } }, "node_modules/rollup": { - "version": "2.52.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz", - "integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==", + "version": "2.52.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.3.tgz", + "integrity": "sha512-QF3Sju8Kl2z0osI4unyOLyUudyhOMK6G0AeqJWgfiyigqLAlnNrfBcDWDx+f1cqn+JU2iIYVkDrgQ6/KtwEfrg==", "bin": { "rollup": "dist/bin/rollup" }, @@ -9482,9 +9482,9 @@ } }, "@patternfly/patternfly": { - "version": "4.108.2", - "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.108.2.tgz", - "integrity": "sha512-z0VB+1CXcH+eoClYQABwapX5FURSvm1nPr6asLWwg/Z4Wuxs0RjZpC6Gb+KRm8nGQwSAcMKZY1jLfPqVnznQnw==" + "version": "4.115.2", + "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.115.2.tgz", + "integrity": "sha512-7hbJ4pRmj+rlXclD2F/UwceO6fS+9flGsgHc4eUc7NyTN2GXl6PLcqrjE2CtiKEPV90+KwsGQGJXZj8bz9HweA==" }, "@polymer/font-roboto": { "version": "3.0.2", @@ -9669,13 +9669,13 @@ } }, "@sentry/browser": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.7.2.tgz", - "integrity": "sha512-Lv0Ne1QcesyGAhVcQDfQa3hDPR/MhPSDTMg3xFi+LxqztchVc4w/ynzR0wCZFb8KIHpTj5SpJHfxpDhXYMtS9g==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.8.0.tgz", + "integrity": "sha512-nxa71csHlG5sMHUxI4e4xxuCWtbCv/QbBfMsYw7ncJSfCKG3yNlCVh8NJ7NS0rZW/MJUT6S6+r93zw0HetNDOA==", "requires": { - "@sentry/core": "6.7.2", - "@sentry/types": "6.7.2", - "@sentry/utils": "6.7.2", + "@sentry/core": "6.8.0", + "@sentry/types": "6.8.0", + "@sentry/utils": "6.8.0", "tslib": "^1.9.3" }, "dependencies": { @@ -9687,14 +9687,14 @@ } }, "@sentry/core": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.7.2.tgz", - "integrity": "sha512-NTZqwN5nR94yrXmSfekoPs1mIFuKvf8esdIW/DadwSKWAdLJwQTJY9xK/8PQv+SEzd7wiitPAx+mCw2By1xiNQ==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.8.0.tgz", + "integrity": "sha512-vJzWt/znEB+JqVwtwfjkRrAYRN+ep+l070Ti8GhJnvwU4IDtVlV3T/jVNrj6rl6UChcczaJQMxVxtG5x0crlAA==", "requires": { - "@sentry/hub": "6.7.2", - "@sentry/minimal": "6.7.2", - "@sentry/types": "6.7.2", - "@sentry/utils": "6.7.2", + "@sentry/hub": "6.8.0", + "@sentry/minimal": "6.8.0", + "@sentry/types": "6.8.0", + "@sentry/utils": "6.8.0", "tslib": "^1.9.3" }, "dependencies": { @@ -9706,12 +9706,12 @@ } }, "@sentry/hub": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.7.2.tgz", - "integrity": "sha512-05qVW6ymChJsXag4+fYCQokW3AcABIgcqrVYZUBf6GMU/Gbz5SJqpV7y1+njwWvnPZydMncP9LaDVpMKbE7UYQ==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.8.0.tgz", + "integrity": "sha512-hFrI2Ss1fTov7CH64FJpigqRxH7YvSnGeqxT9Jc1BL7nzW/vgCK+Oh2mOZbosTcrzoDv+lE8ViOnSN3w/fo+rg==", "requires": { - "@sentry/types": "6.7.2", - "@sentry/utils": "6.7.2", + "@sentry/types": "6.8.0", + "@sentry/utils": "6.8.0", "tslib": "^1.9.3" }, "dependencies": { @@ -9723,12 +9723,12 @@ } }, "@sentry/minimal": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.7.2.tgz", - "integrity": "sha512-jkpwFv2GFHoVl5vnK+9/Q+Ea8eVdbJ3hn3/Dqq9MOLFnVK7ED6MhdHKLT79puGSFj+85OuhM5m2Q44mIhyS5mw==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.8.0.tgz", + "integrity": "sha512-MRxUKXiiYwKjp8mOQMpTpEuIby1Jh3zRTU0cmGZtfsZ38BC1JOle8xlwC4FdtOH+VvjSYnPBMya5lgNHNPUJDQ==", "requires": { - "@sentry/hub": "6.7.2", - "@sentry/types": "6.7.2", + "@sentry/hub": "6.8.0", + "@sentry/types": "6.8.0", "tslib": "^1.9.3" }, "dependencies": { @@ -9740,14 +9740,14 @@ } }, "@sentry/tracing": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.7.2.tgz", - "integrity": "sha512-juKlI7FICKONWJFJxDxerj0A+8mNRhmtrdR+OXFqOkqSAy/QXlSFZcA/j//O19k2CfwK1BrvoMcQ/4gnffUOVg==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.8.0.tgz", + "integrity": "sha512-3gDkQnmOuOjHz5rY7BOatLEUksANU3efR8wuBa2ujsPQvoLSLFuyZpRjPPsxuUHQOqAYIbSNAoDloXECvQeHjw==", "requires": { - "@sentry/hub": "6.7.2", - "@sentry/minimal": "6.7.2", - "@sentry/types": "6.7.2", - "@sentry/utils": "6.7.2", + "@sentry/hub": "6.8.0", + "@sentry/minimal": "6.8.0", + "@sentry/types": "6.8.0", + "@sentry/utils": "6.8.0", "tslib": "^1.9.3" }, "dependencies": { @@ -9759,16 +9759,16 @@ } }, "@sentry/types": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.7.2.tgz", - "integrity": "sha512-h21Go/PfstUN+ZV6SbwRSZVg9GXRJWdLfHoO5PSVb3TVEMckuxk8tAE57/u+UZDwX8wu+Xyon2TgsKpiWKxqUg==" + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.8.0.tgz", + "integrity": "sha512-PbSxqlh6Fd5thNU5f8EVYBVvX+G7XdPA+ThNb2QvSK8yv3rIf0McHTyF6sIebgJ38OYN7ZFK7vvhC/RgSAfYTA==" }, "@sentry/utils": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.7.2.tgz", - "integrity": "sha512-9COL7aaBbe61Hp5BlArtXZ1o/cxli1NGONLPrVT4fMyeQFmLonhUiy77NdsW19XnvhvaA+2IoV5dg3dnFiF/og==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.8.0.tgz", + "integrity": "sha512-OYlI2JNrcWKMdvYbWNdQwR4QBVv2V0y5wK0U6f53nArv6RsyO5TzwRu5rMVSIZofUUqjoE5hl27jqnR+vpUrsA==", "requires": { - "@sentry/types": "6.7.2", + "@sentry/types": "6.8.0", "tslib": "^1.9.3" }, "dependencies": { @@ -9797,9 +9797,9 @@ } }, "@types/codemirror": { - "version": "5.60.0", - "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.0.tgz", - "integrity": "sha512-xgzXZyCzedLRNC67/Nn8rpBtTFnAsX2C+Q/LGoH6zgcpF/LqdNHJMHEOhqT1bwUcSp6kQdOIuKzRbeW9DYhEhg==", + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.1.tgz", + "integrity": "sha512-yV14LQ5VvghnW0uSuCw2bEfZC6NvxHQEckl2w3dEk5l0yPGzQh14dCaWvG5KD/2l3cgFSifR+6nIUD7LDLdUTg==", "requires": { "@types/tern": "*" } @@ -9925,12 +9925,12 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==" }, "@typescript-eslint/eslint-plugin": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.0.tgz", - "integrity": "sha512-KcF6p3zWhf1f8xO84tuBailV5cN92vhS+VT7UJsPzGBm9VnQqfI9AsiMUFUCYHTYPg1uCCo+HyiDnpDuvkAMfQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.1.tgz", + "integrity": "sha512-9yfcNpDaNGQ6/LQOX/KhUFTR1sCKH+PBr234k6hI9XJ0VP5UqGxap0AnNwBnWFk1MNyWBylJH9ZkzBXC+5akZQ==", "requires": { - "@typescript-eslint/experimental-utils": "4.28.0", - "@typescript-eslint/scope-manager": "4.28.0", + "@typescript-eslint/experimental-utils": "4.28.1", + "@typescript-eslint/scope-manager": "4.28.1", "debug": "^4.3.1", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.1.0", @@ -9939,14 +9939,14 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.0.tgz", - "integrity": "sha512-9XD9s7mt3QWMk82GoyUpc/Ji03vz4T5AYlHF9DcoFNfJ/y3UAclRsfGiE2gLfXtyC+JRA3trR7cR296TEb1oiQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.1.tgz", + "integrity": "sha512-n8/ggadrZ+uyrfrSEchx3jgODdmcx7MzVM2sI3cTpI/YlfSm0+9HEUaWw3aQn2urL2KYlWYMDgn45iLfjDYB+Q==", "requires": { "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.28.0", - "@typescript-eslint/types": "4.28.0", - "@typescript-eslint/typescript-estree": "4.28.0", + "@typescript-eslint/scope-manager": "4.28.1", + "@typescript-eslint/types": "4.28.1", + "@typescript-eslint/typescript-estree": "4.28.1", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" }, @@ -9962,37 +9962,37 @@ } }, "@typescript-eslint/parser": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.0.tgz", - "integrity": "sha512-7x4D22oPY8fDaOCvkuXtYYTQ6mTMmkivwEzS+7iml9F9VkHGbbZ3x4fHRwxAb5KeuSkLqfnYjs46tGx2Nour4A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.1.tgz", + "integrity": "sha512-UjrMsgnhQIIK82hXGaD+MCN8IfORS1CbMdu7VlZbYa8LCZtbZjJA26De4IPQB7XYZbL8gJ99KWNj0l6WD0guJg==", "requires": { - "@typescript-eslint/scope-manager": "4.28.0", - "@typescript-eslint/types": "4.28.0", - "@typescript-eslint/typescript-estree": "4.28.0", + "@typescript-eslint/scope-manager": "4.28.1", + "@typescript-eslint/types": "4.28.1", + "@typescript-eslint/typescript-estree": "4.28.1", "debug": "^4.3.1" } }, "@typescript-eslint/scope-manager": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.0.tgz", - "integrity": "sha512-eCALCeScs5P/EYjwo6se9bdjtrh8ByWjtHzOkC4Tia6QQWtQr3PHovxh3TdYTuFcurkYI4rmFsRFpucADIkseg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.1.tgz", + "integrity": "sha512-o95bvGKfss6705x7jFGDyS7trAORTy57lwJ+VsYwil/lOUxKQ9tA7Suuq+ciMhJc/1qPwB3XE2DKh9wubW8YYA==", "requires": { - "@typescript-eslint/types": "4.28.0", - "@typescript-eslint/visitor-keys": "4.28.0" + "@typescript-eslint/types": "4.28.1", + "@typescript-eslint/visitor-keys": "4.28.1" } }, "@typescript-eslint/types": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.0.tgz", - "integrity": "sha512-p16xMNKKoiJCVZY5PW/AfILw2xe1LfruTcfAKBj3a+wgNYP5I9ZEKNDOItoRt53p4EiPV6iRSICy8EPanG9ZVA==" + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.1.tgz", + "integrity": "sha512-4z+knEihcyX7blAGi7O3Fm3O6YRCP+r56NJFMNGsmtdw+NCdpG5SgNz427LS9nQkRVTswZLhz484hakQwB8RRg==" }, "@typescript-eslint/typescript-estree": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.0.tgz", - "integrity": "sha512-m19UQTRtxMzKAm8QxfKpvh6OwQSXaW1CdZPoCaQuLwAq7VZMNuhJmZR4g5281s2ECt658sldnJfdpSZZaxUGMQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.1.tgz", + "integrity": "sha512-GhKxmC4sHXxHGJv8e8egAZeTZ6HI4mLU6S7FUzvFOtsk7ZIDN1ksA9r9DyOgNqowA9yAtZXV0Uiap61bIO81FQ==", "requires": { - "@typescript-eslint/types": "4.28.0", - "@typescript-eslint/visitor-keys": "4.28.0", + "@typescript-eslint/types": "4.28.1", + "@typescript-eslint/visitor-keys": "4.28.1", "debug": "^4.3.1", "globby": "^11.0.3", "is-glob": "^4.0.1", @@ -10016,11 +10016,11 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.0.tgz", - "integrity": "sha512-PjJyTWwrlrvM5jazxYF5ZPs/nl0kHDZMVbuIcbpawVXaDPelp3+S9zpOz5RmVUfS/fD5l5+ZXNKnWhNYjPzCvw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.1.tgz", + "integrity": "sha512-K4HMrdFqr9PFquPu178SaSb92CaWe2yErXyPumc8cYWxFmhgJsNY9eSePmO05j0JhBvf2Cdhptd6E6Yv9HVHcg==", "requires": { - "@typescript-eslint/types": "4.28.0", + "@typescript-eslint/types": "4.28.1", "eslint-visitor-keys": "^2.0.0" } }, @@ -10461,9 +10461,9 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "chart.js": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.3.2.tgz", - "integrity": "sha512-H0hSO7xqTIrwxoACqnSoNromEMfXvfuVnrbuSt2TuXfBDDofbnto4zuZlRtRvC73/b37q3wGAWZyUU41QPvNbA==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.4.0.tgz", + "integrity": "sha512-mJsRm2apQm5mwz2OgYqGNG4erZh/qljcRZkWSa0kLkFr3UC3e1wKRMgnIh6WdhUrNu0w/JT9PkjLyylqEqHXEQ==" }, "chartjs-adapter-moment": { "version": "1.0.0", @@ -13200,9 +13200,9 @@ } }, "rollup": { - "version": "2.52.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.2.tgz", - "integrity": "sha512-4RlFC3k2BIHlUsJ9mGd8OO+9Lm2eDF5P7+6DNQOp5sx+7N/1tFM01kELfbxlMX3MxT6owvLB1ln4S3QvvQlbUA==", + "version": "2.52.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.3.tgz", + "integrity": "sha512-QF3Sju8Kl2z0osI4unyOLyUudyhOMK6G0AeqJWgfiyigqLAlnNrfBcDWDx+f1cqn+JU2iIYVkDrgQ6/KtwEfrg==", "requires": { "fsevents": "~2.3.2" } diff --git a/web/package.json b/web/package.json index 3df6200404..14b11d0ebd 100644 --- a/web/package.json +++ b/web/package.json @@ -47,24 +47,24 @@ "@lingui/cli": "^3.10.2", "@lingui/core": "^3.10.4", "@lingui/macro": "^3.10.2", - "@patternfly/patternfly": "^4.108.2", + "@patternfly/patternfly": "^4.115.2", "@polymer/iron-form": "^3.0.1", "@polymer/paper-input": "^3.2.1", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-replace": "^2.4.2", "@rollup/plugin-typescript": "^8.2.1", - "@sentry/browser": "^6.7.2", - "@sentry/tracing": "^6.7.2", + "@sentry/browser": "^6.8.0", + "@sentry/tracing": "^6.8.0", "@types/chart.js": "^2.9.32", - "@types/codemirror": "5.60.0", + "@types/codemirror": "5.60.1", "@types/grecaptcha": "^3.0.2", - "@typescript-eslint/eslint-plugin": "^4.28.0", - "@typescript-eslint/parser": "^4.28.0", + "@typescript-eslint/eslint-plugin": "^4.28.1", + "@typescript-eslint/parser": "^4.28.1", "@webcomponents/webcomponentsjs": "^2.5.0", "authentik-api": "file:api", "babel-plugin-macros": "^3.1.0", "base64-js": "^1.5.1", - "chart.js": "^3.3.2", + "chart.js": "^3.4.0", "chartjs-adapter-moment": "^1.0.0", "codemirror": "^5.62.0", "construct-style-sheets-polyfill": "^2.4.16", @@ -77,7 +77,7 @@ "lit-html": "^1.4.1", "moment": "^2.29.1", "rapidoc": "^9.0.0", - "rollup": "^2.52.2", + "rollup": "^2.52.3", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-copy": "^3.4.0", "rollup-plugin-cssimport": "^1.0.2", diff --git a/web/src/authentik.css b/web/src/authentik.css index bb409f2f15..312af5a081 100644 --- a/web/src/authentik.css +++ b/web/src/authentik.css @@ -139,6 +139,7 @@ body { /* Card */ .pf-c-card { --pf-c-card--BackgroundColor: var(--ak-dark-background-light); + color: var(--ak-dark-foreground); } .pf-c-card__title, .pf-c-card__body { diff --git a/web/src/flows/sources/plex/PlexLoginInit.ts b/web/src/flows/sources/plex/PlexLoginInit.ts index ebfe8262d3..dbf6d5fe6c 100644 --- a/web/src/flows/sources/plex/PlexLoginInit.ts +++ b/web/src/flows/sources/plex/PlexLoginInit.ts @@ -1,5 +1,5 @@ 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 PFForm from "@patternfly/patternfly/components/Form/form.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 { showMessage } from "../../../elements/messages/MessageContainer"; import { MessageLevel } from "../../../elements/messages/Message"; -import { PlexAuthenticationChallengeResponseRequest } from "authentik-api/dist/models/PlexAuthenticationChallengeResponseRequest"; @customElement("ak-flow-sources-plex") diff --git a/web/src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts b/web/src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts index 7eacf3dd90..0dd9720bde 100644 --- a/web/src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts +++ b/web/src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts @@ -11,9 +11,8 @@ import { BaseStage } from "../base"; import "../../../elements/forms/FormElement"; import "../../../elements/EmptyState"; import "../../FormStatic"; -import { AuthenticatorDuoChallenge, StagesApi } from "authentik-api"; +import { AuthenticatorDuoChallenge, AuthenticatorDuoChallengeResponseRequest, StagesApi } from "authentik-api"; import { DEFAULT_CONFIG } from "../../../api/Config"; -import { AuthenticatorDuoChallengeResponseRequest } from "authentik-api/dist/models/AuthenticatorDuoChallengeResponseRequest"; import { ifDefined } from "lit-html/directives/if-defined"; @customElement("ak-stage-authenticator-duo") diff --git a/web/src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts b/web/src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts index 9a1b041bc3..ef0527b346 100644 --- a/web/src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts +++ b/web/src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts @@ -11,8 +11,7 @@ import { BaseStage } from "../base"; import "../../../elements/forms/FormElement"; import "../../../elements/EmptyState"; import "../../FormStatic"; -import { AuthenticatorStaticChallenge } from "authentik-api"; -import { AuthenticatorStaticChallengeResponseRequest } from "authentik-api/dist/models/AuthenticatorStaticChallengeResponseRequest"; +import { AuthenticatorStaticChallenge, AuthenticatorStaticChallengeResponseRequest } from "authentik-api"; import { ifDefined } from "lit-html/directives/if-defined"; export const STATIC_TOKEN_STYLE = css` diff --git a/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts b/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts index e3ad9b41ea..76d69ae498 100644 --- a/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts +++ b/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts @@ -12,8 +12,7 @@ import "./AuthenticatorValidateStageWebAuthn"; import "./AuthenticatorValidateStageCode"; import "./AuthenticatorValidateStageDuo"; import { PasswordManagerPrefill } from "../identification/IdentificationStage"; -import { AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; -import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge"; +import { AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; export enum DeviceClasses { STATIC = "static", diff --git a/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts b/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts index b3371079e1..941d7367f8 100644 --- a/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts +++ b/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts @@ -13,8 +13,7 @@ import "../../../elements/forms/FormElement"; import "../../../elements/EmptyState"; import { PasswordManagerPrefill } from "../identification/IdentificationStage"; import "../../FormStatic"; -import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge"; -import { AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; +import { AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; import { ifDefined } from "lit-html/directives/if-defined"; @customElement("ak-stage-authenticator-validate-code") diff --git a/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts b/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts index 7d16fcca13..fdfc4c178c 100644 --- a/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts +++ b/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageDuo.ts @@ -12,8 +12,7 @@ import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage"; import "../../../elements/forms/FormElement"; import "../../../elements/EmptyState"; import "../../FormStatic"; -import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge"; -import { AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; +import { AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; import { ifDefined } from "lit-html/directives/if-defined"; @customElement("ak-stage-authenticator-validate-duo") diff --git a/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts b/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts index c866ad1213..41b11c731f 100644 --- a/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts +++ b/web/src/flows/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn.ts @@ -11,8 +11,7 @@ import { PFSize } from "../../../elements/Spinner"; import { transformAssertionForServer, transformCredentialRequestOptions } from "../authenticator_webauthn/utils"; import { BaseStage } from "../base"; import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage"; -import { AuthenticatorValidationChallenge } from "authentik-api/dist/models/AuthenticatorValidationChallenge"; -import { AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; +import { AuthenticatorValidationChallenge, AuthenticatorValidationChallengeResponseRequest, DeviceChallenge } from "authentik-api"; @customElement("ak-stage-authenticator-validate-webauthn") export class AuthenticatorValidateStageWebAuthn extends BaseStage { diff --git a/web/src/flows/stages/autosubmit/AutosubmitStage.ts b/web/src/flows/stages/autosubmit/AutosubmitStage.ts index a97d7ccc1e..87578eeefa 100644 --- a/web/src/flows/stages/autosubmit/AutosubmitStage.ts +++ b/web/src/flows/stages/autosubmit/AutosubmitStage.ts @@ -9,8 +9,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; import AKGlobal from "../../../authentik.css"; import { BaseStage } from "../base"; import "../../../elements/EmptyState"; -import { AutosubmitChallenge } from "authentik-api"; -import { AutoSubmitChallengeResponseRequest } from "authentik-api/dist/models/AutoSubmitChallengeResponseRequest"; +import { AutosubmitChallenge, AutoSubmitChallengeResponseRequest } from "authentik-api"; @customElement("ak-stage-autosubmit") export class AutosubmitStage extends BaseStage { diff --git a/web/src/locales/en.po b/web/src/locales/en.po index 6cf58af8cb..a4ff46ed91 100644 --- a/web/src/locales/en.po +++ b/web/src/locales/en.po @@ -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." 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 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." @@ -941,6 +945,10 @@ msgstr "Create User" msgid "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/flows/BoundStagesList.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." #: 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." -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." +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 both options disabled, policies are **not** evaluated." #: src/pages/events/EventListPage.ts msgid "Event Log" @@ -1451,9 +1459,14 @@ msgid "Explicit Consent" msgstr "Explicit Consent" #: src/pages/flows/FlowListPage.ts +#: src/pages/flows/FlowViewPage.ts msgid "Export" msgstr "Export" +#: src/pages/flows/FlowViewPage.ts +msgid "Export flow" +msgstr "Export flow" + #: src/pages/events/EventInfo.ts #: src/pages/policies/expression/ExpressionPolicyForm.ts #: src/pages/property-mappings/PropertyMappingLDAPForm.ts @@ -1876,6 +1889,10 @@ msgstr "Internal host" msgid "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 msgid "Invalidation" msgstr "Invalidation" @@ -2138,6 +2155,10 @@ msgstr "Logs" msgid "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 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." @@ -2842,6 +2863,18 @@ msgstr "Public key, acquired from https://www.google.com/recaptcha/intro/v3.html msgid "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 msgid "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/prompt/PromptStageForm.ts #: src/pages/stages/user_login/UserLoginStageForm.ts +#: src/pages/stages/user_write/UserWriteStageForm.ts msgid "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}" 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 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." diff --git a/web/src/locales/pseudo-LOCALE.po b/web/src/locales/pseudo-LOCALE.po index e814e67914..b5dab26463 100644 --- a/web/src/locales/pseudo-LOCALE.po +++ b/web/src/locales/pseudo-LOCALE.po @@ -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." 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." msgstr "" @@ -935,6 +939,10 @@ msgstr "" msgid "Create provider" msgstr "" +#: +msgid "Create users as inactive" +msgstr "" + #: #: #: @@ -1364,7 +1372,7 @@ msgid "Evaluate policies before the Stage is present to the user." 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 "" #: @@ -1442,10 +1450,15 @@ msgstr "" msgid "Explicit Consent" msgstr "" +#: #: msgid "Export" msgstr "" +#: +msgid "Export flow" +msgstr "" + #: #: #: @@ -1868,6 +1881,10 @@ msgstr "" msgid "Internal host SSL Validation" msgstr "" +#: +msgid "Invalid response action" +msgstr "" + #: msgid "Invalidation" msgstr "" @@ -2130,6 +2147,10 @@ msgstr "" msgid "Long-running operations which authentik executes in the background." 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." msgstr "" @@ -2834,6 +2855,18 @@ msgstr "" msgid "Publisher" 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)" msgstr "" @@ -3351,6 +3384,7 @@ msgstr "" #: #: #: +#: msgid "Stage-specific settings" msgstr "" @@ -3808,6 +3842,13 @@ msgstr "" msgid "The following objects use {objName}" 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." msgstr "" diff --git a/web/src/pages/events/EventInfo.ts b/web/src/pages/events/EventInfo.ts index 38ca070721..439ff763ca 100644 --- a/web/src/pages/events/EventInfo.ts +++ b/web/src/pages/events/EventInfo.ts @@ -1,7 +1,7 @@ import { t } from "@lingui/macro"; import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { until } from "lit-html/directives/until"; -import { EventMatcherPolicyActionEnum, FlowsApi } from "authentik-api"; +import { EventActions, FlowsApi } from "authentik-api"; import "../../elements/Spinner"; import "../../elements/Expand"; import { PFSize } from "../../elements/Spinner"; @@ -189,14 +189,14 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)} return html``; } switch (this.event?.action) { - case EventMatcherPolicyActionEnum.ModelCreated: - case EventMatcherPolicyActionEnum.ModelUpdated: - case EventMatcherPolicyActionEnum.ModelDeleted: + case EventActions.ModelCreated: + case EventActions.ModelUpdated: + case EventActions.ModelDeleted: return html`

${t`Affected model:`}

${this.getModelInfo(this.event.context?.model as EventModel)} `; - case EventMatcherPolicyActionEnum.AuthorizeApplication: + case EventActions.AuthorizeApplication: return html`

${t`Authorized application:`}

@@ -213,17 +213,17 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)}
${this.defaultResponse()}`; - case EventMatcherPolicyActionEnum.EmailSent: + case EventActions.EmailSent: return html`

${t`Email info:`}

${this.getEmailInfo(this.event.context)} `; - case EventMatcherPolicyActionEnum.SecretView: + case EventActions.SecretView: return html`

${t`Secret:`}

${this.getModelInfo(this.event.context.secret as EventModel)}`; - case EventMatcherPolicyActionEnum.SystemException: + case EventActions.SystemException: return html` ${this.defaultResponse()}`; - case EventMatcherPolicyActionEnum.PropertyMappingException: + case EventActions.PropertyMappingException: return html`

${t`Exception`}

@@ -252,7 +252,7 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)}
${this.defaultResponse()}`; - case EventMatcherPolicyActionEnum.PolicyException: + case EventActions.PolicyException: return html`

${t`Binding`}

@@ -271,7 +271,7 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)}
${this.defaultResponse()}`; - case EventMatcherPolicyActionEnum.PolicyExecution: + case EventActions.PolicyExecution: return html`

${t`Binding`}

@@ -299,10 +299,10 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)}
${this.defaultResponse()}`; - case EventMatcherPolicyActionEnum.ConfigurationError: + case EventActions.ConfigurationError: return html`

${this.event.context.message}

${this.defaultResponse()}`; - case EventMatcherPolicyActionEnum.UpdateAvailable: + case EventActions.UpdateAvailable: return html`

${t`New version available!`}

`; // Action types which typically don't record any extra context. // If context is not empty, we fall to the default response. - case EventMatcherPolicyActionEnum.Login: + case EventActions.Login: if ("using_source" in this.event.context) { return html`
@@ -321,11 +321,11 @@ new?labels=bug,from_authentik&title=${encodeURIComponent(title)}
`; } return this.defaultResponse(); - case EventMatcherPolicyActionEnum.LoginFailed: + case EventActions.LoginFailed: return html`

${t`Attempted to log in as ${this.event.context.username}`}

${this.defaultResponse()}`; - case EventMatcherPolicyActionEnum.Logout: + case EventActions.Logout: if (this.event.context === {}) { return html`${t`No additional data available.`}`; } diff --git a/web/src/pages/events/EventInfoPage.ts b/web/src/pages/events/EventInfoPage.ts index 069e5ef3f9..51d14904a5 100644 --- a/web/src/pages/events/EventInfoPage.ts +++ b/web/src/pages/events/EventInfoPage.ts @@ -1,5 +1,5 @@ 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 { DEFAULT_CONFIG } from "../../api/Config"; import { EventWithContext } from "../../api/Events"; @@ -27,11 +27,7 @@ export class EventInfoPage extends LitElement { event!: EventWithContext; static get styles(): CSSResult[] { - return [PFBase, PFPage, PFContent, PFCard, AKGlobal].concat(css` - .pf-c-card { - color: var(--ak-dark-foreground); - } - `); + return [PFBase, PFPage, PFContent, PFCard, AKGlobal]; } render(): TemplateResult { diff --git a/web/src/pages/flows/FlowViewPage.ts b/web/src/pages/flows/FlowViewPage.ts index 4710f17b6e..eaf19127c3 100644 --- a/web/src/pages/flows/FlowViewPage.ts +++ b/web/src/pages/flows/FlowViewPage.ts @@ -51,9 +51,9 @@ export class FlowViewPage extends LitElement { return html``; } return html` + icon="pf-icon pf-icon-process-automation" + header=${this.flow.name} + description=${this.flow.title}>
diff --git a/web/src/pages/flows/StageBindingForm.ts b/web/src/pages/flows/StageBindingForm.ts index ecce2cd6d7..81a4563cc6 100644 --- a/web/src/pages/flows/StageBindingForm.ts +++ b/web/src/pages/flows/StageBindingForm.ts @@ -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 { customElement, property } from "lit-element"; import { html, TemplateResult } from "lit-html"; @@ -123,7 +123,7 @@ export class StageBindingForm extends ModelForm {

- ${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.`}

@@ -135,6 +135,23 @@ export class StageBindingForm extends ModelForm {

${t`Evaluate policies before the Stage is present to the user.`}

+ + +

${t`Configure how the flow executor should handle an invalid response to a challenge.`}

+
{
${t`Allows/denys requests based on the users and/or the IPs reputation.`}
+
+ ${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.`} +
+ .instancePk=${this.provider.pk}>