def execute(self, request: Request, slug: str): """Execute flow for current user""" # Because we pre-plan the flow here, and not in the planner, we need to manually clear # the history of the inspector request.session[SESSION_KEY_HISTORY] = [] flow: Flow = self.get_object() planner = FlowPlanner(flow) planner.use_cache = False try: plan = planner.plan(self.request, {PLAN_CONTEXT_PENDING_USER: request.user}) self.request.session[SESSION_KEY_PLAN] = plan except FlowNonApplicableException as exc: return bad_request_message( request, _( "Flow not applicable to current user/request: %(messages)s" % {"messages": str(exc)} ), ) return Response( { "link": request._request.build_absolute_uri( reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}) ) } )
def pre_permission_check(self): """Check prompt parameter before checking permission/authentication, see https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3.1.2.6""" try: self.params = OAuthAuthorizationParams.from_request(self.request) except AuthorizeError as error: error.to_event(redirect_uri=error.redirect_uri).from_http(self.request) raise RequestValidationError(HttpResponseRedirect(error.create_uri())) except OAuth2Error as error: error.to_event().from_http(self.request) raise RequestValidationError( bad_request_message(self.request, error.description, title=error.error) ) except OAuth2Provider.DoesNotExist: raise Http404 if PROMPT_NONE in self.params.prompt and not self.request.user.is_authenticated: # When "prompt" is set to "none" but the user is not logged in, show an error message error = AuthorizeError( self.params.redirect_uri, "login_required", self.params.grant_type, self.params.state, ) error.to_event(redirect_uri=error.redirect_uri).from_http(self.request) raise RequestValidationError(HttpResponseRedirect(error.create_uri()))
def post(self, request: HttpRequest, source_slug: str) -> HttpResponse: """Handles a POSTed SSO Assertion and logs the user in.""" source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug) if not source.enabled: raise Http404 processor = ResponseProcessor(source) try: processor.parse(request) except MissingSAMLResponse as exc: return bad_request_message(request, str(exc)) except VerificationError as exc: return bad_request_message(request, str(exc)) try: return processor.prepare_flow(request) except UnsupportedNameIDFormat as exc: return bad_request_message(request, str(exc))
def check_saml_request(self) -> Optional[HttpRequest]: """Handle POST bindings""" if REQUEST_KEY_SAML_REQUEST not in self.request.POST: LOGGER.info("check_saml_request: SAML payload missing") return bad_request_message(self.request, "The SAML request payload is missing.") try: auth_n_request = AuthNRequestParser(self.provider).parse( self.request.POST[REQUEST_KEY_SAML_REQUEST], self.request.POST.get(REQUEST_KEY_RELAY_STATE), ) self.request.session[SESSION_KEY_AUTH_N_REQUEST] = auth_n_request except CannotHandleAssertion as exc: LOGGER.info(str(exc)) return bad_request_message(self.request, str(exc)) return None
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(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: application: Application = self.executor.plan.context[ PLAN_CONTEXT_APPLICATION] provider: SAMLProvider = get_object_or_404(SAMLProvider, pk=application.provider_id) if SESSION_KEY_AUTH_N_REQUEST not in self.request.session: return self.executor.stage_invalid() auth_n_request: AuthNRequest = self.request.session.pop( SESSION_KEY_AUTH_N_REQUEST) try: response = AssertionProcessor(provider, request, auth_n_request).build_response() except SAMLException as exc: Event.new( EventAction.CONFIGURATION_ERROR, message=f"Failed to process SAML assertion: {str(exc)}", provider=provider, ).from_http(self.request) return self.executor.stage_invalid() # Log Application Authorization Event.new( EventAction.AUTHORIZE_APPLICATION, authorized_application=application, flow=self.executor.plan.flow_pk, ).from_http(self.request) if provider.sp_binding == SAMLBindings.POST: form_attrs = { "ACSUrl": provider.acs_url, REQUEST_KEY_SAML_RESPONSE: nice64(response), } if auth_n_request.relay_state: form_attrs[ REQUEST_KEY_RELAY_STATE] = auth_n_request.relay_state return super().get( self.request, **{ "type": ChallengeTypes.NATIVE.value, "component": "ak-stage-autosubmit", "title": "Redirecting to %(app)s..." % { "app": application.name }, "url": provider.acs_url, "attrs": form_attrs, }, ) if provider.sp_binding == SAMLBindings.REDIRECT: url_args = { REQUEST_KEY_SAML_RESPONSE: deflate_and_base64_encode(response), } if auth_n_request.relay_state: url_args[REQUEST_KEY_RELAY_STATE] = auth_n_request.relay_state querystring = urlencode(url_args) return redirect(f"{provider.acs_url}?{querystring}") return bad_request_message(request, "Invalid sp_binding specified")
def check_saml_request(self) -> Optional[HttpRequest]: """Handle POST bindings""" payload = self.request.POST # Restore the post body from the session # This happens when using POST bindings but the user isn't logged in # (user gets redirected and POST body is 'lost') if SESSION_KEY_POST in self.request.session: payload = self.request.session.pop(SESSION_KEY_POST) if REQUEST_KEY_SAML_REQUEST not in payload: LOGGER.info("check_saml_request: SAML payload missing") return bad_request_message(self.request, "The SAML request payload is missing.") try: auth_n_request = AuthNRequestParser(self.provider).parse( payload[REQUEST_KEY_SAML_REQUEST], payload.get(REQUEST_KEY_RELAY_STATE), ) self.request.session[SESSION_KEY_AUTH_N_REQUEST] = auth_n_request except CannotHandleAssertion as exc: LOGGER.info(str(exc)) return bad_request_message(self.request, str(exc)) return None
def check_saml_request(self) -> Optional[HttpRequest]: """Handle REDIRECT bindings""" if REQUEST_KEY_SAML_REQUEST not in self.request.GET: LOGGER.info("handle_saml_request: SAML payload missing") return bad_request_message(self.request, "The SAML request payload is missing.") try: auth_n_request = AuthNRequestParser(self.provider).parse_detached( self.request.GET[REQUEST_KEY_SAML_REQUEST], self.request.GET.get(REQUEST_KEY_RELAY_STATE), self.request.GET.get(REQUEST_KEY_SAML_SIGNATURE), self.request.GET.get(REQUEST_KEY_SAML_SIG_ALG), ) self.request.session[SESSION_KEY_AUTH_N_REQUEST] = auth_n_request except CannotHandleAssertion as exc: Event.new( EventAction.CONFIGURATION_ERROR, provider=self.provider, message=str(exc), ).save() LOGGER.info(str(exc)) return bad_request_message(self.request, str(exc)) return None
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """final Stage of an OAuth2 Flow""" if PLAN_CONTEXT_PARAMS not in self.executor.plan.context: LOGGER.warning("Got to fulfillment stage with no pending context") return HttpResponseBadRequest() self.params: OAuthAuthorizationParams = self.executor.plan.context.pop( PLAN_CONTEXT_PARAMS) application: Application = self.executor.plan.context.pop( PLAN_CONTEXT_APPLICATION) self.provider = get_object_or_404(OAuth2Provider, pk=application.provider_id) try: # At this point we don't need to check permissions anymore if {PROMPT_NONE, PROMPT_CONSNET}.issubset(self.params.prompt): raise AuthorizeError( self.params.redirect_uri, "consent_required", self.params.grant_type, self.params.state, ) Event.new( EventAction.AUTHORIZE_APPLICATION, authorized_application=application, flow=self.executor.plan.flow_pk, scopes=", ".join(self.params.scope), ).from_http(self.request) return self.redirect(self.create_response_uri()) except (ClientIdError, RedirectUriError) as error: error.to_event(application=application).from_http(request) self.executor.stage_invalid() # pylint: disable=no-member return bad_request_message(request, error.description, title=error.error) except AuthorizeError as error: error.to_event(application=application).from_http(request) self.executor.stage_invalid() return self.redirect(error.create_uri())
def test_bad_request_message(self): """test bad_request_message""" request = self.factory.get("/") self.assertEqual(bad_request_message(request, "foo").status_code, 400)