Ejemplo n.º 1
0
 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,
     )
Ejemplo n.º 2
0
 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 []
Ejemplo n.º 3
0
 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,
     )
Ejemplo n.º 4
0
    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,
            },
        )
Ejemplo n.º 5
0
    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()
Ejemplo n.º 6
0
 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,
     )
Ejemplo n.º 7
0
 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,
     )