flows: add invalid_response_action to configure how the FlowExecutor should handle invalid responses
closes #1079 Default value of `retry` behaves like previous version. `restart` and `restart_with_context` restart the flow upon an invalid response. `restart_with_context` keeps the same context of the Flow, allowing users to bind policies that maybe aren't valid on the first execution, but are after a retry, like a reputation policy with a deny stage. Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
ba9edd6c44
commit
2b1356bb91
|
@ -25,6 +25,7 @@ class FlowStageBindingSerializer(ModelSerializer):
|
||||||
"re_evaluate_policies",
|
"re_evaluate_policies",
|
||||||
"order",
|
"order",
|
||||||
"policy_engine_mode",
|
"policy_engine_mode",
|
||||||
|
"invalid_response_action",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ from typing import TYPE_CHECKING, Optional
|
||||||
from django.http.request import HttpRequest
|
from django.http.request import HttpRequest
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.core.models import User
|
|
||||||
from authentik.flows.models import FlowStageBinding
|
from authentik.flows.models import FlowStageBinding
|
||||||
from authentik.policies.engine import PolicyEngine
|
from authentik.policies.engine import PolicyEngine
|
||||||
from authentik.policies.models import PolicyBinding
|
from authentik.policies.models import PolicyBinding
|
||||||
|
@ -25,7 +24,7 @@ class StageMarker:
|
||||||
self,
|
self,
|
||||||
plan: "FlowPlan",
|
plan: "FlowPlan",
|
||||||
binding: FlowStageBinding,
|
binding: FlowStageBinding,
|
||||||
http_request: Optional[HttpRequest],
|
http_request: HttpRequest,
|
||||||
) -> Optional[FlowStageBinding]:
|
) -> Optional[FlowStageBinding]:
|
||||||
"""Process callback for this marker. This should be overridden by sub-classes.
|
"""Process callback for this marker. This should be overridden by sub-classes.
|
||||||
If a stage should be removed, return None."""
|
If a stage should be removed, return None."""
|
||||||
|
@ -37,24 +36,26 @@ class ReevaluateMarker(StageMarker):
|
||||||
"""Reevaluate Marker, forces stage's policies to be evaluated again."""
|
"""Reevaluate Marker, forces stage's policies to be evaluated again."""
|
||||||
|
|
||||||
binding: PolicyBinding
|
binding: PolicyBinding
|
||||||
user: User
|
|
||||||
|
|
||||||
def process(
|
def process(
|
||||||
self,
|
self,
|
||||||
plan: "FlowPlan",
|
plan: "FlowPlan",
|
||||||
binding: FlowStageBinding,
|
binding: FlowStageBinding,
|
||||||
http_request: Optional[HttpRequest],
|
http_request: HttpRequest,
|
||||||
) -> Optional[FlowStageBinding]:
|
) -> Optional[FlowStageBinding]:
|
||||||
"""Re-evaluate policies bound to stage, and if they fail, remove from plan"""
|
"""Re-evaluate policies bound to stage, and if they fail, remove from plan"""
|
||||||
|
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||||
|
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
"f(plan_inst)[re-eval marker]: running re-evaluation",
|
"f(plan_inst)[re-eval marker]: running re-evaluation",
|
||||||
binding=binding,
|
binding=binding,
|
||||||
policy_binding=self.binding,
|
policy_binding=self.binding,
|
||||||
)
|
)
|
||||||
engine = PolicyEngine(self.binding, self.user)
|
engine = PolicyEngine(
|
||||||
|
self.binding, plan.context.get(PLAN_CONTEXT_PENDING_USER, http_request.user)
|
||||||
|
)
|
||||||
engine.use_cache = False
|
engine.use_cache = False
|
||||||
if http_request:
|
engine.request.set_http_request(http_request)
|
||||||
engine.request.set_http_request(http_request)
|
|
||||||
engine.request.context = plan.context
|
engine.request.context = plan.context
|
||||||
engine.build()
|
engine.build()
|
||||||
result = engine.result
|
result = engine.result
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 3.2.4 on 2021-06-27 16:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_flows", "0020_flow_compatibility_mode"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="flowstagebinding",
|
||||||
|
name="invalid_response_action",
|
||||||
|
field=models.TextField(
|
||||||
|
choices=[("retry", "Retry"), ("continue", "Continue")],
|
||||||
|
default="retry",
|
||||||
|
help_text="Configure how the flow executor should handle an invalid response to a challenge. RETRY returns the error message and a similar challenge to the executor while CONTINUE continues with the next stage.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -27,6 +27,14 @@ class NotConfiguredAction(models.TextChoices):
|
||||||
CONFIGURE = "configure"
|
CONFIGURE = "configure"
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidResponseAction(models.TextChoices):
|
||||||
|
"""Configure how the flow executor should handle invalid responses to challenges"""
|
||||||
|
|
||||||
|
RETRY = "retry"
|
||||||
|
RESTART = "restart"
|
||||||
|
RESTART_WITH_CONTEXT = "restart_with_context"
|
||||||
|
|
||||||
|
|
||||||
class FlowDesignation(models.TextChoices):
|
class FlowDesignation(models.TextChoices):
|
||||||
"""Designation of what a Flow should be used for. At a later point, this
|
"""Designation of what a Flow should be used for. At a later point, this
|
||||||
should be replaced by a database entry."""
|
should be replaced by a database entry."""
|
||||||
|
@ -201,6 +209,17 @@ class FlowStageBinding(SerializerModel, PolicyBindingModel):
|
||||||
help_text=_("Evaluate policies when the Stage is present to the user."),
|
help_text=_("Evaluate policies when the Stage is present to the user."),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
invalid_response_action = models.TextField(
|
||||||
|
choices=InvalidResponseAction.choices,
|
||||||
|
default=InvalidResponseAction.RETRY,
|
||||||
|
help_text=_(
|
||||||
|
"Configure how the flow executor should handle an invalid response to a "
|
||||||
|
"challenge. RETRY returns the error message and a similar challenge to the "
|
||||||
|
"executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT "
|
||||||
|
"restarts the flow while keeping the current context."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
order = models.IntegerField()
|
order = models.IntegerField()
|
||||||
|
|
||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
|
|
|
@ -224,7 +224,7 @@ class FlowPlanner:
|
||||||
"f(plan): stage has re-evaluate marker",
|
"f(plan): stage has re-evaluate marker",
|
||||||
stage=binding.stage,
|
stage=binding.stage,
|
||||||
)
|
)
|
||||||
marker = ReevaluateMarker(binding=binding, user=user)
|
marker = ReevaluateMarker(binding=binding)
|
||||||
if stage:
|
if stage:
|
||||||
plan.append(binding, marker)
|
plan.append(binding, marker)
|
||||||
HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug)
|
HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug)
|
||||||
|
|
|
@ -16,6 +16,7 @@ from authentik.flows.challenge import (
|
||||||
HttpChallengeResponse,
|
HttpChallengeResponse,
|
||||||
WithUserInfoChallenge,
|
WithUserInfoChallenge,
|
||||||
)
|
)
|
||||||
|
from authentik.flows.models import InvalidResponseAction
|
||||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||||
from authentik.flows.views import FlowExecutorView
|
from authentik.flows.views import FlowExecutorView
|
||||||
|
|
||||||
|
@ -69,7 +70,13 @@ class ChallengeStageView(StageView):
|
||||||
"""Return a challenge for the frontend to solve"""
|
"""Return a challenge for the frontend to solve"""
|
||||||
challenge = self._get_challenge(*args, **kwargs)
|
challenge = self._get_challenge(*args, **kwargs)
|
||||||
if not challenge.is_valid():
|
if not challenge.is_valid():
|
||||||
LOGGER.warning(challenge.errors, stage_view=self, challenge=challenge)
|
LOGGER.warning(
|
||||||
|
"f(ch): Invalid challenge",
|
||||||
|
binding=self.executor.current_binding,
|
||||||
|
errors=challenge.errors,
|
||||||
|
stage_view=self,
|
||||||
|
challenge=challenge,
|
||||||
|
)
|
||||||
return HttpChallengeResponse(challenge)
|
return HttpChallengeResponse(challenge)
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
@ -77,6 +84,21 @@ class ChallengeStageView(StageView):
|
||||||
"""Handle challenge response"""
|
"""Handle challenge response"""
|
||||||
challenge: ChallengeResponse = self.get_response_instance(data=request.data)
|
challenge: ChallengeResponse = self.get_response_instance(data=request.data)
|
||||||
if not challenge.is_valid():
|
if not challenge.is_valid():
|
||||||
|
if self.executor.current_binding.invalid_response_action in [
|
||||||
|
InvalidResponseAction.RESTART,
|
||||||
|
InvalidResponseAction.RESTART_WITH_CONTEXT,
|
||||||
|
]:
|
||||||
|
keep_context = (
|
||||||
|
self.executor.current_binding.invalid_response_action
|
||||||
|
== InvalidResponseAction.RESTART_WITH_CONTEXT
|
||||||
|
)
|
||||||
|
LOGGER.debug(
|
||||||
|
"f(ch): Invalid response, restarting flow",
|
||||||
|
binding=self.executor.current_binding,
|
||||||
|
stage_view=self,
|
||||||
|
keep_context=keep_context,
|
||||||
|
)
|
||||||
|
return self.executor.restart_flow(keep_context)
|
||||||
return self.challenge_invalid(challenge)
|
return self.challenge_invalid(challenge)
|
||||||
return self.challenge_valid(challenge)
|
return self.challenge_valid(challenge)
|
||||||
|
|
||||||
|
@ -126,5 +148,10 @@ class ChallengeStageView(StageView):
|
||||||
)
|
)
|
||||||
challenge_response.initial_data["response_errors"] = full_errors
|
challenge_response.initial_data["response_errors"] = full_errors
|
||||||
if not challenge_response.is_valid():
|
if not challenge_response.is_valid():
|
||||||
LOGGER.warning(challenge_response.errors)
|
LOGGER.warning(
|
||||||
|
"f(ch): invalid challenge response",
|
||||||
|
binding=self.executor.current_binding,
|
||||||
|
errors=challenge_response.errors,
|
||||||
|
stage_view=self,
|
||||||
|
)
|
||||||
return HttpChallengeResponse(challenge_response)
|
return HttpChallengeResponse(challenge_response)
|
||||||
|
|
|
@ -11,15 +11,23 @@ from authentik.core.models import User
|
||||||
from authentik.flows.challenge import ChallengeTypes
|
from authentik.flows.challenge import ChallengeTypes
|
||||||
from authentik.flows.exceptions import FlowNonApplicableException
|
from authentik.flows.exceptions import FlowNonApplicableException
|
||||||
from authentik.flows.markers import ReevaluateMarker, StageMarker
|
from authentik.flows.markers import ReevaluateMarker, StageMarker
|
||||||
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
|
from authentik.flows.models import (
|
||||||
|
Flow,
|
||||||
|
FlowDesignation,
|
||||||
|
FlowStageBinding,
|
||||||
|
InvalidResponseAction,
|
||||||
|
)
|
||||||
from authentik.flows.planner import FlowPlan, FlowPlanner
|
from authentik.flows.planner import FlowPlan, FlowPlanner
|
||||||
from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageView
|
from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageView
|
||||||
from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_PLAN, FlowExecutorView
|
from authentik.flows.views import NEXT_ARG_NAME, SESSION_KEY_PLAN, FlowExecutorView
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG
|
||||||
from authentik.policies.dummy.models import DummyPolicy
|
from authentik.policies.dummy.models import DummyPolicy
|
||||||
from authentik.policies.models import PolicyBinding
|
from authentik.policies.models import PolicyBinding
|
||||||
|
from authentik.policies.reputation.models import ReputationPolicy
|
||||||
from authentik.policies.types import PolicyResult
|
from authentik.policies.types import PolicyResult
|
||||||
|
from authentik.stages.deny.models import DenyStage
|
||||||
from authentik.stages.dummy.models import DummyStage
|
from authentik.stages.dummy.models import DummyStage
|
||||||
|
from authentik.stages.identification.models import IdentificationStage, UserFields
|
||||||
|
|
||||||
POLICY_RETURN_FALSE = PropertyMock(return_value=PolicyResult(False))
|
POLICY_RETURN_FALSE = PropertyMock(return_value=PolicyResult(False))
|
||||||
POLICY_RETURN_TRUE = MagicMock(return_value=PolicyResult(True))
|
POLICY_RETURN_TRUE = MagicMock(return_value=PolicyResult(True))
|
||||||
|
@ -513,3 +521,78 @@ class TestFlowExecutor(TestCase):
|
||||||
|
|
||||||
stage_view = StageView(executor)
|
stage_view = StageView(executor)
|
||||||
self.assertEqual(ident, stage_view.get_pending_user(for_display=True).username)
|
self.assertEqual(ident, stage_view.get_pending_user(for_display=True).username)
|
||||||
|
|
||||||
|
def test_invalid_restart(self):
|
||||||
|
"""Test flow that restarts on invalid entry"""
|
||||||
|
flow = Flow.objects.create(
|
||||||
|
name="restart-on-invalid",
|
||||||
|
slug="restart-on-invalid",
|
||||||
|
designation=FlowDesignation.AUTHENTICATION,
|
||||||
|
)
|
||||||
|
# Stage 0 is a deny stage that is added dynamically
|
||||||
|
# when the reputation policy says so
|
||||||
|
deny_stage = DenyStage.objects.create(name="deny")
|
||||||
|
reputation_policy = ReputationPolicy.objects.create(
|
||||||
|
name="reputation", threshold=-1, check_ip=False
|
||||||
|
)
|
||||||
|
deny_binding = FlowStageBinding.objects.create(
|
||||||
|
target=flow,
|
||||||
|
stage=deny_stage,
|
||||||
|
order=0,
|
||||||
|
evaluate_on_plan=False,
|
||||||
|
re_evaluate_policies=True,
|
||||||
|
)
|
||||||
|
PolicyBinding.objects.create(
|
||||||
|
policy=reputation_policy, target=deny_binding, order=0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stage 1 is an identification stage
|
||||||
|
ident_stage = IdentificationStage.objects.create(
|
||||||
|
name="ident",
|
||||||
|
user_fields=[UserFields.E_MAIL],
|
||||||
|
)
|
||||||
|
FlowStageBinding.objects.create(
|
||||||
|
target=flow,
|
||||||
|
stage=ident_stage,
|
||||||
|
order=1,
|
||||||
|
invalid_response_action=InvalidResponseAction.RESTART_WITH_CONTEXT,
|
||||||
|
)
|
||||||
|
exec_url = reverse(
|
||||||
|
"authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}
|
||||||
|
)
|
||||||
|
# First request, run the planner
|
||||||
|
response = self.client.get(exec_url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertJSONEqual(
|
||||||
|
force_str(response.content),
|
||||||
|
{
|
||||||
|
"type": ChallengeTypes.NATIVE.value,
|
||||||
|
"component": "ak-stage-identification",
|
||||||
|
"flow_info": {
|
||||||
|
"background": flow.background_url,
|
||||||
|
"cancel_url": reverse("authentik_flows:cancel"),
|
||||||
|
"title": "",
|
||||||
|
},
|
||||||
|
"password_fields": False,
|
||||||
|
"primary_action": "Log in",
|
||||||
|
"sources": [],
|
||||||
|
"user_fields": [UserFields.E_MAIL],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
response = self.client.post(
|
||||||
|
exec_url, {"uid_field": "invalid-string"}, follow=True
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertJSONEqual(
|
||||||
|
force_str(response.content),
|
||||||
|
{
|
||||||
|
"component": "ak-stage-access-denied",
|
||||||
|
"error_message": None,
|
||||||
|
"flow_info": {
|
||||||
|
"background": flow.background_url,
|
||||||
|
"cancel_url": reverse("authentik_flows:cancel"),
|
||||||
|
"title": "",
|
||||||
|
},
|
||||||
|
"type": ChallengeTypes.NATIVE.value,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
|
@ -288,6 +288,20 @@ class FlowExecutorView(APIView):
|
||||||
return self._initiate_plan()
|
return self._initiate_plan()
|
||||||
return plan
|
return plan
|
||||||
|
|
||||||
|
def restart_flow(self, keep_context=False) -> HttpResponse:
|
||||||
|
"""Restart the currently active flow, optionally keeping the current context"""
|
||||||
|
planner = FlowPlanner(self.flow)
|
||||||
|
default_context = None
|
||||||
|
if keep_context:
|
||||||
|
default_context = self.plan.context
|
||||||
|
plan = planner.plan(self.request, default_context)
|
||||||
|
self.request.session[SESSION_KEY_PLAN] = plan
|
||||||
|
kwargs = self.kwargs
|
||||||
|
kwargs.update({"flow_slug": self.flow.slug})
|
||||||
|
return redirect_with_qs(
|
||||||
|
"authentik_api:flow-executor", self.request.GET, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
def _flow_done(self) -> HttpResponse:
|
def _flow_done(self) -> HttpResponse:
|
||||||
"""User Successfully passed all stages"""
|
"""User Successfully passed all stages"""
|
||||||
# Since this is wrapped by the ExecutorShell, the next argument is saved in the session
|
# Since this is wrapped by the ExecutorShell, the next argument is saved in the session
|
||||||
|
|
|
@ -33,21 +33,21 @@ class ReputationPolicy(Policy):
|
||||||
|
|
||||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||||
remote_ip = get_client_ip(request.http_request)
|
remote_ip = get_client_ip(request.http_request)
|
||||||
passing = True
|
passing = False
|
||||||
if self.check_ip:
|
if self.check_ip:
|
||||||
score = cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0)
|
score = cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0)
|
||||||
passing = passing and score <= self.threshold
|
passing += passing or score <= self.threshold
|
||||||
LOGGER.debug("Score for IP", ip=remote_ip, score=score, passing=passing)
|
LOGGER.debug("Score for IP", ip=remote_ip, score=score, passing=passing)
|
||||||
if self.check_username:
|
if self.check_username:
|
||||||
score = cache.get_or_set(CACHE_KEY_USER_PREFIX + request.user.username, 0)
|
score = cache.get_or_set(CACHE_KEY_USER_PREFIX + request.user.username, 0)
|
||||||
passing = passing and score <= self.threshold
|
passing += passing or score <= self.threshold
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
"Score for Username",
|
"Score for Username",
|
||||||
username=request.user.username,
|
username=request.user.username,
|
||||||
score=score,
|
score=score,
|
||||||
passing=passing,
|
passing=passing,
|
||||||
)
|
)
|
||||||
return PolicyResult(passing)
|
return PolicyResult(bool(passing))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,18 @@ class IdentificationChallengeResponse(ChallengeResponse):
|
||||||
identification_failed.send(
|
identification_failed.send(
|
||||||
sender=self, request=self.stage.request, uid_field=uid_field
|
sender=self, request=self.stage.request, uid_field=uid_field
|
||||||
)
|
)
|
||||||
|
# We set the pending_user even on failure so it's part of the context, even
|
||||||
|
# when the input is invalid
|
||||||
|
# This is so its part of the current flow plan, and on flow restart can be kept, and
|
||||||
|
# policies can be applied.
|
||||||
|
self.stage.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User(
|
||||||
|
username=uid_field,
|
||||||
|
email=uid_field,
|
||||||
|
)
|
||||||
|
if not current_stage.show_matched_user:
|
||||||
|
self.stage.executor.plan.context[
|
||||||
|
PLAN_CONTEXT_PENDING_USER_IDENTIFIER
|
||||||
|
] = uid_field
|
||||||
raise ValidationError("Failed to authenticate.")
|
raise ValidationError("Failed to authenticate.")
|
||||||
self.pre_user = pre_user
|
self.pre_user = pre_user
|
||||||
if not current_stage.password_stage:
|
if not current_stage.password_stage:
|
||||||
|
|
39
schema.yml
39
schema.yml
|
@ -4572,6 +4572,18 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
|
- in: query
|
||||||
|
name: invalid_response_action
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- restart
|
||||||
|
- restart_with_context
|
||||||
|
- retry
|
||||||
|
description: Configure how the flow executor should handle an invalid response
|
||||||
|
to a challenge. RETRY returns the error message and a similar challenge
|
||||||
|
to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT
|
||||||
|
restarts the flow while keeping the current context.
|
||||||
- in: query
|
- in: query
|
||||||
name: order
|
name: order
|
||||||
schema:
|
schema:
|
||||||
|
@ -19810,6 +19822,13 @@ components:
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
policy_engine_mode:
|
policy_engine_mode:
|
||||||
$ref: '#/components/schemas/PolicyEngineMode'
|
$ref: '#/components/schemas/PolicyEngineMode'
|
||||||
|
invalid_response_action:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/InvalidResponseActionEnum'
|
||||||
|
description: Configure how the flow executor should handle an invalid response
|
||||||
|
to a challenge. RETRY returns the error message and a similar challenge
|
||||||
|
to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT
|
||||||
|
restarts the flow while keeping the current context.
|
||||||
required:
|
required:
|
||||||
- order
|
- order
|
||||||
- pk
|
- pk
|
||||||
|
@ -19840,6 +19859,13 @@ components:
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
policy_engine_mode:
|
policy_engine_mode:
|
||||||
$ref: '#/components/schemas/PolicyEngineMode'
|
$ref: '#/components/schemas/PolicyEngineMode'
|
||||||
|
invalid_response_action:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/InvalidResponseActionEnum'
|
||||||
|
description: Configure how the flow executor should handle an invalid response
|
||||||
|
to a challenge. RETRY returns the error message and a similar challenge
|
||||||
|
to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT
|
||||||
|
restarts the flow while keeping the current context.
|
||||||
required:
|
required:
|
||||||
- order
|
- order
|
||||||
- stage
|
- stage
|
||||||
|
@ -20185,6 +20211,12 @@ components:
|
||||||
- api
|
- api
|
||||||
- recovery
|
- recovery
|
||||||
type: string
|
type: string
|
||||||
|
InvalidResponseActionEnum:
|
||||||
|
enum:
|
||||||
|
- retry
|
||||||
|
- restart
|
||||||
|
- restart_with_context
|
||||||
|
type: string
|
||||||
Invitation:
|
Invitation:
|
||||||
type: object
|
type: object
|
||||||
description: Invitation Serializer
|
description: Invitation Serializer
|
||||||
|
@ -24662,6 +24694,13 @@ components:
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
policy_engine_mode:
|
policy_engine_mode:
|
||||||
$ref: '#/components/schemas/PolicyEngineMode'
|
$ref: '#/components/schemas/PolicyEngineMode'
|
||||||
|
invalid_response_action:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/InvalidResponseActionEnum'
|
||||||
|
description: Configure how the flow executor should handle an invalid response
|
||||||
|
to a challenge. RETRY returns the error message and a similar challenge
|
||||||
|
to the executor. RESTART restarts the flow from the beginning, and RESTART_WITH_CONTEXT
|
||||||
|
restarts the flow while keeping the current context.
|
||||||
PatchedGroupRequest:
|
PatchedGroupRequest:
|
||||||
type: object
|
type: object
|
||||||
description: Group Serializer
|
description: Group Serializer
|
||||||
|
|
|
@ -698,6 +698,10 @@ msgstr "Configure how long refresh tokens and their id_tokens are valid for."
|
||||||
msgid "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected."
|
msgid "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected."
|
||||||
msgstr "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected."
|
msgstr "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected."
|
||||||
|
|
||||||
|
#: src/pages/flows/StageBindingForm.ts
|
||||||
|
msgid "Configure how the flow executor should handle an invalid response to a challenge."
|
||||||
|
msgstr "Configure how the flow executor should handle an invalid response to a challenge."
|
||||||
|
|
||||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
msgid "Configure how the issuer field of the ID Token should be filled."
|
msgid "Configure how the issuer field of the ID Token should be filled."
|
||||||
msgstr "Configure how the issuer field of the ID Token should be filled."
|
msgstr "Configure how the issuer field of the ID Token should be filled."
|
||||||
|
@ -1881,6 +1885,10 @@ msgstr "Internal host"
|
||||||
msgid "Internal host SSL Validation"
|
msgid "Internal host SSL Validation"
|
||||||
msgstr "Internal host SSL Validation"
|
msgstr "Internal host SSL Validation"
|
||||||
|
|
||||||
|
#: src/pages/flows/StageBindingForm.ts
|
||||||
|
msgid "Invalid response action"
|
||||||
|
msgstr "Invalid response action"
|
||||||
|
|
||||||
#: src/pages/flows/FlowForm.ts
|
#: src/pages/flows/FlowForm.ts
|
||||||
msgid "Invalidation"
|
msgid "Invalidation"
|
||||||
msgstr "Invalidation"
|
msgstr "Invalidation"
|
||||||
|
@ -2847,6 +2855,18 @@ msgstr "Public key, acquired from https://www.google.com/recaptcha/intro/v3.html
|
||||||
msgid "Publisher"
|
msgid "Publisher"
|
||||||
msgstr "Publisher"
|
msgstr "Publisher"
|
||||||
|
|
||||||
|
#: src/pages/flows/StageBindingForm.ts
|
||||||
|
msgid "RESTART restarts the flow from the beginning, while keeping the flow context."
|
||||||
|
msgstr "RESTART restarts the flow from the beginning, while keeping the flow context."
|
||||||
|
|
||||||
|
#: src/pages/flows/StageBindingForm.ts
|
||||||
|
msgid "RESTART restarts the flow from the beginning."
|
||||||
|
msgstr "RESTART restarts the flow from the beginning."
|
||||||
|
|
||||||
|
#: src/pages/flows/StageBindingForm.ts
|
||||||
|
msgid "RETRY returns the error message and a similar challenge to the executor."
|
||||||
|
msgstr "RETRY returns the error message and a similar challenge to the executor."
|
||||||
|
|
||||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
msgid "RS256 (Asymmetric Encryption)"
|
msgid "RS256 (Asymmetric Encryption)"
|
||||||
msgstr "RS256 (Asymmetric Encryption)"
|
msgstr "RS256 (Asymmetric Encryption)"
|
||||||
|
|
|
@ -692,6 +692,10 @@ msgstr ""
|
||||||
msgid "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected."
|
msgid "Configure how the NameID value will be created. When left empty, the NameIDPolicy of the incoming request will be respected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Configure how the flow executor should handle an invalid response to a challenge."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#:
|
#:
|
||||||
msgid "Configure how the issuer field of the ID Token should be filled."
|
msgid "Configure how the issuer field of the ID Token should be filled."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -1873,6 +1877,10 @@ msgstr ""
|
||||||
msgid "Internal host SSL Validation"
|
msgid "Internal host SSL Validation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "Invalid response action"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#:
|
#:
|
||||||
msgid "Invalidation"
|
msgid "Invalidation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -2839,6 +2847,18 @@ msgstr ""
|
||||||
msgid "Publisher"
|
msgid "Publisher"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "RESTART restarts the flow from the beginning, while keeping the flow context."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "RESTART restarts the flow from the beginning."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#:
|
||||||
|
msgid "RETRY returns the error message and a similar challenge to the executor."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#:
|
#:
|
||||||
msgid "RS256 (Asymmetric Encryption)"
|
msgid "RS256 (Asymmetric Encryption)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { FlowsApi, FlowStageBinding, PolicyEngineMode, Stage, StagesApi } from "authentik-api";
|
import { FlowsApi, FlowStageBinding, InvalidResponseActionEnum, PolicyEngineMode, Stage, StagesApi } from "authentik-api";
|
||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
import { customElement, property } from "lit-element";
|
import { customElement, property } from "lit-element";
|
||||||
import { html, TemplateResult } from "lit-html";
|
import { html, TemplateResult } from "lit-html";
|
||||||
|
@ -135,6 +135,23 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
|
||||||
</div>
|
</div>
|
||||||
<p class="pf-c-form__helper-text">${t`Evaluate policies before the Stage is present to the user.`}</p>
|
<p class="pf-c-form__helper-text">${t`Evaluate policies before the Stage is present to the user.`}</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal
|
||||||
|
label=${t`Invalid response action`}
|
||||||
|
?required=${true}
|
||||||
|
name="invalidResponseAction">
|
||||||
|
<select class="pf-c-form-control">
|
||||||
|
<option value=${InvalidResponseActionEnum.Retry} ?selected=${this.instance?.invalidResponseAction === InvalidResponseActionEnum.Retry}>
|
||||||
|
${t`RETRY returns the error message and a similar challenge to the executor.`}
|
||||||
|
</option>
|
||||||
|
<option value=${InvalidResponseActionEnum.Restart} ?selected=${this.instance?.invalidResponseAction === InvalidResponseActionEnum.Restart}>
|
||||||
|
${t`RESTART restarts the flow from the beginning.`}
|
||||||
|
</option>
|
||||||
|
<option value=${InvalidResponseActionEnum.RestartWithContext} ?selected=${this.instance?.invalidResponseAction === InvalidResponseActionEnum.RestartWithContext}>
|
||||||
|
${t`RESTART restarts the flow from the beginning, while keeping the flow context.`}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<p class="pf-c-form__helper-text">${t`Configure how the flow executor should handle an invalid response to a challenge.`}</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${t`Policy engine mode`}
|
label=${t`Policy engine mode`}
|
||||||
?required=${true}
|
?required=${true}
|
||||||
|
|
Reference in a new issue