def stage_ok(self) -> HttpResponse: """Callback called by stages upon successful completion. Persists updated plan and context to session.""" self._logger.debug( "f(exec): Stage ok", stage_class=class_to_path(self.current_stage_view.__class__), ) self.request.session.get(SESSION_KEY_HISTORY, []).append(deepcopy(self.plan)) self.plan.pop() self.request.session[SESSION_KEY_PLAN] = self.plan if self.plan.bindings: self._logger.debug( "f(exec): Continuing with next stage", remaining=len(self.plan.bindings), ) kwargs = self.kwargs kwargs.update({"flow_slug": self.flow.slug}) return redirect_with_qs("authentik_api:flow-executor", self.request.GET, **kwargs) # User passed all stages self._logger.debug( "f(exec): User passed all stages", context=cleanse_dict(self.plan.context), ) return self._flow_done()
def dispatch(self, request: HttpRequest) -> HttpResponse: tenant: Tenant = request.tenant flow = None # First, attempt to get default flow from tenant if self.designation == FlowDesignation.AUTHENTICATION: flow = tenant.flow_authentication if self.designation == FlowDesignation.INVALIDATION: flow = tenant.flow_invalidation # If no flow was set, get the first based on slug and policy if not flow: flow = Flow.with_policy(request, designation=self.designation) # If we still don't have a flow, 404 if not flow: raise Http404 # If user already has a pending plan, clear it so we don't have to later. if SESSION_KEY_PLAN in self.request.session: plan: FlowPlan = self.request.session[SESSION_KEY_PLAN] if plan.flow_pk != flow.pk.hex: LOGGER.warning( "f(def): Found existing plan for other flow, deleting plan", flow_slug=flow.slug, ) del self.request.session[SESSION_KEY_PLAN] return redirect_with_qs("authentik_core:if-flow", request.GET, flow_slug=flow.slug)
def handle_login_flow( self, source: SAMLSource, *stages_to_append, **kwargs ) -> HttpResponse: """Prepare Authentication Plan, redirect user FlowExecutor""" # Ensure redirect is carried through when user was trying to # authorize application final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get( NEXT_ARG_NAME, "authentik_core:if-admin" ) kwargs.update( { PLAN_CONTEXT_SSO: True, PLAN_CONTEXT_SOURCE: source, PLAN_CONTEXT_REDIRECT: final_redirect, } ) # We run the Flow planner here so we can pass the Pending user in the context planner = FlowPlanner(source.pre_authentication_flow) planner.allow_empty_flows = True plan = planner.plan(self.request, kwargs) for stage in stages_to_append: plan.append(stage) self.request.session[SESSION_KEY_PLAN] = plan return redirect_with_qs( "authentik_core:if-flow", self.request.GET, flow_slug=source.pre_authentication_flow.slug, )
def _handle_login_flow(self, flow: Flow, **kwargs) -> HttpResponse: """Prepare Authentication Plan, redirect user FlowExecutor""" # Ensure redirect is carried through when user was trying to # authorize application final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get( NEXT_ARG_NAME, "authentik_core:if-admin") kwargs.update({ # Since we authenticate the user by their token, they have no backend set PLAN_CONTEXT_AUTHENTICATION_BACKEND: "django.contrib.auth.backends.ModelBackend", PLAN_CONTEXT_SSO: True, PLAN_CONTEXT_SOURCE: self.source, PLAN_CONTEXT_REDIRECT: final_redirect, }) if not flow: return HttpResponseBadRequest() # We run the Flow planner here so we can pass the Pending user in the context planner = FlowPlanner(flow) plan = planner.plan(self.request, kwargs) for stage in self.get_stages_to_append(flow): plan.append(stage) self.request.session[SESSION_KEY_PLAN] = plan return redirect_with_qs( "authentik_core:if-flow", self.request.GET, flow_slug=flow.slug, )
def get(self, request: HttpRequest, application_slug: str) -> HttpResponse: """Verify the SAML Request, and if valid initiate the FlowPlanner for the application""" # Call the method handler, which checks the SAML # Request and returns a HTTP Response on error method_response = self.check_saml_request() if method_response: return method_response # Regardless, we start the planner and return to it planner = FlowPlanner(self.provider.authorization_flow) planner.allow_empty_flows = True plan = planner.plan( request, { PLAN_CONTEXT_SSO: True, PLAN_CONTEXT_APPLICATION: self.application, PLAN_CONTEXT_CONSENT_HEADER: _("You're about to sign into %(application)s.") % { "application": self.application.name }, PLAN_CONTEXT_CONSENT_PERMISSIONS: [], }, ) plan.append(in_memory_stage(SAMLFlowFinalView)) request.session[SESSION_KEY_PLAN] = plan return redirect_with_qs( "authentik_core:if-flow", request.GET, flow_slug=self.provider.authorization_flow.slug, )
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """Start FlowPLanner, return to flow executor shell""" # After we've checked permissions, and the user has access, check if we need # to re-authenticate the user if self.params.max_age: current_age: timedelta = ( timezone.now() - Event.objects.filter( action=EventAction.LOGIN, user=get_user(self.request.user) ) .latest("created") .created ) if current_age.total_seconds() > self.params.max_age: return self.handle_no_permission() # If prompt=login, we need to re-authenticate the user regardless if ( PROMPT_LOGIN in self.params.prompt and SESSION_NEEDS_LOGIN not in self.request.session ): self.request.session[SESSION_NEEDS_LOGIN] = True return self.handle_no_permission() # Regardless, we start the planner and return to it planner = FlowPlanner(self.provider.authorization_flow) # planner.use_cache = False planner.allow_empty_flows = True scope_descriptions = UserInfoView().get_scope_descriptions(self.params.scope) plan: FlowPlan = planner.plan( self.request, { PLAN_CONTEXT_SSO: True, PLAN_CONTEXT_APPLICATION: self.application, # OAuth2 related params PLAN_CONTEXT_PARAMS: self.params, # Consent related params PLAN_CONTEXT_CONSENT_HEADER: _( "You're about to sign into %(application)s." ) % {"application": self.application.name}, PLAN_CONTEXT_CONSENT_PERMISSIONS: scope_descriptions, }, ) # OpenID clients can specify a `prompt` parameter, and if its set to consent we # need to inject a consent stage if PROMPT_CONSNET in self.params.prompt: if not any(isinstance(x, ConsentStageView) for x in plan.stages): # Plan does not have any consent stage, so we add an in-memory one stage = ConsentStage( name="OAuth2 Provider In-memory consent stage", mode=ConsentMode.ALWAYS_REQUIRE, ) plan.append(stage) plan.append(in_memory_stage(OAuthFulfillmentStage)) self.request.session[SESSION_KEY_PLAN] = plan return redirect_with_qs( "authentik_core:if-flow", self.request.GET, flow_slug=self.provider.authorization_flow.slug, )
def _flow_response(self, request: HttpRequest, flow: Flow, **kwargs) -> HttpResponse: kwargs[PLAN_CONTEXT_SSO] = True kwargs[PLAN_CONTEXT_SOURCE] = self._source request.session[SESSION_KEY_PLAN] = FlowPlanner(flow).plan( request, kwargs) return redirect_with_qs( "authentik_core:if-flow", request.GET, flow_slug=flow.slug, )
def restart_flow(self, keep_context=False) -> HttpResponse: """Restart the currently active flow, optionally keeping the current context""" planner = FlowPlanner(self.flow) default_context = None if keep_context: default_context = self.plan.context plan = planner.plan(self.request, default_context) self.request.session[SESSION_KEY_PLAN] = plan kwargs = self.kwargs kwargs.update({"flow_slug": self.flow.slug}) return redirect_with_qs("authentik_api:flow-executor", self.request.GET, **kwargs)
def _flow_done(self) -> HttpResponse: """User Successfully passed all stages""" # Since this is wrapped by the ExecutorShell, the next argument is saved in the session # extract the next param before cancel as that cleans it next_param = None if self.plan: next_param = self.plan.context.get(PLAN_CONTEXT_REDIRECT) if not next_param: next_param = self.request.session.get(SESSION_KEY_GET, {}).get( NEXT_ARG_NAME, "authentik_core:root-redirect") self.cancel() return to_stage_response(self.request, redirect_with_qs(next_param))
def _flow_done(self) -> HttpResponse: """User Successfully passed all stages""" # Since this is wrapped by the ExecutorShell, the next argument is saved in the session # extract the next param before cancel as that cleans it if self.plan and PLAN_CONTEXT_REDIRECT in self.plan.context: # The context `redirect` variable can only be set by # an expression policy or authentik itself, so we don't # check if its an absolute URL or a relative one self.cancel() return redirect(self.plan.context.get(PLAN_CONTEXT_REDIRECT)) next_param = self.request.session.get(SESSION_KEY_GET, {}).get( NEXT_ARG_NAME, "authentik_core:root-redirect") self.cancel() return to_stage_response(self.request, redirect_with_qs(next_param))
def dispatch(self, request: HttpRequest) -> HttpResponse: flow = Flow.with_policy(request, designation=self.designation) if not flow: raise Http404 # If user already has a pending plan, clear it so we don't have to later. if SESSION_KEY_PLAN in self.request.session: plan: FlowPlan = self.request.session[SESSION_KEY_PLAN] if plan.flow_pk != flow.pk.hex: LOGGER.warning( "f(def): Found existing plan for other flow, deleteing plan", flow_slug=flow.slug, ) del self.request.session[SESSION_KEY_PLAN] return redirect_with_qs("authentik_core:if-flow", request.GET, flow_slug=flow.slug)
def get(self, request: HttpRequest, stage_uuid: str) -> HttpResponse: """Initiate planner for selected change flow and redirect to flow executor, or raise Http404 if no configure_flow has been set.""" try: stage: Stage = Stage.objects.get_subclass(pk=stage_uuid) except Stage.DoesNotExist as exc: raise Http404 from exc if not isinstance(stage, ConfigurableStage): LOGGER.debug("Stage does not inherit ConfigurableStage", stage=stage) raise Http404 if not stage.configure_flow: LOGGER.debug("Stage has no configure_flow set", stage=stage) raise Http404 plan = FlowPlanner(stage.configure_flow).plan( request, {PLAN_CONTEXT_PENDING_USER: request.user}) request.session[SESSION_KEY_PLAN] = plan return redirect_with_qs( "authentik_core:if-flow", self.request.GET, flow_slug=stage.configure_flow.slug, )