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 ldap_sync_all(): """Sync all sources""" for source in LDAPSource.objects.filter(enabled=True): for sync_class in [ UserLDAPSynchronizer, GroupLDAPSynchronizer, MembershipLDAPSynchronizer, ]: ldap_sync.delay(source.pk, class_to_path(sync_class))
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """Solve the previously retrieved challenge and advanced to the next stage.""" self._logger.debug( "f(exec): Passing POST", view_class=class_to_path(self.current_stage_view.__class__), stage=self.current_stage, ) try: with Hub.current.start_span( op="authentik.flow.executor.stage", description=class_to_path( self.current_stage_view.__class__), ) as span: span.set_data("Method", "POST") span.set_data("authentik Stage", self.current_stage_view) span.set_data("authentik Flow", self.flow.slug) stage_response = self.current_stage_view.post( request, *args, **kwargs) return to_stage_response(request, stage_response) except Exception as exc: # pylint: disable=broad-except return self.handle_exception(exc)
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """Get the next pending challenge from the currently active flow.""" self._logger.debug( "f(exec): Passing GET", view_class=class_to_path(self.current_stage_view.__class__), stage=self.current_stage, ) try: with Hub.current.start_span( op="authentik.flow.executor.stage", description=class_to_path( self.current_stage_view.__class__), ) as span: span.set_data("Method", "GET") span.set_data("authentik Stage", self.current_stage_view) span.set_data("authentik Flow", self.flow.slug) stage_response = self.current_stage_view.get( request, *args, **kwargs) return to_stage_response(request, stage_response) except Exception as exc: # pylint: disable=broad-except return self.handle_exception(exc)
def post_save_update(sender, instance: Model, **_): """If an Outpost is saved, Ensure that token is created/updated If an OutpostModel, or a model that is somehow connected to an OutpostModel is saved, we send a message down the relevant OutpostModels WS connection to trigger an update""" if instance.__module__ == "django.db.migrations.recorder": return if instance.__module__ == "__fake__": return if not isinstance(instance, UPDATE_TRIGGERING_MODELS): return outpost_post_save.delay(class_to_path(instance.__class__), instance.pk)
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """Solve the previously retrieved challenge and advanced to the next stage.""" self._logger.debug( "f(exec): Passing POST", view_class=class_to_path(self.current_stage_view.__class__), stage=self.current_stage, ) try: stage_response = self.current_stage_view.post( request, *args, **kwargs) return to_stage_response(request, stage_response) except Exception as exc: # pylint: disable=broad-except self._logger.exception(exc) return to_stage_response(request, FlowErrorResponse(request, exc))
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """Get the next pending challenge from the currently active flow.""" self._logger.debug( "f(exec): Passing GET", view_class=class_to_path(self.current_stage_view.__class__), stage=self.current_stage, ) try: stage_response = self.current_stage_view.get( request, *args, **kwargs) return to_stage_response(request, stage_response) except Exception as exc: # pylint: disable=broad-except self._logger.exception(exc) return to_stage_response(request, FlowErrorResponse(request, exc))
def sync_ldap_source_on_save(sender, instance: LDAPSource, **_): """Ensure that source is synced on save (if enabled)""" if not instance.enabled: return # Don't sync sources when they don't have any property mappings. This will only happen if: # - the user forgets to set them or # - the source is newly created, this is the first save event # and the mappings are created with an m2m event if not instance.property_mappings.exists() or not instance.property_mappings_group.exists(): return for sync_class in [ UserLDAPSynchronizer, GroupLDAPSynchronizer, MembershipLDAPSynchronizer, ]: ldap_sync.delay(instance.pk, class_to_path(sync_class))
async def __call__(self, scope, receive, send): transaction: Optional[Transaction] = Hub.current.scope.transaction class_path = class_to_path(self.inner.consumer_class) if transaction: transaction.name = class_path return await self.inner(scope, receive, send)
def get(self, request: HttpRequest) -> HttpResponse: """Save data in the current flow to the currently pending user. If no user is pending, a new user is created.""" if PLAN_CONTEXT_PROMPT not in self.executor.plan.context: message = _("No Pending data.") messages.error(request, message) LOGGER.debug(message) return self.executor.stage_invalid() data = self.executor.plan.context[PLAN_CONTEXT_PROMPT] user_created = False if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context: self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = User() self.executor.plan.context[ PLAN_CONTEXT_AUTHENTICATION_BACKEND ] = class_to_path(ModelBackend) LOGGER.debug( "Created new user", flow_slug=self.executor.flow.slug, ) user_created = True user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] # Before we change anything, check if the user is the same as in the request # and we're updating a password. In that case we need to update the session hash # Also check that we're not currently impersonating, so we don't update the session should_update_seesion = False if ( any("password" in x for x in data.keys()) and self.request.user.pk == user.pk and SESSION_IMPERSONATE_USER not in self.request.session ): should_update_seesion = True for key, value in data.items(): setter_name = f"set_{key}" # Check if user has a setter for this key, like set_password if hasattr(user, setter_name): setter = getattr(user, setter_name) if callable(setter): setter(value) # User has this key already elif hasattr(user, key): setattr(user, key, value) # Otherwise we just save it as custom attribute, but only if the value is prefixed with # `attribute_`, to prevent accidentally saving values else: if not key.startswith("attribute_"): LOGGER.debug("discarding key", key=key) continue user.attributes[key.replace("attribute_", "", 1)] = value # Extra check to prevent flows from saving a user with a blank username if user.username == "": LOGGER.warning("Aborting write to empty username", user=user) return self.executor.stage_invalid() # Check if we're writing from a source, and save the source to the attributes if PLAN_CONTEXT_SOURCES_CONNECTION in self.executor.plan.context: if USER_ATTRIBUTE_SOURCES not in user.attributes or not isinstance( user.attributes.get(USER_ATTRIBUTE_SOURCES), list ): user.attributes[USER_ATTRIBUTE_SOURCES] = [] connection: UserSourceConnection = self.executor.plan.context[ PLAN_CONTEXT_SOURCES_CONNECTION ] user.attributes[USER_ATTRIBUTE_SOURCES].append(connection.source.name) user.save() user_write.send( sender=self, request=request, user=user, data=data, created=user_created ) # Check if the password has been updated, and update the session auth hash if should_update_seesion: update_session_auth_hash(self.request, user) LOGGER.debug("Updated session hash", user=user) LOGGER.debug( "Updated existing user", user=user, flow_slug=self.executor.flow.slug, ) return self.executor.stage_ok()
def m2m_changed_update(sender, instance: Model, action: str, **_): """Update outpost on m2m change, when providers are added or removed""" if action in ["post_add", "post_remove", "post_clear"]: outpost_post_save.delay(class_to_path(instance.__class__), instance.pk)