authentik.enterprise.endpoints.connectors.agent.views.auth_interactive
1from hashlib import sha256 2from hmac import compare_digest 3 4from django.http import Http404, HttpRequest, HttpResponse, HttpResponseBadRequest, QueryDict 5 6from authentik.common.oauth.constants import QS_LOGIN_HINT 7from authentik.endpoints.connectors.agent.auth import ( 8 agent_auth_issue_token, 9 check_device_policies, 10) 11from authentik.endpoints.connectors.agent.models import AgentConnector, DeviceAuthenticationToken 12from authentik.endpoints.connectors.agent.stage import PLAN_CONTEXT_DEVICE_AUTH_TOKEN 13from authentik.endpoints.models import Device 14from authentik.enterprise.policy import EnterprisePolicyAccessView 15from authentik.flows.exceptions import FlowNonApplicableException 16from authentik.flows.models import in_memory_stage 17from authentik.flows.planner import PLAN_CONTEXT_DEVICE, FlowPlanner 18from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageView 19from authentik.providers.oauth2.utils import HttpResponseRedirectScheme 20 21QS_AGENT_IA_TOKEN = "ak-auth-ia-token" # nosec 22 23 24class AgentInteractiveAuth(EnterprisePolicyAccessView): 25 """Agent device authentication""" 26 27 auth_token: DeviceAuthenticationToken 28 device: Device 29 connector: AgentConnector 30 31 def resolve_provider_application(self): 32 auth_token = ( 33 DeviceAuthenticationToken.objects.filter(identifier=self.kwargs["token_uuid"]) 34 .prefetch_related() 35 .first() 36 ) 37 if not auth_token: 38 raise Http404 39 self.auth_token = auth_token 40 self.device = auth_token.device 41 self.connector = auth_token.connector.agentconnector 42 43 def user_has_access(self, user=None, pbm=None): 44 enterprise_result = self.check_license() 45 if not enterprise_result.passing: 46 return enterprise_result 47 return check_device_policies(self.device, user or self.request.user, self.request) 48 49 def modify_flow_context(self, flow, context): 50 return { 51 PLAN_CONTEXT_DEVICE: self.device, 52 **context, 53 } 54 55 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: 56 device_token_hash = request.headers.get("X-Authentik-Platform-Auth-DTH") 57 if not device_token_hash: 58 return HttpResponseBadRequest("Invalid device token") 59 if not compare_digest( 60 device_token_hash, sha256(self.auth_token.device_token.key.encode()).hexdigest() 61 ): 62 return HttpResponseBadRequest("Invalid device token") 63 if not self.connector.authorization_flow: 64 return HttpResponseBadRequest("No authorization flow configured") 65 66 planner = FlowPlanner(self.connector.authorization_flow) 67 planner.allow_empty_flows = True 68 context = { 69 PLAN_CONTEXT_DEVICE: self.device, 70 PLAN_CONTEXT_DEVICE_AUTH_TOKEN: self.auth_token, 71 } 72 if QS_LOGIN_HINT in request.GET: 73 context[PLAN_CONTEXT_PENDING_USER_IDENTIFIER] = request.GET[QS_LOGIN_HINT] 74 try: 75 plan = planner.plan(self.request, context) 76 except FlowNonApplicableException: 77 return self.handle_no_permission_authenticated() 78 plan.append_stage(in_memory_stage(AgentAuthFulfillmentStage)) 79 80 return plan.to_redirect( 81 self.request, 82 self.connector.authorization_flow, 83 allowed_silent_types=[AgentAuthFulfillmentStage], 84 ) 85 86 87class AgentAuthFulfillmentStage(StageView): 88 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: 89 device: Device = self.executor.plan.context.pop(PLAN_CONTEXT_DEVICE) 90 auth_token: DeviceAuthenticationToken = self.executor.plan.context.pop( 91 PLAN_CONTEXT_DEVICE_AUTH_TOKEN 92 ) 93 94 token, exp = agent_auth_issue_token( 95 device, 96 auth_token.connector.agentconnector, 97 request.user, 98 jti=str(auth_token.identifier), 99 ) 100 if not token or not exp: 101 return self.executor.stage_invalid("Failed to generate token") 102 auth_token.user = request.user 103 auth_token.token = token 104 auth_token.expires = exp 105 auth_token.expiring = True 106 auth_token.save() 107 qd = QueryDict(mutable=True) 108 qd[QS_AGENT_IA_TOKEN] = token 109 return HttpResponseRedirectScheme( 110 "goauthentik.io://platform/finished?" + qd.urlencode(), 111 allowed_schemes=["goauthentik.io"], 112 )
QS_AGENT_IA_TOKEN =
'ak-auth-ia-token'
25class AgentInteractiveAuth(EnterprisePolicyAccessView): 26 """Agent device authentication""" 27 28 auth_token: DeviceAuthenticationToken 29 device: Device 30 connector: AgentConnector 31 32 def resolve_provider_application(self): 33 auth_token = ( 34 DeviceAuthenticationToken.objects.filter(identifier=self.kwargs["token_uuid"]) 35 .prefetch_related() 36 .first() 37 ) 38 if not auth_token: 39 raise Http404 40 self.auth_token = auth_token 41 self.device = auth_token.device 42 self.connector = auth_token.connector.agentconnector 43 44 def user_has_access(self, user=None, pbm=None): 45 enterprise_result = self.check_license() 46 if not enterprise_result.passing: 47 return enterprise_result 48 return check_device_policies(self.device, user or self.request.user, self.request) 49 50 def modify_flow_context(self, flow, context): 51 return { 52 PLAN_CONTEXT_DEVICE: self.device, 53 **context, 54 } 55 56 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: 57 device_token_hash = request.headers.get("X-Authentik-Platform-Auth-DTH") 58 if not device_token_hash: 59 return HttpResponseBadRequest("Invalid device token") 60 if not compare_digest( 61 device_token_hash, sha256(self.auth_token.device_token.key.encode()).hexdigest() 62 ): 63 return HttpResponseBadRequest("Invalid device token") 64 if not self.connector.authorization_flow: 65 return HttpResponseBadRequest("No authorization flow configured") 66 67 planner = FlowPlanner(self.connector.authorization_flow) 68 planner.allow_empty_flows = True 69 context = { 70 PLAN_CONTEXT_DEVICE: self.device, 71 PLAN_CONTEXT_DEVICE_AUTH_TOKEN: self.auth_token, 72 } 73 if QS_LOGIN_HINT in request.GET: 74 context[PLAN_CONTEXT_PENDING_USER_IDENTIFIER] = request.GET[QS_LOGIN_HINT] 75 try: 76 plan = planner.plan(self.request, context) 77 except FlowNonApplicableException: 78 return self.handle_no_permission_authenticated() 79 plan.append_stage(in_memory_stage(AgentAuthFulfillmentStage)) 80 81 return plan.to_redirect( 82 self.request, 83 self.connector.authorization_flow, 84 allowed_silent_types=[AgentAuthFulfillmentStage], 85 )
Agent device authentication
def
resolve_provider_application(self):
32 def resolve_provider_application(self): 33 auth_token = ( 34 DeviceAuthenticationToken.objects.filter(identifier=self.kwargs["token_uuid"]) 35 .prefetch_related() 36 .first() 37 ) 38 if not auth_token: 39 raise Http404 40 self.auth_token = auth_token 41 self.device = auth_token.device 42 self.connector = auth_token.connector.agentconnector
Resolve self.provider and self.application. *.DoesNotExist Exceptions cause a normal AccessDenied view to be shown. An Http404 exception is not caught, and will return directly
def
user_has_access(self, user=None, pbm=None):
44 def user_has_access(self, user=None, pbm=None): 45 enterprise_result = self.check_license() 46 if not enterprise_result.passing: 47 return enterprise_result 48 return check_device_policies(self.device, user or self.request.user, self.request)
Check if user has access to application.
def
modify_flow_context(self, flow, context):
50 def modify_flow_context(self, flow, context): 51 return { 52 PLAN_CONTEXT_DEVICE: self.device, 53 **context, 54 }
optionally modify the flow context which is used for the authentication flow
def
get( self, request: django.http.request.HttpRequest, *args, **kwargs) -> django.http.response.HttpResponse:
56 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: 57 device_token_hash = request.headers.get("X-Authentik-Platform-Auth-DTH") 58 if not device_token_hash: 59 return HttpResponseBadRequest("Invalid device token") 60 if not compare_digest( 61 device_token_hash, sha256(self.auth_token.device_token.key.encode()).hexdigest() 62 ): 63 return HttpResponseBadRequest("Invalid device token") 64 if not self.connector.authorization_flow: 65 return HttpResponseBadRequest("No authorization flow configured") 66 67 planner = FlowPlanner(self.connector.authorization_flow) 68 planner.allow_empty_flows = True 69 context = { 70 PLAN_CONTEXT_DEVICE: self.device, 71 PLAN_CONTEXT_DEVICE_AUTH_TOKEN: self.auth_token, 72 } 73 if QS_LOGIN_HINT in request.GET: 74 context[PLAN_CONTEXT_PENDING_USER_IDENTIFIER] = request.GET[QS_LOGIN_HINT] 75 try: 76 plan = planner.plan(self.request, context) 77 except FlowNonApplicableException: 78 return self.handle_no_permission_authenticated() 79 plan.append_stage(in_memory_stage(AgentAuthFulfillmentStage)) 80 81 return plan.to_redirect( 82 self.request, 83 self.connector.authorization_flow, 84 allowed_silent_types=[AgentAuthFulfillmentStage], 85 )
88class AgentAuthFulfillmentStage(StageView): 89 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: 90 device: Device = self.executor.plan.context.pop(PLAN_CONTEXT_DEVICE) 91 auth_token: DeviceAuthenticationToken = self.executor.plan.context.pop( 92 PLAN_CONTEXT_DEVICE_AUTH_TOKEN 93 ) 94 95 token, exp = agent_auth_issue_token( 96 device, 97 auth_token.connector.agentconnector, 98 request.user, 99 jti=str(auth_token.identifier), 100 ) 101 if not token or not exp: 102 return self.executor.stage_invalid("Failed to generate token") 103 auth_token.user = request.user 104 auth_token.token = token 105 auth_token.expires = exp 106 auth_token.expiring = True 107 auth_token.save() 108 qd = QueryDict(mutable=True) 109 qd[QS_AGENT_IA_TOKEN] = token 110 return HttpResponseRedirectScheme( 111 "goauthentik.io://platform/finished?" + qd.urlencode(), 112 allowed_schemes=["goauthentik.io"], 113 )
Abstract Stage
def
get( self, request: django.http.request.HttpRequest, *args, **kwargs) -> django.http.response.HttpResponse:
89 def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: 90 device: Device = self.executor.plan.context.pop(PLAN_CONTEXT_DEVICE) 91 auth_token: DeviceAuthenticationToken = self.executor.plan.context.pop( 92 PLAN_CONTEXT_DEVICE_AUTH_TOKEN 93 ) 94 95 token, exp = agent_auth_issue_token( 96 device, 97 auth_token.connector.agentconnector, 98 request.user, 99 jti=str(auth_token.identifier), 100 ) 101 if not token or not exp: 102 return self.executor.stage_invalid("Failed to generate token") 103 auth_token.user = request.user 104 auth_token.token = token 105 auth_token.expires = exp 106 auth_token.expiring = True 107 auth_token.save() 108 qd = QueryDict(mutable=True) 109 qd[QS_AGENT_IA_TOKEN] = token 110 return HttpResponseRedirectScheme( 111 "goauthentik.io://platform/finished?" + qd.urlencode(), 112 allowed_schemes=["goauthentik.io"], 113 )