Exemplo n.º 1
0
 def handle_login_flow(
     self, source: SAMLSource, *stages_to_append, **kwargs
 ) -> HttpResponse:
     """Prepare Authentication Plan, redirect user FlowExecutor"""
     # Ensure redirect is carried through when user was trying to
     # authorize application
     final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
         NEXT_ARG_NAME, "authentik_core:if-admin"
     )
     kwargs.update(
         {
             PLAN_CONTEXT_SSO: True,
             PLAN_CONTEXT_SOURCE: source,
             PLAN_CONTEXT_REDIRECT: final_redirect,
         }
     )
     # We run the Flow planner here so we can pass the Pending user in the context
     planner = FlowPlanner(source.pre_authentication_flow)
     planner.allow_empty_flows = True
     plan = planner.plan(self.request, kwargs)
     for stage in stages_to_append:
         plan.append(stage)
     self.request.session[SESSION_KEY_PLAN] = plan
     return redirect_with_qs(
         "authentik_core:if-flow",
         self.request.GET,
         flow_slug=source.pre_authentication_flow.slug,
     )
Exemplo n.º 2
0
 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})
             )
         }
     )
Exemplo n.º 3
0
 def _handle_login_flow(self, flow: Flow, **kwargs) -> HttpResponse:
     """Prepare Authentication Plan, redirect user FlowExecutor"""
     # Ensure redirect is carried through when user was trying to
     # authorize application
     final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
         NEXT_ARG_NAME, "authentik_core:if-admin")
     kwargs.update({
         # Since we authenticate the user by their token, they have no backend set
         PLAN_CONTEXT_AUTHENTICATION_BACKEND:
         "django.contrib.auth.backends.ModelBackend",
         PLAN_CONTEXT_SSO: True,
         PLAN_CONTEXT_SOURCE: self.source,
         PLAN_CONTEXT_REDIRECT: final_redirect,
     })
     if not flow:
         return HttpResponseBadRequest()
     # We run the Flow planner here so we can pass the Pending user in the context
     planner = FlowPlanner(flow)
     plan = planner.plan(self.request, kwargs)
     for stage in self.get_stages_to_append(flow):
         plan.append(stage)
     self.request.session[SESSION_KEY_PLAN] = plan
     return redirect_with_qs(
         "authentik_core:if-flow",
         self.request.GET,
         flow_slug=flow.slug,
     )
Exemplo n.º 4
0
    def test_stageview_user_identifier(self):
        """Test PLAN_CONTEXT_PENDING_USER_IDENTIFIER"""
        flow = Flow.objects.create(
            name="test-default-context",
            slug="test-default-context",
            designation=FlowDesignation.AUTHENTICATION,
        )
        FlowStageBinding.objects.create(
            target=flow,
            stage=DummyStage.objects.create(name="dummy"),
            order=0)

        ident = "test-identifier"

        user = User.objects.create(username="******")
        request = self.request_factory.get(
            reverse("authentik_api:flow-executor",
                    kwargs={"flow_slug": flow.slug}), )
        request.user = user
        planner = FlowPlanner(flow)
        plan = planner.plan(
            request,
            default_context={PLAN_CONTEXT_PENDING_USER_IDENTIFIER: ident})

        executor = FlowExecutorView()
        executor.plan = plan
        executor.flow = flow

        stage_view = StageView(executor)
        self.assertEqual(
            ident,
            stage_view.get_pending_user(for_display=True).username)
Exemplo n.º 5
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,
     )
Exemplo 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,
     )
Exemplo n.º 7
0
 def restart_flow(self, keep_context=False) -> HttpResponse:
     """Restart the currently active flow, optionally keeping the current context"""
     planner = FlowPlanner(self.flow)
     default_context = None
     if keep_context:
         default_context = self.plan.context
     plan = planner.plan(self.request, default_context)
     self.request.session[SESSION_KEY_PLAN] = plan
     kwargs = self.kwargs
     kwargs.update({"flow_slug": self.flow.slug})
     return redirect_with_qs("authentik_api:flow-executor",
                             self.request.GET, **kwargs)
Exemplo n.º 8
0
 def _initiate_plan(self) -> FlowPlan:
     planner = FlowPlanner(self.flow)
     plan = planner.plan(self.request)
     self.request.session[SESSION_KEY_PLAN] = plan
     try:
         # Call the has_stages getter to check that
         # there are no issues with the class we might've gotten
         # from the cache. If there are errors, just delete all cached flows
         _ = plan.has_stages
     except Exception:  # pylint: disable=broad-except
         keys = cache.keys("flow_*")
         cache.delete_many(keys)
         return self._initiate_plan()
     return plan
Exemplo n.º 9
0
    def test_planner_cache(self):
        """Test planner cache"""
        flow = Flow.objects.create(
            name="test-cache",
            slug="test-cache",
            designation=FlowDesignation.AUTHENTICATION,
        )
        FlowStageBinding.objects.create(
            target=flow, stage=DummyStage.objects.create(name="dummy"), order=0
        )
        request = self.request_factory.get(
            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
        )
        request.user = get_anonymous_user()

        planner = FlowPlanner(flow)
        planner.plan(request)
        self.assertEqual(
            CACHE_MOCK.set.call_count, 1
        )  # Ensure plan is written to cache
        planner = FlowPlanner(flow)
        planner.plan(request)
        self.assertEqual(
            CACHE_MOCK.set.call_count, 1
        )  # Ensure nothing is written to cache
        self.assertEqual(CACHE_MOCK.get.call_count, 2)  # Get is called twice
