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) 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:

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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(

View File

@ -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()