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 plan( self, request: HttpRequest, default_context: Optional[dict[str, Any]] = None ) -> FlowPlan: """Check each of the flows' policies, check policies for each stage with PolicyBinding and return ordered list""" with Hub.current.start_span( op="authentik.flow.planner.plan", description=self.flow.slug ) as span: span: Span span.set_data("flow", self.flow) span.set_data("request", request) self._logger.debug( "f(plan): starting planning process", ) # Bit of a workaround here, if there is a pending user set in the default context # we use that user for our cache key # to make sure they don't get the generic response if default_context and PLAN_CONTEXT_PENDING_USER in default_context: user = default_context[PLAN_CONTEXT_PENDING_USER] else: user = request.user # First off, check the flow's direct policy bindings # to make sure the user even has access to the flow engine = PolicyEngine(self.flow, user, request) if default_context: span.set_data("default_context", cleanse_dict(default_context)) engine.request.context = default_context engine.build() result = engine.result if not result.passing: exc = FlowNonApplicableException(",".join(result.messages)) exc.policy_result = result raise exc # User is passing so far, check if we have a cached plan cached_plan_key = cache_key(self.flow, user) cached_plan = cache.get(cached_plan_key, None) if cached_plan and self.use_cache: self._logger.debug( "f(plan): taking plan from cache", key=cached_plan_key, ) # Reset the context as this isn't factored into caching cached_plan.context = default_context or {} return cached_plan self._logger.debug( "f(plan): building plan", ) plan = self._build_plan(user, request, default_context) cache.set(cache_key(self.flow, user), plan, CACHE_TIMEOUT) if not plan.bindings and not self.allow_empty_flows: raise EmptyFlowException() return plan