Exemplo n.º 10
0
    def test_non_applicable_plan(self):
        """Test that empty plan raises exception"""
        flow = Flow.objects.create(
            name="test-empty",
            slug="test-empty",
            designation=FlowDesignation.AUTHENTICATION,
        )
        request = self.request_factory.get(
            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
        )
        request.user = get_anonymous_user()

        with self.assertRaises(FlowNonApplicableException):
            planner = FlowPlanner(flow)
            planner.plan(request)
Exemplo n.º 11
0
    def test_planner_reevaluate_actual(self):
        """Test planner with re-evaluate"""
        flow = Flow.objects.create(
            name="test-default-context",
            slug="test-default-context",
            designation=FlowDesignation.AUTHENTICATION,
        )
        false_policy = DummyPolicy.objects.create(result=False, wait_min=1, wait_max=2)

        binding = FlowStageBinding.objects.create(
            target=flow, stage=DummyStage.objects.create(name="dummy1"), order=0
        )
        binding2 = FlowStageBinding.objects.create(
            target=flow,
            stage=DummyStage.objects.create(name="dummy2"),
            order=1,
            re_evaluate_policies=True,
        )

        PolicyBinding.objects.create(policy=false_policy, target=binding2, order=0)

        request = self.request_factory.get(
            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
        )
        request.user = get_anonymous_user()

        middleware = SessionMiddleware(dummy_get_response)
        middleware.process_request(request)
        request.session.save()

        # Here we patch the dummy policy to evaluate to true so the stage is included
        with patch(
            "authentik.policies.dummy.models.DummyPolicy.passes", POLICY_RETURN_TRUE
        ):
            planner = FlowPlanner(flow)
            plan = planner.plan(request)

            self.assertEqual(plan.stages[0], binding.stage)
            self.assertEqual(plan.stages[1], binding2.stage)

            self.assertIsInstance(plan.markers[0], StageMarker)
            self.assertIsInstance(plan.markers[1], ReevaluateMarker)
Exemplo n.º 12
0
 def _flow_response(self, request: HttpRequest, flow: Flow,
                    **kwargs) -> HttpResponse:
     kwargs[PLAN_CONTEXT_SSO] = True
     kwargs[PLAN_CONTEXT_SOURCE] = self._source
     request.session[SESSION_KEY_PLAN] = FlowPlanner(flow).plan(
         request, kwargs)
     return redirect_with_qs(
         "authentik_core:if-flow",
         request.GET,
         flow_slug=flow.slug,
     )
Exemplo n.º 13
0
    def test_planner_default_context(self):
        """Test planner with default_context"""
        flow = Flow.objects.create(
            name="test-default-context",
            slug="test-default-context",
            designation=FlowDesignation.AUTHENTICATION,
        )
        FlowStageBinding.objects.create(
            target=flow, stage=DummyStage.objects.create(name="dummy"), order=0
        )

        user = User.objects.create(username="******")
        request = self.request_factory.get(
            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
        )
        request.user = user
        planner = FlowPlanner(flow)
        planner.plan(request, default_context={PLAN_CONTEXT_PENDING_USER: user})
        key = cache_key(flow, user)
        self.assertTrue(cache.get(key) is not None)
Exemplo n.º 14
0
    def test_planner_marker_reevaluate(self):
        """Test that the planner creates the proper marker"""
        flow = Flow.objects.create(
            name="test-default-context",
            slug="test-default-context",
            designation=FlowDesignation.AUTHENTICATION,
        )

        FlowStageBinding.objects.create(
            target=flow,
            stage=DummyStage.objects.create(name="dummy1"),
            order=0,
            re_evaluate_policies=True,
        )

        request = self.request_factory.get(
            reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
        )
        request.user = get_anonymous_user()

        planner = FlowPlanner(flow)
        plan = planner.plan(request)

        self.assertIsInstance(plan.markers[0], ReevaluateMarker)
Exemplo n.º 15
0
    def get(self, request: HttpRequest, stage_uuid: str) -> HttpResponse:
        """Initiate planner for selected change flow and redirect to flow executor,
        or raise Http404 if no configure_flow has been set."""
        try:
            stage: Stage = Stage.objects.get_subclass(pk=stage_uuid)
        except Stage.DoesNotExist as exc:
            raise Http404 from exc
        if not isinstance(stage, ConfigurableStage):
            LOGGER.debug("Stage does not inherit ConfigurableStage",
                         stage=stage)
            raise Http404
        if not stage.configure_flow:
            LOGGER.debug("Stage has no configure_flow set", stage=stage)
            raise Http404

        plan = FlowPlanner(stage.configure_flow).plan(
            request, {PLAN_CONTEXT_PENDING_USER: request.user})
        request.session[SESSION_KEY_PLAN] = plan
        return redirect_with_qs(
            "authentik_core:if-flow",
            self.request.GET,
            flow_slug=stage.configure_flow.slug,
        )
Exemplo n.º 16
0
 def test_inner():
     planner = FlowPlanner(self.flow)
     planner.use_cache = False
     planner.plan(self.request, {PLAN_CONTEXT_PENDING_USER: self.user})
Exemplo n.º 17
0
 def _initiate_plan(self) -> FlowPlan:
     planner = FlowPlanner(self.flow)
     plan = planner.plan(self.request)
     self.request.session[SESSION_KEY_PLAN] = plan
     return plan