authentik.stages.authenticator_validate.tests.test_sms

Test validator stage

  1"""Test validator stage"""
  2
  3from unittest.mock import MagicMock, patch
  4
  5from django.test.client import RequestFactory
  6from django.urls.base import reverse
  7
  8from authentik.core.tests.utils import create_test_admin_user, create_test_flow
  9from authentik.flows.models import FlowStageBinding, NotConfiguredAction
 10from authentik.flows.tests import FlowTestCase
 11from authentik.lib.generators import generate_id
 12from authentik.stages.authenticator_sms.models import AuthenticatorSMSStage, SMSDevice, SMSProviders
 13from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
 14from authentik.stages.authenticator_validate.stage import COOKIE_NAME_MFA
 15from authentik.stages.identification.models import IdentificationStage, UserFields
 16
 17
 18class AuthenticatorValidateStageSMSTests(FlowTestCase):
 19    """Test validator stage"""
 20
 21    def setUp(self) -> None:
 22        self.user = create_test_admin_user()
 23        self.request_factory = RequestFactory()
 24        self.stage = AuthenticatorSMSStage.objects.create(
 25            name="sms",
 26            provider=SMSProviders.GENERIC,
 27            from_number="1234",
 28        )
 29
 30    def test_last_auth_threshold(self):
 31        """Test last_auth_threshold"""
 32        ident_stage = IdentificationStage.objects.create(
 33            name=generate_id(),
 34            user_fields=[
 35                UserFields.USERNAME,
 36            ],
 37        )
 38        device: SMSDevice = SMSDevice.objects.create(
 39            user=self.user,
 40            confirmed=True,
 41            stage=self.stage,
 42        )
 43
 44        stage = AuthenticatorValidateStage.objects.create(
 45            name=generate_id(),
 46            last_auth_threshold="milliseconds=0",
 47            not_configured_action=NotConfiguredAction.CONFIGURE,
 48            device_classes=[DeviceClasses.SMS],
 49        )
 50        stage.configuration_stages.set([ident_stage])
 51        flow = create_test_flow()
 52        FlowStageBinding.objects.create(target=flow, stage=ident_stage, order=0)
 53        FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
 54
 55        response = self.client.post(
 56            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
 57            {"uid_field": self.user.username},
 58        )
 59        self.assertEqual(response.status_code, 302)
 60        device.generate_token()
 61        response = self.client.post(
 62            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
 63            {"code": device.token},
 64        )
 65        self.assertNotIn(COOKIE_NAME_MFA, response.cookies)
 66
 67    def test_last_auth_threshold_valid(self):
 68        """Test last_auth_threshold"""
 69        ident_stage = IdentificationStage.objects.create(
 70            name=generate_id(),
 71            user_fields=[
 72                UserFields.USERNAME,
 73            ],
 74        )
 75        device: SMSDevice = SMSDevice.objects.create(
 76            user=self.user,
 77            confirmed=True,
 78            stage=self.stage,
 79        )
 80
 81        stage = AuthenticatorValidateStage.objects.create(
 82            name=generate_id(),
 83            last_auth_threshold="hours=1",
 84            not_configured_action=NotConfiguredAction.CONFIGURE,
 85            device_classes=[DeviceClasses.SMS],
 86        )
 87        stage.configuration_stages.set([ident_stage])
 88        flow = create_test_flow()
 89        FlowStageBinding.objects.create(target=flow, stage=ident_stage, order=0)
 90        FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
 91
 92        response = self.client.post(
 93            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
 94            {"uid_field": self.user.username},
 95            follow=True,
 96        )
 97        self.assertEqual(response.status_code, 200)
 98        send_mock = MagicMock()
 99        with patch(
100            "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", send_mock
101        ):
102            response = self.client.post(
103                reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
104                {
105                    "component": "ak-stage-authenticator-validate",
106                    "selected_challenge": {
107                        "device_class": "sms",
108                        "device_uid": str(device.pk),
109                        "challenge": {},
110                        "last_used": None,
111                    },
112                },
113            )
114        self.assertEqual(send_mock.call_count, 1)
115        device.generate_token()
116        response = self.client.post(
117            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
118            {"code": device.token},
119        )
120        self.assertIn(COOKIE_NAME_MFA, response.cookies)
121        self.assertStageResponse(response, component="xak-flow-redirect", to="/")
122
123    def test_sms_hashed(self):
124        """Test hashed SMS device"""
125        ident_stage = IdentificationStage.objects.create(
126            name=generate_id(),
127            user_fields=[
128                UserFields.USERNAME,
129            ],
130        )
131        SMSDevice.objects.create(
132            user=self.user,
133            confirmed=True,
134            stage=self.stage,
135            phone_number="hash:foo",
136        )
137
138        stage = AuthenticatorValidateStage.objects.create(
139            name=generate_id(),
140            last_auth_threshold="hours=1",
141            not_configured_action=NotConfiguredAction.DENY,
142            device_classes=[DeviceClasses.SMS],
143        )
144        stage.configuration_stages.set([ident_stage])
145        flow = create_test_flow()
146        FlowStageBinding.objects.create(target=flow, stage=ident_stage, order=0)
147        FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
148
149        response = self.client.post(
150            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
151            {"uid_field": self.user.username},
152            follow=True,
153        )
154        self.assertEqual(response.status_code, 200)
155        self.assertStageResponse(response, flow, self.user, component="ak-stage-access-denied")
class AuthenticatorValidateStageSMSTests(authentik.flows.tests.FlowTestCase):
 19class AuthenticatorValidateStageSMSTests(FlowTestCase):
 20    """Test validator stage"""
 21
 22    def setUp(self) -> None:
 23        self.user = create_test_admin_user()
 24        self.request_factory = RequestFactory()
 25        self.stage = AuthenticatorSMSStage.objects.create(
 26            name="sms",
 27            provider=SMSProviders.GENERIC,
 28            from_number="1234",
 29        )
 30
 31    def test_last_auth_threshold(self):
 32        """Test last_auth_threshold"""
 33        ident_stage = IdentificationStage.objects.create(
 34            name=generate_id(),
 35            user_fields=[
 36                UserFields.USERNAME,
 37            ],
 38        )
 39        device: SMSDevice = SMSDevice.objects.create(
 40            user=self.user,
 41            confirmed=True,
 42            stage=self.stage,
 43        )
 44
 45        stage = AuthenticatorValidateStage.objects.create(
 46            name=generate_id(),
 47            last_auth_threshold="milliseconds=0",
 48            not_configured_action=NotConfiguredAction.CONFIGURE,
 49            device_classes=[DeviceClasses.SMS],
 50        )
 51        stage.configuration_stages.set([ident_stage])
 52        flow = create_test_flow()
 53        FlowStageBinding.objects.create(target=flow, stage=ident_stage, order=0)
 54        FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
 55
 56        response = self.client.post(
 57            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
 58            {"uid_field": self.user.username},
 59        )
 60        self.assertEqual(response.status_code, 302)
 61        device.generate_token()
 62        response = self.client.post(
 63            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
 64            {"code": device.token},
 65        )
 66        self.assertNotIn(COOKIE_NAME_MFA, response.cookies)
 67
 68    def test_last_auth_threshold_valid(self):
 69        """Test last_auth_threshold"""
 70        ident_stage = IdentificationStage.objects.create(
 71            name=generate_id(),
 72            user_fields=[
 73                UserFields.USERNAME,
 74            ],
 75        )
 76        device: SMSDevice = SMSDevice.objects.create(
 77            user=self.user,
 78            confirmed=True,
 79            stage=self.stage,
 80        )
 81
 82        stage = AuthenticatorValidateStage.objects.create(
 83            name=generate_id(),
 84            last_auth_threshold="hours=1",
 85            not_configured_action=NotConfiguredAction.CONFIGURE,
 86            device_classes=[DeviceClasses.SMS],
 87        )
 88        stage.configuration_stages.set([ident_stage])
 89        flow = create_test_flow()
 90        FlowStageBinding.objects.create(target=flow, stage=ident_stage, order=0)
 91        FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
 92
 93        response = self.client.post(
 94            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
 95            {"uid_field": self.user.username},
 96            follow=True,
 97        )
 98        self.assertEqual(response.status_code, 200)
 99        send_mock = MagicMock()
