stages/authenticator_validate: fix error when using not_configured_action=configure

closes #1048

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-06-22 20:07:23 +02:00
parent 5ff5edf769
commit b69248dd55
2 changed files with 55 additions and 3 deletions

View file

@ -10,7 +10,7 @@ from authentik.flows.challenge import (
ChallengeTypes, ChallengeTypes,
WithUserInfoChallenge, WithUserInfoChallenge,
) )
from authentik.flows.models import NotConfiguredAction from authentik.flows.models import NotConfiguredAction, Stage
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
from authentik.stages.authenticator_validate.challenge import ( from authentik.stages.authenticator_validate.challenge import (
@ -143,9 +143,12 @@ class AuthenticatorValidateStageView(ChallengeStageView):
return self.executor.stage_invalid() return self.executor.stage_invalid()
if stage.not_configured_action == NotConfiguredAction.CONFIGURE: if stage.not_configured_action == NotConfiguredAction.CONFIGURE:
LOGGER.debug("Authenticator not configured, sending user to configure") LOGGER.debug("Authenticator not configured, sending user to configure")
# Because the foreign key to stage.configuration_stage points to
# a base stage class, we need to do another lookup
stage = Stage.objects.get_subclass(pk=stage.configuration_stage.pk)
# plan.insert inserts at 1 index, so when stage_ok pops 0, # plan.insert inserts at 1 index, so when stage_ok pops 0,
# the configuration stage is next # the configuration stage is next
self.executor.plan.insert(stage.configuration_stage) self.executor.plan.insert(stage)
return self.executor.stage_ok() return self.executor.stage_ok()
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)

View file

@ -4,11 +4,14 @@ from unittest.mock import MagicMock, patch
from django.contrib.sessions.middleware import SessionMiddleware from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from django.urls.base import reverse
from django.utils.encoding import force_str
from django_otp.plugins.otp_totp.models import TOTPDevice from django_otp.plugins.otp_totp.models import TOTPDevice
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from authentik.core.models import User from authentik.core.models import User
from authentik.flows.models import NotConfiguredAction from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow, FlowStageBinding, NotConfiguredAction
from authentik.flows.tests.test_planner import dummy_get_response from authentik.flows.tests.test_planner import dummy_get_response
from authentik.providers.oauth2.generators import ( from authentik.providers.oauth2.generators import (
generate_client_id, generate_client_id,
@ -24,7 +27,9 @@ from authentik.stages.authenticator_validate.challenge import (
validate_challenge_duo, validate_challenge_duo,
validate_challenge_webauthn, validate_challenge_webauthn,
) )
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
from authentik.stages.identification.models import IdentificationStage, UserFields
class AuthenticatorValidateStageTests(TestCase): class AuthenticatorValidateStageTests(TestCase):
@ -34,6 +39,50 @@ class AuthenticatorValidateStageTests(TestCase):
self.user = User.objects.get(username="akadmin") self.user = User.objects.get(username="akadmin")
self.request_factory = RequestFactory() self.request_factory = RequestFactory()
def test_not_configured_action(self):
"""Test not_configured_action"""
conf_stage = IdentificationStage.objects.create(
name="conf",
user_fields=[
UserFields.USERNAME,
],
)
stage = AuthenticatorValidateStage.objects.create(
name="foo",
not_configured_action=NotConfiguredAction.CONFIGURE,
configuration_stage=conf_stage,
)
flow = Flow.objects.create(name="test", slug="test", title="test")
FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
{"uid_field": "akadmin"},
)
self.assertEqual(response.status_code, 302)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-identification",
"password_fields": False,
"primary_action": "Log in",
"flow_info": {
"background": flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": flow.title,
},
"user_fields": ["username"],
"sources": [],
},
)
def test_stage_validation(self): def test_stage_validation(self):
"""Test serializer validation""" """Test serializer validation"""
self.client.force_login(self.user) self.client.force_login(self.user)