authentik.stages.captcha.tests
captcha tests
1"""captcha tests""" 2 3from django.urls import reverse 4from requests_mock import Mocker 5 6from authentik.core.tests.utils import create_test_admin_user, create_test_flow 7from authentik.flows.markers import StageMarker 8from authentik.flows.models import FlowDesignation, FlowStageBinding 9from authentik.flows.planner import FlowPlan 10from authentik.flows.tests import FlowTestCase 11from authentik.flows.views.executor import SESSION_KEY_PLAN 12from authentik.lib.generators import generate_id 13from authentik.stages.captcha.models import CaptchaStage 14from authentik.stages.captcha.stage import ( 15 PLAN_CONTEXT_CAPTCHA_PRIVATE_KEY, 16 PLAN_CONTEXT_CAPTCHA_SITE_KEY, 17) 18 19# https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do 20RECAPTCHA_PUBLIC_KEY = "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI" 21RECAPTCHA_PRIVATE_KEY = "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe" 22 23 24class TestCaptchaStage(FlowTestCase): 25 """Captcha tests""" 26 27 def setUp(self): 28 super().setUp() 29 self.user = create_test_admin_user() 30 self.flow = create_test_flow(FlowDesignation.AUTHENTICATION) 31 32 self.stage: CaptchaStage = CaptchaStage.objects.create( 33 name="captcha", 34 public_key=RECAPTCHA_PUBLIC_KEY, 35 private_key=RECAPTCHA_PRIVATE_KEY, 36 ) 37 self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) 38 39 @Mocker() 40 def test_valid(self, mock: Mocker): 41 """Test valid captcha""" 42 mock.post( 43 "https://www.recaptcha.net/recaptcha/api/siteverify", 44 json={ 45 "success": True, 46 "score": 0.5, 47 }, 48 ) 49 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 50 session = self.client.session 51 session[SESSION_KEY_PLAN] = plan 52 session.save() 53 response = self.client.post( 54 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 55 {"token": "PASSED"}, 56 ) 57 self.assertEqual(response.status_code, 200) 58 self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) 59 60 @Mocker() 61 def test_valid_override(self, mock: Mocker): 62 """Test valid captcha""" 63 self.stage.private_key = generate_id() 64 self.stage.public_key = generate_id() 65 mock.post( 66 "https://www.recaptcha.net/recaptcha/api/siteverify", 67 json={ 68 "success": True, 69 "score": 0.5, 70 }, 71 ) 72 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 73 site_key = generate_id() 74 private_key = generate_id() 75 plan.context = { 76 PLAN_CONTEXT_CAPTCHA_SITE_KEY: site_key, 77 PLAN_CONTEXT_CAPTCHA_PRIVATE_KEY: private_key, 78 } 79 session = self.client.session 80 session[SESSION_KEY_PLAN] = plan 81 session.save() 82 response = self.client.get( 83 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 84 ) 85 self.assertStageResponse(response, component="ak-stage-captcha", site_key=site_key) 86 response = self.client.post( 87 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 88 {"token": "PASSED"}, 89 ) 90 self.assertEqual(response.status_code, 200) 91 self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) 92 self.assertIn(private_key, mock.request_history[0].text) 93 94 @Mocker() 95 def test_invalid_score_high(self, mock: Mocker): 96 """Test invalid captcha (score too high)""" 97 mock.post( 98 "https://www.recaptcha.net/recaptcha/api/siteverify", 99 json={ 100 "success": True, 101 "score": 99, 102 }, 103 ) 104 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 105 session = self.client.session 106 session[SESSION_KEY_PLAN] = plan 107 session.save() 108 response = self.client.post( 109 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 110 {"token": "PASSED"}, 111 ) 112 self.assertEqual(response.status_code, 200) 113 self.assertStageResponse( 114 response, 115 component="ak-stage-captcha", 116 response_errors={"token": [{"string": "Invalid captcha response", "code": "invalid"}]}, 117 ) 118 119 @Mocker() 120 def test_invalid_score_low(self, mock: Mocker): 121 """Test invalid captcha (score too low)""" 122 mock.post( 123 "https://www.recaptcha.net/recaptcha/api/siteverify", 124 json={ 125 "success": True, 126 "score": -3, 127 }, 128 ) 129 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 130 session = self.client.session 131 session[SESSION_KEY_PLAN] = plan 132 session.save() 133 response = self.client.post( 134 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 135 {"token": "PASSED"}, 136 ) 137 self.assertEqual(response.status_code, 200) 138 self.assertStageResponse( 139 response, 140 component="ak-stage-captcha", 141 response_errors={"token": [{"string": "Invalid captcha response", "code": "invalid"}]}, 142 ) 143 144 @Mocker() 145 def test_invalid_score_low_continue(self, mock: Mocker): 146 """Test invalid captcha (score too low, but continue)""" 147 self.stage.error_on_invalid_score = False 148 self.stage.save() 149 mock.post( 150 "https://www.recaptcha.net/recaptcha/api/siteverify", 151 json={ 152 "success": True, 153 "score": -3, 154 }, 155 ) 156 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 157 session = self.client.session 158 session[SESSION_KEY_PLAN] = plan 159 session.save() 160 response = self.client.post( 161 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 162 {"token": "PASSED"}, 163 ) 164 self.assertEqual(response.status_code, 200) 165 self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) 166 167 def test_urls(self): 168 """Test URLs captcha""" 169 self.stage.js_url = "https://test.goauthentik.io/test.js" 170 self.stage.api_url = "https://test.goauthentik.io/test" 171 self.stage.save() 172 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 173 session = self.client.session 174 session[SESSION_KEY_PLAN] = plan 175 session.save() 176 response = self.client.get( 177 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 178 ) 179 self.assertEqual(response.status_code, 200) 180 self.assertStageResponse( 181 response, 182 self.flow, 183 js_url="https://test.goauthentik.io/test.js", 184 )
RECAPTCHA_PUBLIC_KEY =
'6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'
RECAPTCHA_PRIVATE_KEY =
'6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe'
25class TestCaptchaStage(FlowTestCase): 26 """Captcha tests""" 27 28 def setUp(self): 29 super().setUp() 30 self.user = create_test_admin_user() 31 self.flow = create_test_flow(FlowDesignation.AUTHENTICATION) 32 33 self.stage: CaptchaStage = CaptchaStage.objects.create( 34 name="captcha", 35 public_key=RECAPTCHA_PUBLIC_KEY, 36 private_key=RECAPTCHA_PRIVATE_KEY, 37 ) 38 self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2) 39 40 @Mocker() 41 def test_valid(self, mock: Mocker): 42 """Test valid captcha""" 43 mock.post( 44 "https://www.recaptcha.net/recaptcha/api/siteverify", 45 json={ 46 "success": True, 47 "score": 0.5, 48 }, 49 ) 50 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 51 session = self.client.session 52 session[SESSION_KEY_PLAN] = plan 53 session.save() 54 response = self.client.post( 55 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 56 {"token": "PASSED"}, 57 ) 58 self.assertEqual(response.status_code, 200) 59 self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) 60 61 @Mocker() 62 def test_valid_override(self, mock: Mocker): 63 """Test valid captcha""" 64 self.stage.private_key = generate_id() 65 self.stage.public_key = generate_id() 66 mock.post( 67 "https://www.recaptcha.net/recaptcha/api/siteverify", 68 json={ 69 "success": True, 70 "score": 0.5, 71 }, 72 ) 73 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 74 site_key = generate_id() 75 private_key = generate_id() 76 plan.context = { 77 PLAN_CONTEXT_CAPTCHA_SITE_KEY: site_key, 78 PLAN_CONTEXT_CAPTCHA_PRIVATE_KEY: private_key, 79 } 80 session = self.client.session 81 session[SESSION_KEY_PLAN] = plan 82 session.save() 83 response = self.client.get( 84 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 85 ) 86 self.assertStageResponse(response, component="ak-stage-captcha", site_key=site_key) 87 response = self.client.post( 88 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 89 {"token": "PASSED"}, 90 ) 91 self.assertEqual(response.status_code, 200) 92 self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) 93 self.assertIn(private_key, mock.request_history[0].text) 94 95 @Mocker() 96 def test_invalid_score_high(self, mock: Mocker): 97 """Test invalid captcha (score too high)""" 98 mock.post( 99 "https://www.recaptcha.net/recaptcha/api/siteverify", 100 json={ 101 "success": True, 102 "score": 99, 103 }, 104 ) 105 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 106 session = self.client.session 107 session[SESSION_KEY_PLAN] = plan 108 session.save() 109 response = self.client.post( 110 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 111 {"token": "PASSED"}, 112 ) 113 self.assertEqual(response.status_code, 200) 114 self.assertStageResponse( 115 response, 116 component="ak-stage-captcha", 117 response_errors={"token": [{"string": "Invalid captcha response", "code": "invalid"}]}, 118 ) 119 120 @Mocker() 121 def test_invalid_score_low(self, mock: Mocker): 122 """Test invalid captcha (score too low)""" 123 mock.post( 124 "https://www.recaptcha.net/recaptcha/api/siteverify", 125 json={ 126 "success": True, 127 "score": -3, 128 }, 129 ) 130 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 131 session = self.client.session 132 session[SESSION_KEY_PLAN] = plan 133 session.save() 134 response = self.client.post( 135 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 136 {"token": "PASSED"}, 137 ) 138 self.assertEqual(response.status_code, 200) 139 self.assertStageResponse( 140 response, 141 component="ak-stage-captcha", 142 response_errors={"token": [{"string": "Invalid captcha response", "code": "invalid"}]}, 143 ) 144 145 @Mocker() 146 def test_invalid_score_low_continue(self, mock: Mocker): 147 """Test invalid captcha (score too low, but continue)""" 148 self.stage.error_on_invalid_score = False 149 self.stage.save() 150 mock.post( 151 "https://www.recaptcha.net/recaptcha/api/siteverify", 152 json={ 153 "success": True, 154 "score": -3, 155 }, 156 ) 157 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 158 session = self.client.session 159 session[SESSION_KEY_PLAN] = plan 160 session.save() 161 response = self.client.post( 162 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 163 {"token": "PASSED"}, 164 ) 165 self.assertEqual(response.status_code, 200) 166 self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) 167 168 def test_urls(self): 169 """Test URLs captcha""" 170 self.stage.js_url = "https://test.goauthentik.io/test.js" 171 self.stage.api_url = "https://test.goauthentik.io/test" 172 self.stage.save() 173 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 174 session = self.client.session 175 session[SESSION_KEY_PLAN] = plan 176 session.save() 177 response = self.client.get( 178 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 179 ) 180 self.assertEqual(response.status_code, 200) 181 self.assertStageResponse( 182 response, 183 self.flow, 184 js_url="https://test.goauthentik.io/test.js", 185 )
Captcha tests
def
setUp(self):
28 def setUp(self): 29 super().setUp() 30 self.user = create_test_admin_user() 31 self.flow = create_test_flow(FlowDesignation.AUTHENTICATION) 32 33 self.stage: CaptchaStage = CaptchaStage.objects.create( 34 name="captcha", 35 public_key=RECAPTCHA_PUBLIC_KEY, 36 private_key=RECAPTCHA_PRIVATE_KEY, 37 ) 38 self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)
Hook method for setting up the test fixture before exercising it.
@Mocker()
def
test_valid(self, mock: requests_mock.mocker.Mocker):
40 @Mocker() 41 def test_valid(self, mock: Mocker): 42 """Test valid captcha""" 43 mock.post( 44 "https://www.recaptcha.net/recaptcha/api/siteverify", 45 json={ 46 "success": True, 47 "score": 0.5, 48 }, 49 ) 50 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 51 session = self.client.session 52 session[SESSION_KEY_PLAN] = plan 53 session.save() 54 response = self.client.post( 55 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 56 {"token": "PASSED"}, 57 ) 58 self.assertEqual(response.status_code, 200) 59 self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
Test valid captcha
@Mocker()
def
test_valid_override(self, mock: requests_mock.mocker.Mocker):
61 @Mocker() 62 def test_valid_override(self, mock: Mocker): 63 """Test valid captcha""" 64 self.stage.private_key = generate_id() 65 self.stage.public_key = generate_id() 66 mock.post( 67 "https://www.recaptcha.net/recaptcha/api/siteverify", 68 json={ 69 "success": True, 70 "score": 0.5, 71 }, 72 ) 73 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 74 site_key = generate_id() 75 private_key = generate_id() 76 plan.context = { 77 PLAN_CONTEXT_CAPTCHA_SITE_KEY: site_key, 78 PLAN_CONTEXT_CAPTCHA_PRIVATE_KEY: private_key, 79 } 80 session = self.client.session 81 session[SESSION_KEY_PLAN] = plan 82 session.save() 83 response = self.client.get( 84 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 85 ) 86 self.assertStageResponse(response, component="ak-stage-captcha", site_key=site_key) 87 response = self.client.post( 88 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 89 {"token": "PASSED"}, 90 ) 91 self.assertEqual(response.status_code, 200) 92 self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) 93 self.assertIn(private_key, mock.request_history[0].text)
Test valid captcha
@Mocker()
def
test_invalid_score_high(self, mock: requests_mock.mocker.Mocker):
95 @Mocker() 96 def test_invalid_score_high(self, mock: Mocker): 97 """Test invalid captcha (score too high)""" 98 mock.post( 99 "https://www.recaptcha.net/recaptcha/api/siteverify", 100 json={ 101 "success": True, 102 "score": 99, 103 }, 104 ) 105 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 106 session = self.client.session 107 session[SESSION_KEY_PLAN] = plan 108 session.save() 109 response = self.client.post( 110 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 111 {"token": "PASSED"}, 112 ) 113 self.assertEqual(response.status_code, 200) 114 self.assertStageResponse( 115 response, 116 component="ak-stage-captcha", 117 response_errors={"token": [{"string": "Invalid captcha response", "code": "invalid"}]}, 118 )
Test invalid captcha (score too high)
@Mocker()
def
test_invalid_score_low(self, mock: requests_mock.mocker.Mocker):
120 @Mocker() 121 def test_invalid_score_low(self, mock: Mocker): 122 """Test invalid captcha (score too low)""" 123 mock.post( 124 "https://www.recaptcha.net/recaptcha/api/siteverify", 125 json={ 126 "success": True, 127 "score": -3, 128 }, 129 ) 130 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 131 session = self.client.session 132 session[SESSION_KEY_PLAN] = plan 133 session.save() 134 response = self.client.post( 135 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 136 {"token": "PASSED"}, 137 ) 138 self.assertEqual(response.status_code, 200) 139 self.assertStageResponse( 140 response, 141 component="ak-stage-captcha", 142 response_errors={"token": [{"string": "Invalid captcha response", "code": "invalid"}]}, 143 )
Test invalid captcha (score too low)
@Mocker()
def
test_invalid_score_low_continue(self, mock: requests_mock.mocker.Mocker):
145 @Mocker() 146 def test_invalid_score_low_continue(self, mock: Mocker): 147 """Test invalid captcha (score too low, but continue)""" 148 self.stage.error_on_invalid_score = False 149 self.stage.save() 150 mock.post( 151 "https://www.recaptcha.net/recaptcha/api/siteverify", 152 json={ 153 "success": True, 154 "score": -3, 155 }, 156 ) 157 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 158 session = self.client.session 159 session[SESSION_KEY_PLAN] = plan 160 session.save() 161 response = self.client.post( 162 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 163 {"token": "PASSED"}, 164 ) 165 self.assertEqual(response.status_code, 200) 166 self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
Test invalid captcha (score too low, but continue)
def
test_urls(self):
168 def test_urls(self): 169 """Test URLs captcha""" 170 self.stage.js_url = "https://test.goauthentik.io/test.js" 171 self.stage.api_url = "https://test.goauthentik.io/test" 172 self.stage.save() 173 plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]) 174 session = self.client.session 175 session[SESSION_KEY_PLAN] = plan 176 session.save() 177 response = self.client.get( 178 reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), 179 ) 180 self.assertEqual(response.status_code, 200) 181 self.assertStageResponse( 182 response, 183 self.flow, 184 js_url="https://test.goauthentik.io/test.js", 185 )
Test URLs captcha