flows: fix post-email continuation not working

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-03-24 11:57:56 +01:00
parent 1d641b2432
commit dce869b566
6 changed files with 45 additions and 9 deletions

View file

@ -46,7 +46,7 @@ class Challenge(Serializer):
background = CharField(required=False)
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:

View file

@ -16,11 +16,27 @@ from authentik.flows.challenge import (
)
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.views import FlowExecutorView
from authentik.lib.sentry import SentryIgnoredException
PLAN_CONTEXT_PENDING_USER_IDENTIFIER = "pending_user_identifier"
LOGGER = get_logger()
class InvalidChallengeError(SentryIgnoredException):
"""Error raised when a challenge from a stage is not valid"""
def __init__(self, errors, stage_view: View, challenge: Challenge) -> None:
super().__init__()
self.errors = errors
self.stage_view = stage_view
self.challenge = challenge
def __str__(self) -> str:
return (
f"Invalid challenge from {self.stage_view}: {self.errors}\n{self.challenge}"
)
class StageView(View):
"""Abstract Stage, inherits TemplateView but can be combined with FormView"""
@ -64,7 +80,7 @@ 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)
LOGGER.warning(challenge.errors, stage_view=self, challenge=challenge)
return HttpChallengeResponse(challenge)
# pylint: disable=unused-argument

View file

@ -103,8 +103,8 @@ class FlowExecutorView(APIView):
# To match behaviour with loading an empty flow plan from cache,
# we don't show an error message here, but rather call _flow_done()
return self._flow_done()
# Initial flow request, check if we have an upstream query string passed in
request.session[SESSION_KEY_GET] = QueryDict(request.GET.get("query", ""))
# Initial flow request, check if we have an upstream query string passed in
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)

View file

@ -84,6 +84,7 @@ class EmailStageView(ChallengeStageView):
messages.success(request, _("Successfully verified Email."))
return self.executor.stage_ok()
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
LOGGER.debug("No pending user")
messages.error(self.request, _("No pending user."))
return self.executor.stage_invalid()
# Check if we've already sent the initial e-mail
@ -94,7 +95,11 @@ class EmailStageView(ChallengeStageView):
def get_challenge(self) -> Challenge:
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

View file

@ -5,12 +5,13 @@ from django.core import mail
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_str
from django.utils.http import urlencode
from authentik.core.models import Token, User
from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.views import SESSION_KEY_GET, SESSION_KEY_PLAN
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.email.models import EmailStage
from authentik.stages.email.stage import QS_KEY_TOKEN
@ -104,11 +105,23 @@ class TestEmailStage(TestCase):
)
session = self.client.session
session[SESSION_KEY_PLAN] = plan
token: Token = Token.objects.get(user=self.user)
session[SESSION_KEY_GET] = {QS_KEY_TOKEN: token.key}
session.save()
token: Token = Token.objects.get(user=self.user)
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
response = self.client.get(
reverse(

View file

@ -89,10 +89,12 @@ class SeleniumTestCase(StaticLiveServerTestCase):
)
self.driver.save_screenshot(screenshot_file)
self.logger.warning("Saved screenshot", file=screenshot_file)
self.logger.debug("--------browser logs")
for line in self.driver.get_log("browser"):
self.logger.warning(
self.logger.debug(
line["message"], source=line["source"], level=line["level"]
)
self.logger.debug("--------end browser logs")
if self.container:
self.container.kill()
self.driver.quit()