Skip to content

Commit

Permalink
flows: add shortcut to redirect current flow (#3192)
Browse files Browse the repository at this point in the history
  • Loading branch information
BeryJu committed Jul 1, 2022
1 parent 1c64616 commit 5e3f44d
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 8 deletions.
9 changes: 6 additions & 3 deletions authentik/flows/markers.py
Expand Up @@ -47,7 +47,8 @@ def process(
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER

LOGGER.debug(
"f(plan_inst)[re-eval marker]: running re-evaluation",
"f(plan_inst): running re-evaluation",
marker="ReevaluateMarker",
binding=binding,
policy_binding=self.binding,
)
Expand All @@ -56,13 +57,15 @@ def process(
)
engine.use_cache = False
engine.request.set_http_request(http_request)
engine.request.context = plan.context
engine.request.context["flow_plan"] = plan
engine.request.context.update(plan.context)
engine.build()
result = engine.result
if result.passing:
return binding
LOGGER.warning(
"f(plan_inst)[re-eval marker]: binding failed re-evaluation",
"f(plan_inst): binding failed re-evaluation",
marker="ReevaluateMarker",
binding=binding,
messages=result.messages,
)
Expand Down
4 changes: 3 additions & 1 deletion authentik/flows/models.py
Expand Up @@ -87,13 +87,15 @@ def __str__(self):
return f"Stage {self.name}"


def in_memory_stage(view: type["StageView"]) -> Stage:
def in_memory_stage(view: type["StageView"], **kwargs) -> Stage:
"""Creates an in-memory stage instance, based on a `view` as view."""
stage = Stage()
# Because we can't pickle a locally generated function,
# we set the view as a separate property and reference a generic function
# that returns that member
setattr(stage, "__in_memory_type", view)
for key, value in kwargs.items():
setattr(stage, key, value)
return stage


Expand Down
13 changes: 10 additions & 3 deletions authentik/flows/planner.py
Expand Up @@ -13,7 +13,7 @@
from authentik.flows.apps import HIST_FLOWS_PLAN_TIME
from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException
from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, Stage
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding, Stage, in_memory_stage
from authentik.lib.config import CONFIG
from authentik.policies.engine import PolicyEngine

Expand Down Expand Up @@ -62,6 +62,12 @@ def insert_stage(self, stage: Stage, marker: Optional[StageMarker] = None):
self.bindings.insert(1, FlowStageBinding(stage=stage, order=0))
self.markers.insert(1, marker or StageMarker())

def redirect(self, destination: str):
"""Insert a redirect stage as next stage"""
from authentik.flows.stage import RedirectStage

self.insert_stage(in_memory_stage(RedirectStage, destination=destination))

def next(self, http_request: Optional[HttpRequest]) -> Optional[FlowStageBinding]:
"""Return next pending stage from the bottom of the list"""
if not self.has_stages:
Expand Down Expand Up @@ -137,7 +143,7 @@ def plan(
engine = PolicyEngine(self.flow, user, request)
if default_context:
span.set_data("default_context", cleanse_dict(default_context))
engine.request.context = default_context
engine.request.context.update(default_context)
engine.build()
result = engine.result
if not result.passing:
Expand Down Expand Up @@ -198,7 +204,8 @@ def _build_plan(
stage=binding.stage,
)
engine = PolicyEngine(binding, user, request)
engine.request.context = plan.context
engine.request.context["flow_plan"] = plan
engine.request.context.update(plan.context)
engine.build()
if engine.passing:
self._logger.debug(
Expand Down
19 changes: 19 additions & 0 deletions authentik/flows/stage.py
Expand Up @@ -19,6 +19,7 @@
ChallengeTypes,
ContextualFlowInfo,
HttpChallengeResponse,
RedirectChallenge,
WithUserInfoChallenge,
)
from authentik.flows.models import InvalidResponseAction
Expand Down Expand Up @@ -219,3 +220,21 @@ def get_challenge(self, *args, **kwargs) -> Challenge:
# .get() method is called
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse: # pragma: no cover
return self.executor.cancel()


class RedirectStage(ChallengeStageView):
"""Redirect to any URL"""

def get_challenge(self, *args, **kwargs) -> RedirectChallenge:
destination = getattr(
self.executor.current_stage, "destination", reverse("authentik_core:root-redirect")
)
return RedirectChallenge(
data={
"type": ChallengeTypes.REDIRECT.value,
"to": destination,
}
)

def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
return HttpChallengeResponse(self.get_challenge())
File renamed without changes.
17 changes: 17 additions & 0 deletions website/docs/flow/examples/snippets.md
@@ -0,0 +1,17 @@
---
title: Example policy snippets for flows
---

### Redirect current flow to another URL

:::info
Requires authentik 2022.7
:::

```python
plan = request.context["flow_plan"]
plan.redirect("https://foo.bar")
return False
```

This policy should be bound to the stage after your redirect should happen. For example, if you have an identification and a password stage, and you want to redirect after identification, bind the policy to the password stage. Make sure the policy binding is set to re-evaluate policies.
1 change: 1 addition & 0 deletions website/docs/policies/expression.mdx
Expand Up @@ -94,6 +94,7 @@ Additionally, when the policy is executed from a flow, every variable from the f

This includes the following:

- `context['flow_plan']`: The actual flow plan itself, can be used to inject stages.
- `context['prompt_data']`: Data which has been saved from a prompt stage or an external source.
- `context['application']`: The application the user is in the process of authorizing.
- `context['pending_user']`: The currently pending user, see [User](../user-group/user.md#object-attributes)
Expand Down
6 changes: 5 additions & 1 deletion website/sidebars.js
Expand Up @@ -102,7 +102,11 @@ module.exports = {
items: [
"flow/layouts",
"flow/inspector",
"flow/examples",
{
type: "category",
label: "Examples",
items: ["flow/examples/flows", "flow/examples/snippets"],
},
{
type: "category",
label: "Executors",
Expand Down

0 comments on commit 5e3f44d

Please sign in to comment.