100        with patch(
101            "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", send_mock
102        ):
103            response = self.client.post(
104                reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
105                {
106                    "component": "ak-stage-authenticator-validate",
107                    "selected_challenge": {
108                        "device_class": "sms",
109                        "device_uid": str(device.pk),
110                        "challenge": {},
111                        "last_used": None,
112                    },
113                },
114            )
115        self.assertEqual(send_mock.call_count, 1)
116        device.generate_token()
117        response = self.client.post(
118            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
119            {"code": device.token},
120        )
121        self.assertIn(COOKIE_NAME_MFA, response.cookies)
122        self.assertStageResponse(response, component="xak-flow-redirect", to="/")
123
124    def test_sms_hashed(self):
125        """Test hashed SMS device"""
126        ident_stage = IdentificationStage.objects.create(
127            name=generate_id(),
128            user_fields=[
129                UserFields.USERNAME,
130            ],
131        )
132        SMSDevice.objects.create(
133            user=self.user,
134            confirmed=True,
135            stage=self.stage,
136            phone_number="hash:foo",
137        )
138
139        stage = AuthenticatorValidateStage.objects.create(
140            name=generate_id(),
141            last_auth_threshold="hours=1",
142            not_configured_action=NotConfiguredAction.DENY,
143            device_classes=[DeviceClasses.SMS],
144        )
145        stage.configuration_stages.set([ident_stage])
146        flow = create_test_flow()
147        FlowStageBinding.objects.create(target=flow, stage=ident_stage, order=0)
148        FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
149
150        response = self.client.post(
151            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
152            {"uid_field": self.user.username},
153            follow=True,
154        )
155        self.assertEqual(response.status_code, 200)
156        self.assertStageResponse(response, flow, self.user, component="ak-stage-access-denied")

