authentik.endpoints.connectors.agent.tests.test_stage
1from hashlib import sha256 2from json import loads 3from unittest.mock import PropertyMock, patch 4 5from django.urls import reverse 6from jwt import encode 7 8from authentik.core.tests.utils import create_test_cert, create_test_flow 9from authentik.endpoints.connectors.agent.models import ( 10 AgentConnector, 11 AgentDeviceConnection, 12 DeviceAuthenticationToken, 13 DeviceToken, 14 EnrollmentToken, 15) 16from authentik.endpoints.connectors.agent.stage import ( 17 PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, 18 PLAN_CONTEXT_DEVICE_AUTH_TOKEN, 19) 20from authentik.endpoints.models import Device, EndpointStage, StageMode 21from authentik.flows.models import FlowStageBinding 22from authentik.flows.planner import PLAN_CONTEXT_DEVICE 23from authentik.flows.tests import FlowTestCase 24from authentik.lib.generators import generate_id 25 26 27class TestEndpointStage(FlowTestCase): 28 29 def setUp(self): 30 cert = create_test_cert() 31 self.connector = AgentConnector.objects.create(name=generate_id(), challenge_key=cert) 32 self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector) 33 self.device = Device.objects.create( 34 identifier=generate_id(), 35 ) 36 self.connection = AgentDeviceConnection.objects.create( 37 device=self.device, 38 connector=self.connector, 39 ) 40 self.device_token = DeviceToken.objects.create( 41 device=self.connection, 42 key=generate_id(), 43 ) 44 self.device_auth_token = DeviceAuthenticationToken.objects.create( 45 device=self.device, 46 device_token=self.device_token, 47 connector=self.connector, 48 ) 49 50 def test_endpoint_stage(self): 51 flow = create_test_flow() 52 stage = EndpointStage.objects.create(connector=self.connector) 53 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 54 55 res = self.client.get( 56 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 57 ) 58 self.assertEqual(res.status_code, 200) 59 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 60 61 challenge = loads(res.content.decode())["challenge"] 62 63 DeviceToken.objects.create( 64 device=self.connection, 65 key=generate_id(), 66 ) 67 68 response = encode( 69 { 70 "iss": self.device.identifier, 71 "atc": challenge, 72 "aud": "goauthentik.io/platform/endpoint", 73 }, 74 key=self.device_token.key, 75 algorithm="HS512", 76 ) 77 78 with self.assertFlowFinishes() as plan: 79 res = self.client.post( 80 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 81 data={"component": "ak-stage-endpoint-agent", "response": response}, 82 ) 83 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 84 plan = plan() 85 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 86 self.assertEqual(plan.context[PLAN_CONTEXT_DEVICE], self.device) 87 88 def test_endpoint_stage_optional(self): 89 flow = create_test_flow() 90 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.OPTIONAL) 91 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 92 93 res = self.client.get( 94 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 95 ) 96 self.assertEqual(res.status_code, 200) 97 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 98 99 challenge = loads(res.content.decode())["challenge"] 100 101 response = encode( 102 { 103 "iss": self.device.identifier, 104 "atc": challenge, 105 "aud": "goauthentik.io/platform/endpoint", 106 }, 107 key=self.device_token.key, 108 algorithm="HS512", 109 ) 110 111 with self.assertFlowFinishes() as plan: 112 res = self.client.post( 113 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 114 data={"component": "ak-stage-endpoint-agent", "response": response}, 115 ) 116 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 117 plan = plan() 118 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 119 self.assertEqual(plan.context[PLAN_CONTEXT_DEVICE], self.device) 120 121 def test_endpoint_stage_optional_none(self): 122 flow = create_test_flow() 123 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.OPTIONAL) 124 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 125 126 res = self.client.get( 127 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 128 ) 129 self.assertEqual(res.status_code, 200) 130 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 131 132 with self.assertFlowFinishes() as plan: 133 res = self.client.post( 134 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 135 data={"component": "ak-stage-endpoint-agent", "response": None}, 136 ) 137 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 138 plan = plan() 139 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 140 self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context) 141 142 def test_endpoint_stage_optional_invalid(self): 143 flow = create_test_flow() 144 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.OPTIONAL) 145 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 146 147 res = self.client.get( 148 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 149 ) 150 self.assertEqual(res.status_code, 200) 151 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 152 153 with self.assertFlowFinishes() as plan: 154 res = self.client.post( 155 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 156 data={"component": "ak-stage-endpoint-agent", "response": generate_id()}, 157 ) 158 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 159 plan = plan() 160 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 161 self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context) 162 163 def test_endpoint_stage_required_none(self): 164 flow = create_test_flow() 165 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.REQUIRED) 166 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 167 168 res = self.client.get( 169 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 170 ) 171 self.assertEqual(res.status_code, 200) 172 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 173 174 res = self.client.post( 175 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 176 data={"component": "ak-stage-endpoint-agent", "response": None}, 177 ) 178 self.assertStageResponse( 179 res, 180 flow=flow, 181 component="ak-stage-access-denied", 182 error_message="Invalid challenge response", 183 ) 184 185 def test_endpoint_stage_required_invalid(self): 186 flow = create_test_flow() 187 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.REQUIRED) 188 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 189 190 res = self.client.get( 191 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 192 ) 193 self.assertEqual(res.status_code, 200) 194 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 195 196 res = self.client.post( 197 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 198 data={"component": "ak-stage-endpoint-agent", "response": generate_id()}, 199 ) 200 self.assertStageResponse( 201 res, 202 flow=flow, 203 component="ak-stage-endpoint-agent", 204 response_errors={ 205 "response": [{"string": "Invalid challenge response", "code": "invalid"}] 206 }, 207 ) 208 209 def test_endpoint_stage_ia_dth(self): 210 """Test with DTH""" 211 flow = create_test_flow() 212 stage = EndpointStage.objects.create(connector=self.connector) 213 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 214 215 # Send an "invalid" request first, to populate the flow plan 216 res = self.client.get( 217 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 218 ) 219 plan = self.get_flow_plan() 220 plan.context[PLAN_CONTEXT_DEVICE_AUTH_TOKEN] = DeviceAuthenticationToken.objects.get( 221 pk=self.device_auth_token.pk 222 ) 223 self.set_flow_plan(plan) 224 225 with self.assertFlowFinishes() as plan: 226 res = self.client.get( 227 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 228 HTTP_X_AUTHENTIK_PLATFORM_AUTH_DTH=sha256( 229 self.device_token.key.encode() 230 ).hexdigest(), 231 ) 232 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 233 plan = plan() 234 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 235 self.assertEqual(plan.context[PLAN_CONTEXT_DEVICE], self.device) 236 237 def test_endpoint_stage_connector_no_stage_optional(self): 238 flow = create_test_flow() 239 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.OPTIONAL) 240 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 241 242 with patch( 243 "authentik.endpoints.connectors.agent.models.AgentConnector.stage", 244 PropertyMock(return_value=None), 245 ): 246 with self.assertFlowFinishes() as plan: 247 res = self.client.get( 248 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 249 ) 250 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 251 plan = plan() 252 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 253 self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context) 254 255 def test_endpoint_stage_connector_no_stage_required(self): 256 flow = create_test_flow() 257 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.REQUIRED) 258 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 259 260 with patch( 261 "authentik.endpoints.connectors.agent.models.AgentConnector.stage", 262 PropertyMock(return_value=None), 263 ): 264 with self.assertFlowFinishes() as plan: 265 res = self.client.get( 266 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 267 ) 268 self.assertStageResponse( 269 res, 270 component="ak-stage-access-denied", 271 error_message="Invalid stage configuration", 272 ) 273 plan = plan() 274 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 275 self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context)
28class TestEndpointStage(FlowTestCase): 29 30 def setUp(self): 31 cert = create_test_cert() 32 self.connector = AgentConnector.objects.create(name=generate_id(), challenge_key=cert) 33 self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector) 34 self.device = Device.objects.create( 35 identifier=generate_id(), 36 ) 37 self.connection = AgentDeviceConnection.objects.create( 38 device=self.device, 39 connector=self.connector, 40 ) 41 self.device_token = DeviceToken.objects.create( 42 device=self.connection, 43 key=generate_id(), 44 ) 45 self.device_auth_token = DeviceAuthenticationToken.objects.create( 46 device=self.device, 47 device_token=self.device_token, 48 connector=self.connector, 49 ) 50 51 def test_endpoint_stage(self): 52 flow = create_test_flow() 53 stage = EndpointStage.objects.create(connector=self.connector) 54 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 55 56 res = self.client.get( 57 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 58 ) 59 self.assertEqual(res.status_code, 200) 60 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 61 62 challenge = loads(res.content.decode())["challenge"] 63 64 DeviceToken.objects.create( 65 device=self.connection, 66 key=generate_id(), 67 ) 68 69 response = encode( 70 { 71 "iss": self.device.identifier, 72 "atc": challenge, 73 "aud": "goauthentik.io/platform/endpoint", 74 }, 75 key=self.device_token.key, 76 algorithm="HS512", 77 ) 78 79 with self.assertFlowFinishes() as plan: 80 res = self.client.post( 81 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 82 data={"component": "ak-stage-endpoint-agent", "response": response}, 83 ) 84 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 85 plan = plan() 86 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 87 self.assertEqual(plan.context[PLAN_CONTEXT_DEVICE], self.device) 88 89 def test_endpoint_stage_optional(self): 90 flow = create_test_flow() 91 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.OPTIONAL) 92 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 93 94 res = self.client.get( 95 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 96 ) 97 self.assertEqual(res.status_code, 200) 98 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 99 100 challenge = loads(res.content.decode())["challenge"] 101 102 response = encode( 103 { 104 "iss": self.device.identifier, 105 "atc": challenge, 106 "aud": "goauthentik.io/platform/endpoint", 107 }, 108 key=self.device_token.key, 109 algorithm="HS512", 110 ) 111 112 with self.assertFlowFinishes() as plan: 113 res = self.client.post( 114 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 115 data={"component": "ak-stage-endpoint-agent", "response": response}, 116 ) 117 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 118 plan = plan() 119 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 120 self.assertEqual(plan.context[PLAN_CONTEXT_DEVICE], self.device) 121 122 def test_endpoint_stage_optional_none(self): 123 flow = create_test_flow() 124 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.OPTIONAL) 125 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 126 127 res = self.client.get( 128 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 129 ) 130 self.assertEqual(res.status_code, 200) 131 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 132 133 with self.assertFlowFinishes() as plan: 134 res = self.client.post( 135 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 136 data={"component": "ak-stage-endpoint-agent", "response": None}, 137 ) 138 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 139 plan = plan() 140 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 141 self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context) 142 143 def test_endpoint_stage_optional_invalid(self): 144 flow = create_test_flow() 145 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.OPTIONAL) 146 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 147 148 res = self.client.get( 149 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 150 ) 151 self.assertEqual(res.status_code, 200) 152 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 153 154 with self.assertFlowFinishes() as plan: 155 res = self.client.post( 156 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 157 data={"component": "ak-stage-endpoint-agent", "response": generate_id()}, 158 ) 159 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 160 plan = plan() 161 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 162 self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context) 163 164 def test_endpoint_stage_required_none(self): 165 flow = create_test_flow() 166 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.REQUIRED) 167 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 168 169 res = self.client.get( 170 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 171 ) 172 self.assertEqual(res.status_code, 200) 173 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 174 175 res = self.client.post( 176 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 177 data={"component": "ak-stage-endpoint-agent", "response": None}, 178 ) 179 self.assertStageResponse( 180 res, 181 flow=flow, 182 component="ak-stage-access-denied", 183 error_message="Invalid challenge response", 184 ) 185 186 def test_endpoint_stage_required_invalid(self): 187 flow = create_test_flow() 188 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.REQUIRED) 189 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 190 191 res = self.client.get( 192 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 193 ) 194 self.assertEqual(res.status_code, 200) 195 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 196 197 res = self.client.post( 198 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 199 data={"component": "ak-stage-endpoint-agent", "response": generate_id()}, 200 ) 201 self.assertStageResponse( 202 res, 203 flow=flow, 204 component="ak-stage-endpoint-agent", 205 response_errors={ 206 "response": [{"string": "Invalid challenge response", "code": "invalid"}] 207 }, 208 ) 209 210 def test_endpoint_stage_ia_dth(self): 211 """Test with DTH""" 212 flow = create_test_flow() 213 stage = EndpointStage.objects.create(connector=self.connector) 214 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 215 216 # Send an "invalid" request first, to populate the flow plan 217 res = self.client.get( 218 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 219 ) 220 plan = self.get_flow_plan() 221 plan.context[PLAN_CONTEXT_DEVICE_AUTH_TOKEN] = DeviceAuthenticationToken.objects.get( 222 pk=self.device_auth_token.pk 223 ) 224 self.set_flow_plan(plan) 225 226 with self.assertFlowFinishes() as plan: 227 res = self.client.get( 228 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 229 HTTP_X_AUTHENTIK_PLATFORM_AUTH_DTH=sha256( 230 self.device_token.key.encode() 231 ).hexdigest(), 232 ) 233 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 234 plan = plan() 235 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 236 self.assertEqual(plan.context[PLAN_CONTEXT_DEVICE], self.device) 237 238 def test_endpoint_stage_connector_no_stage_optional(self): 239 flow = create_test_flow() 240 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.OPTIONAL) 241 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 242 243 with patch( 244 "authentik.endpoints.connectors.agent.models.AgentConnector.stage", 245 PropertyMock(return_value=None), 246 ): 247 with self.assertFlowFinishes() as plan: 248 res = self.client.get( 249 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 250 ) 251 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 252 plan = plan() 253 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 254 self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context) 255 256 def test_endpoint_stage_connector_no_stage_required(self): 257 flow = create_test_flow() 258 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.REQUIRED) 259 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 260 261 with patch( 262 "authentik.endpoints.connectors.agent.models.AgentConnector.stage", 263 PropertyMock(return_value=None), 264 ): 265 with self.assertFlowFinishes() as plan: 266 res = self.client.get( 267 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 268 ) 269 self.assertStageResponse( 270 res, 271 component="ak-stage-access-denied", 272 error_message="Invalid stage configuration", 273 ) 274 plan = plan() 275 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 276 self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context)
Helpers for testing flows and stages.
def
setUp(self):
30 def setUp(self): 31 cert = create_test_cert() 32 self.connector = AgentConnector.objects.create(name=generate_id(), challenge_key=cert) 33 self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector) 34 self.device = Device.objects.create( 35 identifier=generate_id(), 36 ) 37 self.connection = AgentDeviceConnection.objects.create( 38 device=self.device, 39 connector=self.connector, 40 ) 41 self.device_token = DeviceToken.objects.create( 42 device=self.connection, 43 key=generate_id(), 44 ) 45 self.device_auth_token = DeviceAuthenticationToken.objects.create( 46 device=self.device, 47 device_token=self.device_token, 48 connector=self.connector, 49 )
Hook method for setting up the test fixture before exercising it.
def
test_endpoint_stage(self):
51 def test_endpoint_stage(self): 52 flow = create_test_flow() 53 stage = EndpointStage.objects.create(connector=self.connector) 54 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 55 56 res = self.client.get( 57 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 58 ) 59 self.assertEqual(res.status_code, 200) 60 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 61 62 challenge = loads(res.content.decode())["challenge"] 63 64 DeviceToken.objects.create( 65 device=self.connection, 66 key=generate_id(), 67 ) 68 69 response = encode( 70 { 71 "iss": self.device.identifier, 72 "atc": challenge, 73 "aud": "goauthentik.io/platform/endpoint", 74 }, 75 key=self.device_token.key, 76 algorithm="HS512", 77 ) 78 79 with self.assertFlowFinishes() as plan: 80 res = self.client.post( 81 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 82 data={"component": "ak-stage-endpoint-agent", "response": response}, 83 ) 84 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 85 plan = plan() 86 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 87 self.assertEqual(plan.context[PLAN_CONTEXT_DEVICE], self.device)
def
test_endpoint_stage_optional(self):
89 def test_endpoint_stage_optional(self): 90 flow = create_test_flow() 91 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.OPTIONAL) 92 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 93 94 res = self.client.get( 95 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 96 ) 97 self.assertEqual(res.status_code, 200) 98 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 99 100 challenge = loads(res.content.decode())["challenge"] 101 102 response = encode( 103 { 104 "iss": self.device.identifier, 105 "atc": challenge, 106 "aud": "goauthentik.io/platform/endpoint", 107 }, 108 key=self.device_token.key, 109 algorithm="HS512", 110 ) 111 112 with self.assertFlowFinishes() as plan: 113 res = self.client.post( 114 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 115 data={"component": "ak-stage-endpoint-agent", "response": response}, 116 ) 117 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 118 plan = plan() 119 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 120 self.assertEqual(plan.context[PLAN_CONTEXT_DEVICE], self.device)
def
test_endpoint_stage_optional_none(self):
122 def test_endpoint_stage_optional_none(self): 123 flow = create_test_flow() 124 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.OPTIONAL) 125 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 126 127 res = self.client.get( 128 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 129 ) 130 self.assertEqual(res.status_code, 200) 131 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 132 133 with self.assertFlowFinishes() as plan: 134 res = self.client.post( 135 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 136 data={"component": "ak-stage-endpoint-agent", "response": None}, 137 ) 138 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 139 plan = plan() 140 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 141 self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context)
def
test_endpoint_stage_optional_invalid(self):
143 def test_endpoint_stage_optional_invalid(self): 144 flow = create_test_flow() 145 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.OPTIONAL) 146 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 147 148 res = self.client.get( 149 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 150 ) 151 self.assertEqual(res.status_code, 200) 152 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 153 154 with self.assertFlowFinishes() as plan: 155 res = self.client.post( 156 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 157 data={"component": "ak-stage-endpoint-agent", "response": generate_id()}, 158 ) 159 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 160 plan = plan() 161 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 162 self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context)
def
test_endpoint_stage_required_none(self):
164 def test_endpoint_stage_required_none(self): 165 flow = create_test_flow() 166 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.REQUIRED) 167 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 168 169 res = self.client.get( 170 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 171 ) 172 self.assertEqual(res.status_code, 200) 173 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 174 175 res = self.client.post( 176 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 177 data={"component": "ak-stage-endpoint-agent", "response": None}, 178 ) 179 self.assertStageResponse( 180 res, 181 flow=flow, 182 component="ak-stage-access-denied", 183 error_message="Invalid challenge response", 184 )
def
test_endpoint_stage_required_invalid(self):
186 def test_endpoint_stage_required_invalid(self): 187 flow = create_test_flow() 188 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.REQUIRED) 189 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 190 191 res = self.client.get( 192 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 193 ) 194 self.assertEqual(res.status_code, 200) 195 self.assertStageResponse(res, flow=flow, component="ak-stage-endpoint-agent") 196 197 res = self.client.post( 198 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 199 data={"component": "ak-stage-endpoint-agent", "response": generate_id()}, 200 ) 201 self.assertStageResponse( 202 res, 203 flow=flow, 204 component="ak-stage-endpoint-agent", 205 response_errors={ 206 "response": [{"string": "Invalid challenge response", "code": "invalid"}] 207 }, 208 )
def
test_endpoint_stage_ia_dth(self):
210 def test_endpoint_stage_ia_dth(self): 211 """Test with DTH""" 212 flow = create_test_flow() 213 stage = EndpointStage.objects.create(connector=self.connector) 214 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 215 216 # Send an "invalid" request first, to populate the flow plan 217 res = self.client.get( 218 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 219 ) 220 plan = self.get_flow_plan() 221 plan.context[PLAN_CONTEXT_DEVICE_AUTH_TOKEN] = DeviceAuthenticationToken.objects.get( 222 pk=self.device_auth_token.pk 223 ) 224 self.set_flow_plan(plan) 225 226 with self.assertFlowFinishes() as plan: 227 res = self.client.get( 228 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 229 HTTP_X_AUTHENTIK_PLATFORM_AUTH_DTH=sha256( 230 self.device_token.key.encode() 231 ).hexdigest(), 232 ) 233 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 234 plan = plan() 235 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 236 self.assertEqual(plan.context[PLAN_CONTEXT_DEVICE], self.device)
Test with DTH
def
test_endpoint_stage_connector_no_stage_optional(self):
238 def test_endpoint_stage_connector_no_stage_optional(self): 239 flow = create_test_flow() 240 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.OPTIONAL) 241 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 242 243 with patch( 244 "authentik.endpoints.connectors.agent.models.AgentConnector.stage", 245 PropertyMock(return_value=None), 246 ): 247 with self.assertFlowFinishes() as plan: 248 res = self.client.get( 249 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 250 ) 251 self.assertStageRedirects(res, reverse("authentik_core:root-redirect")) 252 plan = plan() 253 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 254 self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context)
def
test_endpoint_stage_connector_no_stage_required(self):
256 def test_endpoint_stage_connector_no_stage_required(self): 257 flow = create_test_flow() 258 stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.REQUIRED) 259 FlowStageBinding.objects.create(stage=stage, target=flow, order=0) 260 261 with patch( 262 "authentik.endpoints.connectors.agent.models.AgentConnector.stage", 263 PropertyMock(return_value=None), 264 ): 265 with self.assertFlowFinishes() as plan: 266 res = self.client.get( 267 reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), 268 ) 269 self.assertStageResponse( 270 res, 271 component="ak-stage-access-denied", 272 error_message="Invalid stage configuration", 273 ) 274 plan = plan() 275 self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context) 276 self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context)