flows: fix post-email continuation not working
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
1d641b2432
commit
dce869b566
|
@ -46,7 +46,7 @@ class Challenge(Serializer):
|
||||||
background = CharField(required=False)
|
background = CharField(required=False)
|
||||||
|
|
||||||
response_errors = DictField(
|
response_errors = DictField(
|
||||||
child=ErrorDetailSerializer(many=True), allow_empty=False, required=False
|
child=ErrorDetailSerializer(many=True), allow_empty=True, required=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def create(self, validated_data: dict) -> Model:
|
def create(self, validated_data: dict) -> Model:
|
||||||
|
|
|
@ -16,11 +16,27 @@ from authentik.flows.challenge import (
|
||||||
)
|
)
|
||||||
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
|
||||||
|
from authentik.lib.sentry import SentryIgnoredException
|
||||||
|
|
||||||
PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier"
|
PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier"
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidChallengeError(SentryIgnoredException):
|
||||||
|
"""Error raised when a challenge from a stage is not valid"""
|
||||||
|
|
||||||
|
def __init__(self, errors, stage_view: View, challenge: Challenge) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.errors = errors
|
||||||
|
self.stage_view = stage_view
|
||||||
|
self.challenge = challenge
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return (
|
||||||
|
f"Invalid challenge from {self.stage_view}: {self.errors}\n{self.challenge}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StageView(View):
|
class StageView(View):
|
||||||
"""Abstract Stage, inherits TemplateView but can be combined with FormView"""
|
"""Abstract Stage, inherits TemplateView but can be combined with FormView"""
|
||||||
|
|
||||||
|
@ -64,7 +80,7 @@ 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)
|
LOGGER.warning(challenge.errors, stage_view=self, challenge=challenge)
|
||||||
return HttpChallengeResponse(challenge)
|
return HttpChallengeResponse(challenge)
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
|
|
@ -103,8 +103,8 @@ class FlowExecutorView(APIView):
|
||||||
# To match behaviour with loading an empty flow plan from cache,
|
# To match behaviour with loading an empty flow plan from cache,
|
||||||
# we don't show an error message here, but rather call _flow_done()
|
# we don't show an error message here, but rather call _flow_done()
|
||||||
return self._flow_done()
|
return self._flow_done()
|
||||||
# Initial flow request, check if we have an upstream query string passed in
|
# Initial flow request, check if we have an upstream query string passed in
|
||||||
request.session[SESSION_KEY_GET] = QueryDict(request.GET.get("query", ""))
|
request.session[SESSION_KEY_GET] = QueryDict(request.GET.get("query", ""))
|
||||||
# We don't save the Plan after getting the next stage
|
# We don't save the Plan after getting the next stage
|
||||||
# as it hasn't been successfully passed yet
|
# as it hasn't been successfully passed yet
|
||||||
next_stage = self.plan.next(self.request)
|
next_stage = self.plan.next(self.request)
|
||||||
|
|
|
@ -84,6 +84,7 @@ class EmailStageView(ChallengeStageView):
|
||||||
messages.success(request, _("Successfully verified Email."))
|
messages.success(request, _("Successfully verified Email."))
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
|
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
|
||||||
|
LOGGER.debug("No pending user")
|
||||||
messages.error(self.request, _("No pending user."))
|
messages.error(self.request, _("No pending user."))
|
||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
# Check if we've already sent the initial e-mail
|
# Check if we've already sent the initial e-mail
|
||||||
|
@ -94,7 +95,11 @@ class EmailStageView(ChallengeStageView):
|
||||||
|
|
||||||
def get_challenge(self) -> Challenge:
|
def get_challenge(self) -> Challenge:
|
||||||
challenge = EmailChallenge(
|
challenge = EmailChallenge(
|
||||||
data={"type": ChallengeTypes.native.value, "component": "ak-stage-email"}
|
data={
|
||||||
|
"type": ChallengeTypes.native.value,
|
||||||
|
"component": "ak-stage-email",
|
||||||
|
"title": "Email sent.",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
return challenge
|
return challenge
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,13 @@ from django.core import mail
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
|
from django.utils.http import urlencode
|
||||||
|
|
||||||
from authentik.core.models import Token, User
|
from authentik.core.models import Token, User
|
||||||
from authentik.flows.markers import StageMarker
|
from authentik.flows.markers import StageMarker
|
||||||
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
|
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
|
||||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
|
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
|
||||||
from authentik.flows.views import SESSION_KEY_GET, SESSION_KEY_PLAN
|
from authentik.flows.views import SESSION_KEY_PLAN
|
||||||
from authentik.stages.email.models import EmailStage
|
from authentik.stages.email.models import EmailStage
|
||||||
from authentik.stages.email.stage import QS_KEY_TOKEN
|
from authentik.stages.email.stage import QS_KEY_TOKEN
|
||||||
|
|
||||||
|
@ -104,11 +105,23 @@ class TestEmailStage(TestCase):
|
||||||
)
|
)
|
||||||
session = self.client.session
|
session = self.client.session
|
||||||
session[SESSION_KEY_PLAN] = plan
|
session[SESSION_KEY_PLAN] = plan
|
||||||
token: Token = Token.objects.get(user=self.user)
|
|
||||||
session[SESSION_KEY_GET] = {QS_KEY_TOKEN: token.key}
|
|
||||||
session.save()
|
session.save()
|
||||||
|
token: Token = Token.objects.get(user=self.user)
|
||||||
|
|
||||||
with patch("authentik.flows.views.FlowExecutorView.cancel", MagicMock()):
|
with patch("authentik.flows.views.FlowExecutorView.cancel", MagicMock()):
|
||||||
|
# Call the executor shell to preseed the session
|
||||||
|
url = reverse(
|
||||||
|
"authentik_api:flow-executor",
|
||||||
|
kwargs={"flow_slug": self.flow.slug},
|
||||||
|
)
|
||||||
|
url_query = urlencode(
|
||||||
|
{
|
||||||
|
QS_KEY_TOKEN: token.key,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
url += f"?query={url_query}"
|
||||||
|
self.client.get(url)
|
||||||
|
|
||||||
# Call the actual executor to get the JSON Response
|
# Call the actual executor to get the JSON Response
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse(
|
reverse(
|
||||||
|
|
|
@ -89,10 +89,12 @@ class SeleniumTestCase(StaticLiveServerTestCase):
|
||||||
)
|
)
|
||||||
self.driver.save_screenshot(screenshot_file)
|
self.driver.save_screenshot(screenshot_file)
|
||||||
self.logger.warning("Saved screenshot", file=screenshot_file)
|
self.logger.warning("Saved screenshot", file=screenshot_file)
|
||||||
|
self.logger.debug("--------browser logs")
|
||||||
for line in self.driver.get_log("browser"):
|
for line in self.driver.get_log("browser"):
|
||||||
self.logger.warning(
|
self.logger.debug(
|
||||||
line["message"], source=line["source"], level=line["level"]
|
line["message"], source=line["source"], level=line["level"]
|
||||||
)
|
)
|
||||||
|
self.logger.debug("--------end browser logs")
|
||||||
if self.container:
|
if self.container:
|
||||||
self.container.kill()
|
self.container.kill()
|
||||||
self.driver.quit()
|
self.driver.quit()
|
||||||
|
|
Reference in New Issue