Test validator stage

def setUp(self) -> None:
22    def setUp(self) -> None:
23        self.user = create_test_admin_user()
24        self.request_factory = RequestFactory()
25        self.stage = AuthenticatorSMSStage.objects.create(
26            name="sms",
27            provider=SMSProviders.GENERIC,
28            from_number="1234",
29        )

Hook method for setting up the test fixture before exercising it.

def test_last_auth_threshold(self):
31    def test_last_auth_threshold(self):
32        """Test last_auth_threshold"""
33        ident_stage = IdentificationStage.objects.create(
34            name=generate_id(),
35            user_fields=[
36                UserFields.USERNAME,
37            ],
38        )
39        device: SMSDevice = SMSDevice.objects.create(
40            user=self.user,
41            confirmed=True,
42            stage=self.stage,
43        )
44
45        stage = AuthenticatorValidateStage.objects.create(
46            name=generate_id(),
47            last_auth_threshold="milliseconds=0",
48            not_configured_action=NotConfiguredAction.CONFIGURE,
49            device_classes=[DeviceClasses.SMS],
50        )
51        stage.configuration_stages.set([ident_stage])
52        flow = create_test_flow()
53        FlowStageBinding.objects.create(target=flow, stage=ident_stage, order=0)
54        FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
55
56        response = self.client.post(
57            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
58            {"uid_field": self.user.username},
59        )
60        self.assertEqual(response.status_code, 302)
61        device.generate_token()
62        response = self.client.post(
63            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
64            {"code": device.token},
65        )
66        self.assertNotIn(COOKIE_NAME_MFA, response.cookies)

Test last_auth_threshold

def test_last_auth_threshold_valid(self):
 68    def test_last_auth_threshold_valid(self):
 69        """Test last_auth_threshold"""
 70        ident_stage = IdentificationStage.objects.create(
 71            name=generate_id(),
 72            user_fields=[
 73                UserFields.USERNAME,
 74            ],
 75        )
 76        device: SMSDevice = SMSDevice.objects.create(
 77            user=self.user,
 78            confirmed=True,
 79            stage=self.stage,
 80        )
 81
 82        stage = AuthenticatorValidateStage.objects.create(
 83            name=generate_id(),
 84            last_auth_threshold="hours=1",
 85            not_configured_action=NotConfiguredAction.CONFIGURE,
 86            device_classes=[DeviceClasses.SMS],
 87        )
 88        stage.configuration_stages.set([ident_stage])
 89        flow = create_test_flow()
 90        FlowStageBinding.objects.create(target=flow, stage=ident_stage, order=0)
 91        FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
 92
 93        response = self.client.post(
 94            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
 95            {"uid_field": self.user.username},
 96            follow=True,
 97        )
 98        self.assertEqual(response.status_code, 200)
 99        send_mock = MagicMock()
100        with patch(
101            "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", send_mock
102        ):
103            response = self.client.post(
104                reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
105                {
106                    "component": "ak-stage-authenticator-validate",
107                    "selected_challenge": {
108                        "device_class": "sms",
109                        "device_uid": str(device.pk),
110                        "challenge": {},
111                        "last_used": None,
112                    },
113                },
114            )
115        self.assertEqual(send_mock.call_count, 1)
116        device.generate_token()
117        response = self.client.post(
118            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
119            {"code": device.token},
120        )
121        self.assertIn(COOKIE_NAME_MFA, response.cookies)
122        self.assertStageResponse(response, component="xak-flow-redirect", to="/")

Test last_auth_threshold

def test_sms_hashed(self):
124    def test_sms_hashed(self):
125        """Test hashed SMS device"""
126        ident_stage = IdentificationStage.objects.create(
127            name=generate_id(),
128            user_fields=[
129                UserFields.USERNAME,
130            ],
131        )
132        SMSDevice.objects.create(
133            user=self.user,
134            confirmed=True,
135            stage=self.stage,
136            phone_number="hash:foo",
137        )
138
139        stage = AuthenticatorValidateStage.objects.create(
140            name=generate_id(),
141            last_auth_threshold="hours=1",
142            not_configured_action=NotConfiguredAction.DENY,
143            device_classes=[DeviceClasses.SMS],
144        )
145        stage.configuration_stages.set([ident_stage])
146        flow = create_test_flow()
147        FlowStageBinding.objects.create(target=flow, stage=ident_stage, order=0)
148        FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
149
150        response = self.client.post(
151            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
152            {"uid_field": self.user.username},
153            follow=True,
154        )
155        self.assertEqual(response.status_code, 200)
156        self.assertStageResponse(response, flow, self.user, component="ak-stage-access-denied")

Test hashed SMS device