diff --git a/authentik/stages/authenticator_validate/stage.py b/authentik/stages/authenticator_validate/stage.py index 35cf6a6fc..f81db5c24 100644 --- a/authentik/stages/authenticator_validate/stage.py +++ b/authentik/stages/authenticator_validate/stage.py @@ -377,9 +377,7 @@ class AuthenticatorValidateStageView(ChallengeStageView): def challenge_valid(self, response: AuthenticatorValidationChallengeResponse) -> HttpResponse: # All validation is done by the serializer user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER) - if not user: - if "webauthn" not in response.data: - return self.executor.stage_invalid() + if not user and "webauthn" in response.data: webauthn_device: WebAuthnDevice = response.device self.logger.debug("Set user from user-less flow", user=webauthn_device.user) self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = webauthn_device.user diff --git a/authentik/stages/authenticator_validate/tests/test_stage.py b/authentik/stages/authenticator_validate/tests/test_stage.py index 4dc8170d6..df111993d 100644 --- a/authentik/stages/authenticator_validate/tests/test_stage.py +++ b/authentik/stages/authenticator_validate/tests/test_stage.py @@ -1,18 +1,22 @@ """Test validator stage""" +from unittest.mock import MagicMock, patch + from django.contrib.sessions.middleware import SessionMiddleware from django.test.client import RequestFactory from django.urls.base import reverse from rest_framework.exceptions import ValidationError from authentik.core.tests.utils import create_test_admin_user, create_test_flow -from authentik.flows.models import FlowStageBinding, NotConfiguredAction +from authentik.flows.models import FlowDesignation, FlowStageBinding, NotConfiguredAction +from authentik.flows.planner import FlowPlan from authentik.flows.stage import StageView from authentik.flows.tests import FlowTestCase -from authentik.flows.views.executor import FlowExecutorView -from authentik.lib.generators import generate_id +from authentik.flows.views.executor import SESSION_KEY_PLAN, FlowExecutorView +from authentik.lib.generators import generate_id, generate_key from authentik.lib.tests.utils import dummy_get_response +from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice from authentik.stages.authenticator_validate.api import AuthenticatorValidateStageSerializer -from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage +from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses from authentik.stages.authenticator_validate.stage import ( SESSION_KEY_DEVICE_CHALLENGES, AuthenticatorValidationChallengeResponse, @@ -115,3 +119,57 @@ class AuthenticatorValidateStageTests(FlowTestCase): "device_uid": "1", } ) + + @patch( + "authentik.stages.authenticator_duo.models.AuthenticatorDuoStage.auth_client", + MagicMock( + return_value=MagicMock( + auth=MagicMock( + return_value={ + "result": "allow", + "status": "allow", + "status_msg": "Success. Logging you in...", + } + ) + ) + ), + ) + def test_non_authentication_flow(self): + """Test full in an authorization flow (no pending user)""" + self.client.force_login(self.user) + duo_stage = AuthenticatorDuoStage.objects.create( + name=generate_id(), + client_id=generate_id(), + client_secret=generate_key(), + api_hostname="", + ) + duo_device = DuoDevice.objects.create( + user=self.user, + stage=duo_stage, + ) + + flow = create_test_flow(FlowDesignation.AUTHORIZATION) + stage = AuthenticatorValidateStage.objects.create( + name=generate_id(), + device_classes=[DeviceClasses.DUO], + ) + + plan = FlowPlan(flow_pk=flow.pk.hex) + plan.append(FlowStageBinding.objects.create(target=flow, stage=stage, order=2)) + session = self.client.session + session[SESSION_KEY_PLAN] = plan + session.save() + + response = self.client.get( + reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), + ) + self.assertEqual(response.status_code, 200) + + response = self.client.post( + reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), + {"duo": duo_device.pk}, + follow=True, + ) + + self.assertEqual(response.status_code, 200) + self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))