def get(self, request: HttpRequest, source_slug: str) -> HttpResponse: """Replies with an XHTML SSO Request.""" source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug) if not source.enabled: raise Http404 relay_state = request.GET.get("next", "") auth_n_req = RequestProcessor(source, request, relay_state) # If the source is configured for Redirect bindings, we can just redirect there if source.binding_type == SAMLBindingTypes.REDIRECT: # Parse the initial SSO URL sso_url = urlparse(source.sso_url) # Parse the querystring into a dict... url_kwargs = dict(parse_qsl(sso_url.query)) # ... and update it with the SAML args url_kwargs.update(auth_n_req.build_auth_n_detached()) # Encode it back into a string res = ParseResult( scheme=sso_url.scheme, netloc=sso_url.netloc, path=sso_url.path, params=sso_url.params, query=urlencode(url_kwargs), fragment=sso_url.fragment, ) # and merge it back into a URL final_url = urlunparse(res) return redirect(final_url) # As POST Binding we show a form try: saml_request = nice64(auth_n_req.build_auth_n()) except InternalError as exc: LOGGER.warning(str(exc)) return bad_request_message(request, str(exc)) injected_stages = [] plan_kwargs = { PLAN_CONTEXT_TITLE: _("Redirecting to %(app)s..." % {"app": source.name}), PLAN_CONTEXT_CONSENT_TITLE: _("Redirecting to %(app)s..." % {"app": source.name}), PLAN_CONTEXT_ATTRS: { "SAMLRequest": saml_request, "RelayState": relay_state, }, PLAN_CONTEXT_URL: source.sso_url, } # For just POST we add a consent stage, # otherwise we default to POST_AUTO, with direct redirect if source.binding_type == SAMLBindingTypes.POST: injected_stages.append(in_memory_stage(ConsentStageView)) plan_kwargs[ PLAN_CONTEXT_CONSENT_HEADER] = f"Continue to {source.name}" injected_stages.append(in_memory_stage(AutosubmitStageView)) return self.handle_login_flow( source, *injected_stages, **plan_kwargs, )
def get_stages_to_append(self, flow: Flow) -> list[Stage]: """Hook to override stages which are appended to the flow""" if flow.slug == self.source.enrollment_flow.slug: return [ in_memory_stage(PostUserEnrollmentStage), ] return []
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 handle_enroll( self, source: OAuthSource, access: UserOAuthSourceConnection, info: dict[str, Any], ) -> HttpResponse: """User was not authenticated and previous request was not authenticated.""" messages.success( self.request, _("Successfully authenticated with %(source)s!" % {"source": self.source.name}), ) # We run the Flow planner here so we can pass the Pending user in the context if not source.enrollment_flow: LOGGER.warning("source has no enrollment flow", source=source) return HttpResponseBadRequest() return self.handle_login_flow( source.enrollment_flow, in_memory_stage(PostUserEnrollmentStage), **{ PLAN_CONTEXT_PROMPT: delete_none_keys( self.get_user_enroll_context(source, access, info)), PLAN_CONTEXT_SOURCES_OAUTH_ACCESS: access, }, )
def get(self, request: HttpRequest) -> HttpResponse: """Apply data to the current flow based on a URL""" stage: InvitationStage = self.executor.current_stage token = self.get_token() if not token: # No Invitation was given, raise error or continue if stage.continue_flow_without_invitation: return self.executor.stage_ok() return self.executor.stage_invalid() invite: Invitation = Invitation.objects.filter(pk=token).first() if not invite: LOGGER.debug("invalid invitation", token=token) if stage.continue_flow_without_invitation: return self.executor.stage_ok() return self.executor.stage_invalid() self.executor.plan.context[INVITATION_IN_EFFECT] = True self.executor.plan.context[INVITATION] = invite context = {} always_merger.merge( context, self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {})) always_merger.merge(context, invite.fixed_data) self.executor.plan.context[PLAN_CONTEXT_PROMPT] = context invitation_used.send(sender=self, request=request, invitation=invite) if invite.single_use: self.executor.plan.append_stage( in_memory_stage(InvitationFinalStageView)) return self.executor.stage_ok()
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 get(self, request: HttpRequest, source_slug: str) -> HttpResponse: """Replies with an XHTML SSO Request.""" source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug) if not source.enabled: raise Http404 relay_state = request.GET.get("next", "") auth_n_req = RequestProcessor(source, request, relay_state) # If the source is configured for Redirect bindings, we can just redirect there if source.binding_type == SAMLBindingTypes.REDIRECT: url_args = urlencode(auth_n_req.build_auth_n_detached()) return redirect(f"{source.sso_url}?{url_args}") # As POST Binding we show a form saml_request = nice64(auth_n_req.build_auth_n()) injected_stages = [] plan_kwargs = { PLAN_CONTEXT_TITLE: _("Redirecting to %(app)s..." % {"app": source.name}), PLAN_CONTEXT_CONSENT_TITLE: _( "Redirecting to %(app)s..." % {"app": source.name} ), PLAN_CONTEXT_ATTRS: { "SAMLRequest": saml_request, "RelayState": relay_state, }, PLAN_CONTEXT_URL: source.sso_url, } # For just POST we add a consent stage, # otherwise we default to POST_AUTO, with direct redirect if source.binding_type == SAMLBindingTypes.POST: injected_stages.append(in_memory_stage(ConsentStageView)) plan_kwargs[PLAN_CONTEXT_CONSENT_HEADER] = f"Continue to {source.name}" injected_stages.append(in_memory_stage(AutosubmitStageView)) return self.handle_login_flow( source, *injected_stages, **plan_kwargs, )