authentik.stages.authenticator_sms.tests
Test SMS API
1"""Test SMS API""" 2 3from unittest.mock import MagicMock, patch 4from urllib.parse import parse_qsl 5 6from django.urls import reverse 7from requests_mock import Mocker 8 9from authentik.core.tests.utils import create_test_admin_user, create_test_flow 10from authentik.flows.models import FlowStageBinding 11from authentik.flows.planner import FlowPlan 12from authentik.flows.tests import FlowTestCase 13from authentik.flows.views.executor import SESSION_KEY_PLAN 14from authentik.lib.generators import generate_id 15from authentik.stages.authenticator_sms.models import ( 16 AuthenticatorSMSStage, 17 SMSDevice, 18 SMSProviders, 19 hash_phone_number, 20) 21from authentik.stages.authenticator_sms.stage import PLAN_CONTEXT_PHONE, PLAN_CONTEXT_SMS_DEVICE 22from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT 23 24 25class AuthenticatorSMSStageTests(FlowTestCase): 26 """Test SMS API""" 27 28 def setUp(self) -> None: 29 super().setUp() 30 self.flow = create_test_flow() 31 self.stage: AuthenticatorSMSStage = AuthenticatorSMSStage.objects.create( 32 name="foo", 33 provider=SMSProviders.TWILIO, 34 configure_flow=self.flow, 35 ) 36 FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=0) 37 self.user = create_test_admin_user() 38 self.client.force_login(self.user) 39 40 def test_stage_no_prefill(self): 41 """test stage""" 42 self.client.get( 43 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 44 ) 45 response = self.client.get( 46 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 47 ) 48 self.assertStageResponse( 49 response, 50 self.flow, 51 self.user, 52 component="ak-stage-authenticator-sms", 53 phone_number_required=True, 54 ) 55 56 def test_stage_submit(self): 57 """test stage (submit)""" 58 self.client.get( 59 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 60 ) 61 response = self.client.get( 62 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 63 ) 64 self.assertStageResponse( 65 response, 66 self.flow, 67 self.user, 68 component="ak-stage-authenticator-sms", 69 phone_number_required=True, 70 ) 71 sms_send_mock = MagicMock() 72 with patch( 73 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 74 sms_send_mock, 75 ): 76 response = self.client.post( 77 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 78 data={"component": "ak-stage-authenticator-sms", "phone_number": "foo"}, 79 ) 80 self.assertEqual(response.status_code, 200) 81 sms_send_mock.assert_called_once() 82 self.assertStageResponse( 83 response, 84 self.flow, 85 self.user, 86 component="ak-stage-authenticator-sms", 87 response_errors={}, 88 phone_number_required=False, 89 ) 90 91 def test_stage_submit_twilio(self): 92 """test stage (submit) (twilio)""" 93 self.stage.account_sid = generate_id() 94 self.stage.auth = generate_id() 95 self.stage.from_number = generate_id() 96 self.stage.save() 97 self.client.get( 98 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 99 ) 100 response = self.client.get( 101 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 102 ) 103 self.assertStageResponse( 104 response, 105 self.flow, 106 self.user, 107 component="ak-stage-authenticator-sms", 108 phone_number_required=True, 109 ) 110 number = generate_id() 111 112 with Mocker() as mocker: 113 mocker.post( 114 ( 115 "https://api.twilio.com/2010-04-01/Accounts/" 116 f"{self.stage.account_sid}/Messages.json" 117 ), 118 json={}, 119 ) 120 response = self.client.post( 121 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 122 data={"component": "ak-stage-authenticator-sms", "phone_number": number}, 123 ) 124 self.assertEqual(response.status_code, 200) 125 self.assertEqual(mocker.call_count, 1) 126 self.assertEqual(mocker.request_history[0].method, "POST") 127 request_body = dict(parse_qsl(mocker.request_history[0].body)) 128 device: SMSDevice = self.get_flow_plan().context[PLAN_CONTEXT_SMS_DEVICE] 129 self.assertEqual( 130 request_body, 131 { 132 "To": number, 133 "From": self.stage.from_number, 134 "Body": f"Use this code to authenticate in authentik: {device.token}", 135 }, 136 ) 137 self.assertStageResponse( 138 response, 139 self.flow, 140 self.user, 141 component="ak-stage-authenticator-sms", 142 response_errors={}, 143 phone_number_required=False, 144 ) 145 146 def test_stage_context_data(self): 147 """test stage context data""" 148 self.client.get( 149 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 150 ) 151 sms_send_mock = MagicMock() 152 with ( 153 patch( 154 ( 155 "authentik.stages.authenticator_sms.stage." 156 "AuthenticatorSMSStageView._has_phone_number" 157 ), 158 MagicMock( 159 return_value="1234", 160 ), 161 ), 162 patch( 163 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 164 sms_send_mock, 165 ), 166 ): 167 response = self.client.get( 168 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 169 ) 170 sms_send_mock.assert_called_once() 171 self.assertStageResponse( 172 response, 173 self.flow, 174 self.user, 175 component="ak-stage-authenticator-sms", 176 phone_number_required=False, 177 ) 178 179 def test_stage_context_data_duplicate(self): 180 """test stage context data (phone number exists already)""" 181 self.client.get( 182 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 183 ) 184 plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] 185 plan.context[PLAN_CONTEXT_PROMPT] = { 186 PLAN_CONTEXT_PHONE: "1234", 187 } 188 session = self.client.session 189 session[SESSION_KEY_PLAN] = plan 190 session.save() 191 SMSDevice.objects.create( 192 phone_number="1234", 193 user=self.user, 194 stage=self.stage, 195 ) 196 sms_send_mock = MagicMock() 197 with ( 198 patch( 199 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 200 sms_send_mock, 201 ), 202 ): 203 response = self.client.get( 204 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 205 ) 206 self.assertStageResponse( 207 response, 208 self.flow, 209 self.user, 210 component="ak-stage-authenticator-sms", 211 phone_number_required=True, 212 ) 213 plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] 214 self.assertEqual(plan.context[PLAN_CONTEXT_PROMPT], {}) 215 216 def test_stage_submit_full(self): 217 """test stage (submit)""" 218 self.client.get( 219 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 220 ) 221 response = self.client.get( 222 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 223 ) 224 self.assertStageResponse( 225 response, 226 self.flow, 227 self.user, 228 component="ak-stage-authenticator-sms", 229 phone_number_required=True, 230 ) 231 sms_send_mock = MagicMock() 232 with patch( 233 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 234 sms_send_mock, 235 ): 236 response = self.client.post( 237 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 238 data={"component": "ak-stage-authenticator-sms", "phone_number": "foo"}, 239 ) 240 self.assertEqual(response.status_code, 200) 241 sms_send_mock.assert_called_once() 242 self.assertStageResponse( 243 response, 244 self.flow, 245 self.user, 246 component="ak-stage-authenticator-sms", 247 response_errors={}, 248 phone_number_required=False, 249 ) 250 with patch( 251 "authentik.stages.authenticator_sms.models.SMSDevice.verify_token", 252 MagicMock(return_value=True), 253 ): 254 response = self.client.post( 255 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 256 data={ 257 "component": "ak-stage-authenticator-sms", 258 "phone_number": "foo", 259 "code": "123456", 260 }, 261 ) 262 self.assertEqual(response.status_code, 200) 263 self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) 264 265 def test_stage_hash(self): 266 """test stage (verify_only)""" 267 self.stage.verify_only = True 268 self.stage.save() 269 self.client.get( 270 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 271 ) 272 response = self.client.get( 273 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 274 ) 275 self.assertStageResponse( 276 response, 277 self.flow, 278 self.user, 279 component="ak-stage-authenticator-sms", 280 phone_number_required=True, 281 ) 282 sms_send_mock = MagicMock() 283 with patch( 284 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 285 sms_send_mock, 286 ): 287 response = self.client.post( 288 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 289 data={"component": "ak-stage-authenticator-sms", "phone_number": "foo"}, 290 ) 291 self.assertEqual(response.status_code, 200) 292 sms_send_mock.assert_called_once() 293 self.assertStageResponse( 294 response, 295 self.flow, 296 self.user, 297 component="ak-stage-authenticator-sms", 298 response_errors={}, 299 phone_number_required=False, 300 ) 301 with patch( 302 "authentik.stages.authenticator_sms.models.SMSDevice.verify_token", 303 MagicMock(return_value=True), 304 ): 305 response = self.client.post( 306 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 307 data={ 308 "component": "ak-stage-authenticator-sms", 309 "phone_number": "foo", 310 "code": "123456", 311 }, 312 ) 313 self.assertEqual(response.status_code, 200) 314 self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) 315 device: SMSDevice = SMSDevice.objects.filter(user=self.user).first() 316 self.assertTrue(device.is_hashed) 317 318 def test_stage_hash_twice(self): 319 """test stage (hash + duplicate)""" 320 SMSDevice.objects.create( 321 user=create_test_admin_user(), 322 stage=self.stage, 323 phone_number=hash_phone_number("foo"), 324 ) 325 self.stage.verify_only = True 326 self.stage.save() 327 self.client.get( 328 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 329 ) 330 response = self.client.get( 331 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 332 ) 333 self.assertStageResponse( 334 response, 335 self.flow, 336 self.user, 337 component="ak-stage-authenticator-sms", 338 phone_number_required=True, 339 ) 340 sms_send_mock = MagicMock() 341 with patch( 342 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 343 sms_send_mock, 344 ): 345 response = self.client.post( 346 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 347 data={"component": "ak-stage-authenticator-sms", "phone_number": "foo"}, 348 ) 349 self.assertEqual(response.status_code, 200) 350 self.assertStageResponse( 351 response, 352 self.flow, 353 self.user, 354 component="ak-stage-authenticator-sms", 355 response_errors={ 356 "non_field_errors": [{"code": "invalid", "string": "Invalid phone number"}] 357 }, 358 phone_number_required=False, 359 )
26class AuthenticatorSMSStageTests(FlowTestCase): 27 """Test SMS API""" 28 29 def setUp(self) -> None: 30 super().setUp() 31 self.flow = create_test_flow() 32 self.stage: AuthenticatorSMSStage = AuthenticatorSMSStage.objects.create( 33 name="foo", 34 provider=SMSProviders.TWILIO, 35 configure_flow=self.flow, 36 ) 37 FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=0) 38 self.user = create_test_admin_user() 39 self.client.force_login(self.user) 40 41 def test_stage_no_prefill(self): 42 """test stage""" 43 self.client.get( 44 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 45 ) 46 response = self.client.get( 47 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 48 ) 49 self.assertStageResponse( 50 response, 51 self.flow, 52 self.user, 53 component="ak-stage-authenticator-sms", 54 phone_number_required=True, 55 ) 56 57 def test_stage_submit(self): 58 """test stage (submit)""" 59 self.client.get( 60 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 61 ) 62 response = self.client.get( 63 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 64 ) 65 self.assertStageResponse( 66 response, 67 self.flow, 68 self.user, 69 component="ak-stage-authenticator-sms", 70 phone_number_required=True, 71 ) 72 sms_send_mock = MagicMock() 73 with patch( 74 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 75 sms_send_mock, 76 ): 77 response = self.client.post( 78 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 79 data={"component": "ak-stage-authenticator-sms", "phone_number": "foo"}, 80 ) 81 self.assertEqual(response.status_code, 200) 82 sms_send_mock.assert_called_once() 83 self.assertStageResponse( 84 response, 85 self.flow, 86 self.user, 87 component="ak-stage-authenticator-sms", 88 response_errors={}, 89 phone_number_required=False, 90 ) 91 92 def test_stage_submit_twilio(self): 93 """test stage (submit) (twilio)""" 94 self.stage.account_sid = generate_id() 95 self.stage.auth = generate_id() 96 self.stage.from_number = generate_id() 97 self.stage.save() 98 self.client.get( 99 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 100 ) 101 response = self.client.get( 102 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 103 ) 104 self.assertStageResponse( 105 response, 106 self.flow, 107 self.user, 108 component="ak-stage-authenticator-sms", 109 phone_number_required=True, 110 ) 111 number = generate_id() 112 113 with Mocker() as mocker: 114 mocker.post( 115 ( 116 "https://api.twilio.com/2010-04-01/Accounts/" 117 f"{self.stage.account_sid}/Messages.json" 118 ), 119 json={}, 120 ) 121 response = self.client.post( 122 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 123 data={"component": "ak-stage-authenticator-sms", "phone_number": number}, 124 ) 125 self.assertEqual(response.status_code, 200) 126 self.assertEqual(mocker.call_count, 1) 127 self.assertEqual(mocker.request_history[0].method, "POST") 128 request_body = dict(parse_qsl(mocker.request_history[0].body)) 129 device: SMSDevice = self.get_flow_plan().context[PLAN_CONTEXT_SMS_DEVICE] 130 self.assertEqual( 131 request_body, 132 { 133 "To": number, 134 "From": self.stage.from_number, 135 "Body": f"Use this code to authenticate in authentik: {device.token}", 136 }, 137 ) 138 self.assertStageResponse( 139 response, 140 self.flow, 141 self.user, 142 component="ak-stage-authenticator-sms", 143 response_errors={}, 144 phone_number_required=False, 145 ) 146 147 def test_stage_context_data(self): 148 """test stage context data""" 149 self.client.get( 150 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 151 ) 152 sms_send_mock = MagicMock() 153 with ( 154 patch( 155 ( 156 "authentik.stages.authenticator_sms.stage." 157 "AuthenticatorSMSStageView._has_phone_number" 158 ), 159 MagicMock( 160 return_value="1234", 161 ), 162 ), 163 patch( 164 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 165 sms_send_mock, 166 ), 167 ): 168 response = self.client.get( 169 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 170 ) 171 sms_send_mock.assert_called_once() 172 self.assertStageResponse( 173 response, 174 self.flow, 175 self.user, 176 component="ak-stage-authenticator-sms", 177 phone_number_required=False, 178 ) 179 180 def test_stage_context_data_duplicate(self): 181 """test stage context data (phone number exists already)""" 182 self.client.get( 183 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 184 ) 185 plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] 186 plan.context[PLAN_CONTEXT_PROMPT] = { 187 PLAN_CONTEXT_PHONE: "1234", 188 } 189 session = self.client.session 190 session[SESSION_KEY_PLAN] = plan 191 session.save() 192 SMSDevice.objects.create( 193 phone_number="1234", 194 user=self.user, 195 stage=self.stage, 196 ) 197 sms_send_mock = MagicMock() 198 with ( 199 patch( 200 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 201 sms_send_mock, 202 ), 203 ): 204 response = self.client.get( 205 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 206 ) 207 self.assertStageResponse( 208 response, 209 self.flow, 210 self.user, 211 component="ak-stage-authenticator-sms", 212 phone_number_required=True, 213 ) 214 plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] 215 self.assertEqual(plan.context[PLAN_CONTEXT_PROMPT], {}) 216 217 def test_stage_submit_full(self): 218 """test stage (submit)""" 219 self.client.get( 220 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 221 ) 222 response = self.client.get( 223 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 224 ) 225 self.assertStageResponse( 226 response, 227 self.flow, 228 self.user, 229 component="ak-stage-authenticator-sms", 230 phone_number_required=True, 231 ) 232 sms_send_mock = MagicMock() 233 with patch( 234 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 235 sms_send_mock, 236 ): 237 response = self.client.post( 238 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 239 data={"component": "ak-stage-authenticator-sms", "phone_number": "foo"}, 240 ) 241 self.assertEqual(response.status_code, 200) 242 sms_send_mock.assert_called_once() 243 self.assertStageResponse( 244 response, 245 self.flow, 246 self.user, 247 component="ak-stage-authenticator-sms", 248 response_errors={}, 249 phone_number_required=False, 250 ) 251 with patch( 252 "authentik.stages.authenticator_sms.models.SMSDevice.verify_token", 253 MagicMock(return_value=True), 254 ): 255 response = self.client.post( 256 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 257 data={ 258 "component": "ak-stage-authenticator-sms", 259 "phone_number": "foo", 260 "code": "123456", 261 }, 262 ) 263 self.assertEqual(response.status_code, 200) 264 self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) 265 266 def test_stage_hash(self): 267 """test stage (verify_only)""" 268 self.stage.verify_only = True 269 self.stage.save() 270 self.client.get( 271 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 272 ) 273 response = self.client.get( 274 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 275 ) 276 self.assertStageResponse( 277 response, 278 self.flow, 279 self.user, 280 component="ak-stage-authenticator-sms", 281 phone_number_required=True, 282 ) 283 sms_send_mock = MagicMock() 284 with patch( 285 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 286 sms_send_mock, 287 ): 288 response = self.client.post( 289 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 290 data={"component": "ak-stage-authenticator-sms", "phone_number": "foo"}, 291 ) 292 self.assertEqual(response.status_code, 200) 293 sms_send_mock.assert_called_once() 294 self.assertStageResponse( 295 response, 296 self.flow, 297 self.user, 298 component="ak-stage-authenticator-sms", 299 response_errors={}, 300 phone_number_required=False, 301 ) 302 with patch( 303 "authentik.stages.authenticator_sms.models.SMSDevice.verify_token", 304 MagicMock(return_value=True), 305 ): 306 response = self.client.post( 307 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 308 data={ 309 "component": "ak-stage-authenticator-sms", 310 "phone_number": "foo", 311 "code": "123456", 312 }, 313 ) 314 self.assertEqual(response.status_code, 200) 315 self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) 316 device: SMSDevice = SMSDevice.objects.filter(user=self.user).first() 317 self.assertTrue(device.is_hashed) 318 319 def test_stage_hash_twice(self): 320 """test stage (hash + duplicate)""" 321 SMSDevice.objects.create( 322 user=create_test_admin_user(), 323 stage=self.stage, 324 phone_number=hash_phone_number("foo"), 325 ) 326 self.stage.verify_only = True 327 self.stage.save() 328 self.client.get( 329 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 330 ) 331 response = self.client.get( 332 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 333 ) 334 self.assertStageResponse( 335 response, 336 self.flow, 337 self.user, 338 component="ak-stage-authenticator-sms", 339 phone_number_required=True, 340 ) 341 sms_send_mock = MagicMock() 342 with patch( 343 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 344 sms_send_mock, 345 ): 346 response = self.client.post( 347 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 348 data={"component": "ak-stage-authenticator-sms", "phone_number": "foo"}, 349 ) 350 self.assertEqual(response.status_code, 200) 351 self.assertStageResponse( 352 response, 353 self.flow, 354 self.user, 355 component="ak-stage-authenticator-sms", 356 response_errors={ 357 "non_field_errors": [{"code": "invalid", "string": "Invalid phone number"}] 358 }, 359 phone_number_required=False, 360 )
Test SMS API
def
setUp(self) -> None:
29 def setUp(self) -> None: 30 super().setUp() 31 self.flow = create_test_flow() 32 self.stage: AuthenticatorSMSStage = AuthenticatorSMSStage.objects.create( 33 name="foo", 34 provider=SMSProviders.TWILIO, 35 configure_flow=self.flow, 36 ) 37 FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=0) 38 self.user = create_test_admin_user() 39 self.client.force_login(self.user)
Hook method for setting up the test fixture before exercising it.
def
test_stage_no_prefill(self):
41 def test_stage_no_prefill(self): 42 """test stage""" 43 self.client.get( 44 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 45 ) 46 response = self.client.get( 47 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 48 ) 49 self.assertStageResponse( 50 response, 51 self.flow, 52 self.user, 53 component="ak-stage-authenticator-sms", 54 phone_number_required=True, 55 )
test stage
def
test_stage_submit(self):
57 def test_stage_submit(self): 58 """test stage (submit)""" 59 self.client.get( 60 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 61 ) 62 response = self.client.get( 63 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 64 ) 65 self.assertStageResponse( 66 response, 67 self.flow, 68 self.user, 69 component="ak-stage-authenticator-sms", 70 phone_number_required=True, 71 ) 72 sms_send_mock = MagicMock() 73 with patch( 74 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 75 sms_send_mock, 76 ): 77 response = self.client.post( 78 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 79 data={"component": "ak-stage-authenticator-sms", "phone_number": "foo"}, 80 ) 81 self.assertEqual(response.status_code, 200) 82 sms_send_mock.assert_called_once() 83 self.assertStageResponse( 84 response, 85 self.flow, 86 self.user, 87 component="ak-stage-authenticator-sms", 88 response_errors={}, 89 phone_number_required=False, 90 )
test stage (submit)
def
test_stage_submit_twilio(self):
92 def test_stage_submit_twilio(self): 93 """test stage (submit) (twilio)""" 94 self.stage.account_sid = generate_id() 95 self.stage.auth = generate_id() 96 self.stage.from_number = generate_id() 97 self.stage.save() 98 self.client.get( 99 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 100 ) 101 response = self.client.get( 102 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 103 ) 104 self.assertStageResponse( 105 response, 106 self.flow, 107 self.user, 108 component="ak-stage-authenticator-sms", 109 phone_number_required=True, 110 ) 111 number = generate_id() 112 113 with Mocker() as mocker: 114 mocker.post( 115 ( 116 "https://api.twilio.com/2010-04-01/Accounts/" 117 f"{self.stage.account_sid}/Messages.json" 118 ), 119 json={}, 120 ) 121 response = self.client.post( 122 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 123 data={"component": "ak-stage-authenticator-sms", "phone_number": number}, 124 ) 125 self.assertEqual(response.status_code, 200) 126 self.assertEqual(mocker.call_count, 1) 127 self.assertEqual(mocker.request_history[0].method, "POST") 128 request_body = dict(parse_qsl(mocker.request_history[0].body)) 129 device: SMSDevice = self.get_flow_plan().context[PLAN_CONTEXT_SMS_DEVICE] 130 self.assertEqual( 131 request_body, 132 { 133 "To": number, 134 "From": self.stage.from_number, 135 "Body": f"Use this code to authenticate in authentik: {device.token}", 136 }, 137 ) 138 self.assertStageResponse( 139 response, 140 self.flow, 141 self.user, 142 component="ak-stage-authenticator-sms", 143 response_errors={}, 144 phone_number_required=False, 145 )
test stage (submit) (twilio)
def
test_stage_context_data(self):
147 def test_stage_context_data(self): 148 """test stage context data""" 149 self.client.get( 150 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 151 ) 152 sms_send_mock = MagicMock() 153 with ( 154 patch( 155 ( 156 "authentik.stages.authenticator_sms.stage." 157 "AuthenticatorSMSStageView._has_phone_number" 158 ), 159 MagicMock( 160 return_value="1234", 161 ), 162 ), 163 patch( 164 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 165 sms_send_mock, 166 ), 167 ): 168 response = self.client.get( 169 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 170 ) 171 sms_send_mock.assert_called_once() 172 self.assertStageResponse( 173 response, 174 self.flow, 175 self.user, 176 component="ak-stage-authenticator-sms", 177 phone_number_required=False, 178 )
test stage context data
def
test_stage_context_data_duplicate(self):
180 def test_stage_context_data_duplicate(self): 181 """test stage context data (phone number exists already)""" 182 self.client.get( 183 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 184 ) 185 plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] 186 plan.context[PLAN_CONTEXT_PROMPT] = { 187 PLAN_CONTEXT_PHONE: "1234", 188 } 189 session = self.client.session 190 session[SESSION_KEY_PLAN] = plan 191 session.save() 192 SMSDevice.objects.create( 193 phone_number="1234", 194 user=self.user, 195 stage=self.stage, 196 ) 197 sms_send_mock = MagicMock() 198 with ( 199 patch( 200 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 201 sms_send_mock, 202 ), 203 ): 204 response = self.client.get( 205 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 206 ) 207 self.assertStageResponse( 208 response, 209 self.flow, 210 self.user, 211 component="ak-stage-authenticator-sms", 212 phone_number_required=True, 213 ) 214 plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] 215 self.assertEqual(plan.context[PLAN_CONTEXT_PROMPT], {})
test stage context data (phone number exists already)
def
test_stage_submit_full(self):
217 def test_stage_submit_full(self): 218 """test stage (submit)""" 219 self.client.get( 220 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 221 ) 222 response = self.client.get( 223 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 224 ) 225 self.assertStageResponse( 226 response, 227 self.flow, 228 self.user, 229 component="ak-stage-authenticator-sms", 230 phone_number_required=True, 231 ) 232 sms_send_mock = MagicMock() 233 with patch( 234 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 235 sms_send_mock, 236 ): 237 response = self.client.post( 238 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 239 data={"component": "ak-stage-authenticator-sms", "phone_number": "foo"}, 240 ) 241 self.assertEqual(response.status_code, 200) 242 sms_send_mock.assert_called_once() 243 self.assertStageResponse( 244 response, 245 self.flow, 246 self.user, 247 component="ak-stage-authenticator-sms", 248 response_errors={}, 249 phone_number_required=False, 250 ) 251 with patch( 252 "authentik.stages.authenticator_sms.models.SMSDevice.verify_token", 253 MagicMock(return_value=True), 254 ): 255 response = self.client.post( 256 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 257 data={ 258 "component": "ak-stage-authenticator-sms", 259 "phone_number": "foo", 260 "code": "123456", 261 }, 262 ) 263 self.assertEqual(response.status_code, 200) 264 self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
test stage (submit)
def
test_stage_hash(self):
266 def test_stage_hash(self): 267 """test stage (verify_only)""" 268 self.stage.verify_only = True 269 self.stage.save() 270 self.client.get( 271 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 272 ) 273 response = self.client.get( 274 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 275 ) 276 self.assertStageResponse( 277 response, 278 self.flow, 279 self.user, 280 component="ak-stage-authenticator-sms", 281 phone_number_required=True, 282 ) 283 sms_send_mock = MagicMock() 284 with patch( 285 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 286 sms_send_mock, 287 ): 288 response = self.client.post( 289 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 290 data={"component": "ak-stage-authenticator-sms", "phone_number": "foo"}, 291 ) 292 self.assertEqual(response.status_code, 200) 293 sms_send_mock.assert_called_once() 294 self.assertStageResponse( 295 response, 296 self.flow, 297 self.user, 298 component="ak-stage-authenticator-sms", 299 response_errors={}, 300 phone_number_required=False, 301 ) 302 with patch( 303 "authentik.stages.authenticator_sms.models.SMSDevice.verify_token", 304 MagicMock(return_value=True), 305 ): 306 response = self.client.post( 307 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 308 data={ 309 "component": "ak-stage-authenticator-sms", 310 "phone_number": "foo", 311 "code": "123456", 312 }, 313 ) 314 self.assertEqual(response.status_code, 200) 315 self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) 316 device: SMSDevice = SMSDevice.objects.filter(user=self.user).first() 317 self.assertTrue(device.is_hashed)
test stage (verify_only)
def
test_stage_hash_twice(self):
319 def test_stage_hash_twice(self): 320 """test stage (hash + duplicate)""" 321 SMSDevice.objects.create( 322 user=create_test_admin_user(), 323 stage=self.stage, 324 phone_number=hash_phone_number("foo"), 325 ) 326 self.stage.verify_only = True 327 self.stage.save() 328 self.client.get( 329 reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}), 330 ) 331 response = self.client.get( 332 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 333 ) 334 self.assertStageResponse( 335 response, 336 self.flow, 337 self.user, 338 component="ak-stage-authenticator-sms", 339 phone_number_required=True, 340 ) 341 sms_send_mock = MagicMock() 342 with patch( 343 "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", 344 sms_send_mock, 345 ): 346 response = self.client.post( 347 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 348 data={"component": "ak-stage-authenticator-sms", "phone_number": "foo"}, 349 ) 350 self.assertEqual(response.status_code, 200) 351 self.assertStageResponse( 352 response, 353 self.flow, 354 self.user, 355 component="ak-stage-authenticator-sms", 356 response_errors={ 357 "non_field_errors": [{"code": "invalid", "string": "Invalid phone number"}] 358 }, 359 phone_number_required=False, 360 )
test stage (hash + duplicate)