Skip to content

Commit

Permalink
flows: more tests (#11587)
Browse files Browse the repository at this point in the history
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
  • Loading branch information
BeryJu authored Dec 20, 2024
1 parent 15be3f2 commit ee64826
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 43 deletions.
147 changes: 118 additions & 29 deletions authentik/flows/tests/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from django.test.client import RequestFactory
from django.urls import reverse

from authentik.core.models import User
from authentik.core.tests.utils import create_test_flow
from authentik.core.models import Group, User
from authentik.core.tests.utils import create_test_flow, create_test_user
from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import (
FlowDeniedAction,
Expand Down Expand Up @@ -255,7 +255,11 @@ def test_reevaluate_remove_last(self):
)

binding = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
target=flow,
stage=DummyStage.objects.create(name=generate_id()),
order=0,
evaluate_on_plan=True,
re_evaluate_policies=False,
)
binding2 = FlowStageBinding.objects.create(
target=flow,
Expand All @@ -278,8 +282,8 @@ def test_reevaluate_remove_last(self):
self.assertEqual(plan.bindings[0], binding)
self.assertEqual(plan.bindings[1], binding2)

self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], ReevaluateMarker)
self.assertEqual(plan.markers[0].__class__, StageMarker)
self.assertEqual(plan.markers[1].__class__, ReevaluateMarker)

# Second request, this passes the first dummy stage
response = self.client.post(exec_url)
Expand All @@ -301,7 +305,11 @@ def test_reevaluate_remove_middle(self):
)

binding = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
target=flow,
stage=DummyStage.objects.create(name=generate_id()),
order=0,
evaluate_on_plan=True,
re_evaluate_policies=False,
)
binding2 = FlowStageBinding.objects.create(
target=flow,
Expand All @@ -310,7 +318,11 @@ def test_reevaluate_remove_middle(self):
re_evaluate_policies=True,
)
binding3 = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=2
target=flow,
stage=DummyStage.objects.create(name=generate_id()),
order=2,
evaluate_on_plan=True,
re_evaluate_policies=False,
)

PolicyBinding.objects.create(policy=false_policy, target=binding2, order=0)
Expand All @@ -328,9 +340,9 @@ def test_reevaluate_remove_middle(self):
self.assertEqual(plan.bindings[1], binding2)
self.assertEqual(plan.bindings[2], binding3)

self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], ReevaluateMarker)
self.assertIsInstance(plan.markers[2], StageMarker)
self.assertEqual(plan.markers[0].__class__, StageMarker)
self.assertEqual(plan.markers[1].__class__, ReevaluateMarker)
self.assertEqual(plan.markers[2].__class__, StageMarker)

# Second request, this passes the first dummy stage
response = self.client.post(exec_url)
Expand All @@ -341,8 +353,8 @@ def test_reevaluate_remove_middle(self):
self.assertEqual(plan.bindings[0], binding2)
self.assertEqual(plan.bindings[1], binding3)

self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], StageMarker)
self.assertEqual(plan.markers[0].__class__, ReevaluateMarker)
self.assertEqual(plan.markers[1].__class__, StageMarker)

# third request, this should trigger the re-evaluate
# We do this request without the patch, so the policy results in false
Expand All @@ -360,7 +372,11 @@ def test_reevaluate_keep(self):
)

binding = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
target=flow,
stage=DummyStage.objects.create(name=generate_id()),
order=0,
evaluate_on_plan=True,
re_evaluate_policies=False,
)
binding2 = FlowStageBinding.objects.create(
target=flow,
Expand All @@ -369,7 +385,11 @@ def test_reevaluate_keep(self):
re_evaluate_policies=True,
)
binding3 = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=2
target=flow,
stage=DummyStage.objects.create(name=generate_id()),
order=2,
evaluate_on_plan=True,
re_evaluate_policies=False,
)

PolicyBinding.objects.create(policy=true_policy, target=binding2, order=0)
Expand All @@ -387,9 +407,9 @@ def test_reevaluate_keep(self):
self.assertEqual(plan.bindings[1], binding2)
self.assertEqual(plan.bindings[2], binding3)

self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], ReevaluateMarker)
self.assertIsInstance(plan.markers[2], StageMarker)
self.assertEqual(plan.markers[0].__class__, StageMarker)
self.assertEqual(plan.markers[1].__class__, ReevaluateMarker)
self.assertEqual(plan.markers[2].__class__, StageMarker)

# Second request, this passes the first dummy stage
response = self.client.post(exec_url)
Expand All @@ -400,8 +420,8 @@ def test_reevaluate_keep(self):
self.assertEqual(plan.bindings[0], binding2)
self.assertEqual(plan.bindings[1], binding3)

self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], StageMarker)
self.assertEqual(plan.markers[0].__class__, ReevaluateMarker)
self.assertEqual(plan.markers[1].__class__, StageMarker)

# Third request, this passes the first dummy stage
response = self.client.post(exec_url)
Expand All @@ -411,7 +431,7 @@ def test_reevaluate_keep(self):

self.assertEqual(plan.bindings[0], binding3)

self.assertIsInstance(plan.markers[0], StageMarker)
self.assertEqual(plan.markers[0].__class__, StageMarker)

# third request, this should trigger the re-evaluate
# We do this request without the patch, so the policy results in false
Expand All @@ -429,7 +449,11 @@ def test_reevaluate_remove_consecutive(self):
)

binding = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
target=flow,
stage=DummyStage.objects.create(name=generate_id()),
order=0,
evaluate_on_plan=True,
re_evaluate_policies=False,
)
binding2 = FlowStageBinding.objects.create(
target=flow,
Expand All @@ -444,7 +468,11 @@ def test_reevaluate_remove_consecutive(self):
re_evaluate_policies=True,
)
binding4 = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=2
target=flow,
stage=DummyStage.objects.create(name=generate_id()),
order=2,
evaluate_on_plan=True,
re_evaluate_policies=False,
)

PolicyBinding.objects.create(policy=false_policy, target=binding2, order=0)
Expand All @@ -465,10 +493,10 @@ def test_reevaluate_remove_consecutive(self):
self.assertEqual(plan.bindings[2], binding3)
self.assertEqual(plan.bindings[3], binding4)

self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], ReevaluateMarker)
self.assertIsInstance(plan.markers[2], ReevaluateMarker)
self.assertIsInstance(plan.markers[3], StageMarker)
self.assertEqual(plan.markers[0].__class__, StageMarker)
self.assertEqual(plan.markers[1].__class__, ReevaluateMarker)
self.assertEqual(plan.markers[2].__class__, ReevaluateMarker)
self.assertEqual(plan.markers[3].__class__, StageMarker)

# Second request, this passes the first dummy stage
response = self.client.post(exec_url)
Expand Down Expand Up @@ -519,9 +547,9 @@ def test_invalid_restart(self):
)
# Stage 0 is a deny stage that is added dynamically
# when the reputation policy says so
deny_stage = DenyStage.objects.create(name="deny")
deny_stage = DenyStage.objects.create(name=generate_id())
reputation_policy = ReputationPolicy.objects.create(
name="reputation", threshold=-1, check_ip=False
name=generate_id(), threshold=-1, check_ip=False
)
deny_binding = FlowStageBinding.objects.create(
target=flow,
Expand All @@ -534,7 +562,7 @@ def test_invalid_restart(self):

# Stage 1 is an identification stage
ident_stage = IdentificationStage.objects.create(
name="ident",
name=generate_id(),
user_fields=[UserFields.E_MAIL],
pretend_user_exists=False,
)
Expand All @@ -559,3 +587,64 @@ def test_invalid_restart(self):
)
response = self.client.post(exec_url, {"uid_field": "invalid-string"}, follow=True)
self.assertStageResponse(response, flow, component="ak-stage-access-denied")

def test_re_evaluate_group_binding(self):
"""Test re-evaluate stage binding that has a policy binding to a group"""
flow = create_test_flow()

user_group_membership = create_test_user()
user_direct_binding = create_test_user()
user_other = create_test_user()

group_a = Group.objects.create(name=generate_id())
user_group_membership.ak_groups.add(group_a)

# Stage 0 is an identification stage
ident_stage = IdentificationStage.objects.create(
name=generate_id(),
user_fields=[UserFields.USERNAME],
pretend_user_exists=False,
)
FlowStageBinding.objects.create(
target=flow,
stage=ident_stage,
order=0,
)

# Stage 1 is a dummy stage that is only shown for users in group_a
dummy_stage = DummyStage.objects.create(name=generate_id())
dummy_binding = FlowStageBinding.objects.create(target=flow, stage=dummy_stage, order=1)
PolicyBinding.objects.create(group=group_a, target=dummy_binding, order=0)
PolicyBinding.objects.create(user=user_direct_binding, target=dummy_binding, order=0)

# Stage 2 is a deny stage that (in this case) only user_b will see
deny_stage = DenyStage.objects.create(name=generate_id())
FlowStageBinding.objects.create(target=flow, stage=deny_stage, order=2)

exec_url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug})

with self.subTest(f"Test user access through group: {user_group_membership}"):
self.client.logout()
# First request, run the planner
response = self.client.get(exec_url)
self.assertStageResponse(response, flow, component="ak-stage-identification")
response = self.client.post(
exec_url, {"uid_field": user_group_membership.username}, follow=True
)
self.assertStageResponse(response, flow, component="ak-stage-dummy")
with self.subTest(f"Test user access through user: {user_direct_binding}"):
self.client.logout()
# First request, run the planner
response = self.client.get(exec_url)
self.assertStageResponse(response, flow, component="ak-stage-identification")
response = self.client.post(
exec_url, {"uid_field": user_direct_binding.username}, follow=True
)
self.assertStageResponse(response, flow, component="ak-stage-dummy")
with self.subTest(f"Test user has no access: {user_other}"):
self.client.logout()
# First request, run the planner
response = self.client.get(exec_url)
self.assertStageResponse(response, flow, component="ak-stage-identification")
response = self.client.post(exec_url, {"uid_field": user_other.username}, follow=True)
self.assertStageResponse(response, flow, component="ak-stage-access-denied")
22 changes: 14 additions & 8 deletions authentik/flows/tests/test_inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.models import FlowDesignation, FlowStageBinding, InvalidResponseAction
from authentik.lib.generators import generate_id
from authentik.stages.dummy.models import DummyStage
from authentik.stages.identification.models import IdentificationStage, UserFields

Expand All @@ -26,7 +27,7 @@ def test(self):

# Stage 1 is an identification stage
ident_stage = IdentificationStage.objects.create(
name="ident",
name=generate_id(),
user_fields=[UserFields.USERNAME],
)
FlowStageBinding.objects.create(
Expand All @@ -35,9 +36,8 @@ def test(self):
order=1,
invalid_response_action=InvalidResponseAction.RESTART_WITH_CONTEXT,
)
FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy2"), order=1
)
dummy_stage = DummyStage.objects.create(name=generate_id())
FlowStageBinding.objects.create(target=flow, stage=dummy_stage, order=1)

res = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
Expand Down Expand Up @@ -68,9 +68,11 @@ def test(self):
)
content = loads(ins.content)
self.assertEqual(content["is_completed"], False)
self.assertEqual(content["current_plan"]["current_stage"]["stage_obj"]["name"], "ident")
self.assertEqual(
content["current_plan"]["next_planned_stage"]["stage_obj"]["name"], "dummy2"
content["current_plan"]["current_stage"]["stage_obj"]["name"], ident_stage.name
)
self.assertEqual(
content["current_plan"]["next_planned_stage"]["stage_obj"]["name"], dummy_stage.name
)

self.client.post(
Expand All @@ -84,8 +86,12 @@ def test(self):
)
content = loads(ins.content)
self.assertEqual(content["is_completed"], False)
self.assertEqual(content["plans"][0]["current_stage"]["stage_obj"]["name"], "ident")
self.assertEqual(content["current_plan"]["current_stage"]["stage_obj"]["name"], "dummy2")
self.assertEqual(
content["plans"][0]["current_stage"]["stage_obj"]["name"], ident_stage.name
)
self.assertEqual(
content["current_plan"]["current_stage"]["stage_obj"]["name"], dummy_stage.name
)
self.assertEqual(
content["current_plan"]["plan_context"]["pending_user"]["username"], self.admin.username
)
18 changes: 12 additions & 6 deletions authentik/flows/tests/test_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
cache_key,
)
from authentik.flows.stage import StageView
from authentik.lib.generators import generate_id
from authentik.lib.tests.utils import dummy_get_response
from authentik.outposts.apps import MANAGED_OUTPOST
from authentik.outposts.models import Outpost
Expand Down Expand Up @@ -153,7 +154,7 @@ def test_planner_cache(self):
"""Test planner cache"""
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy"), order=0
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
)
request = self.request_factory.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
Expand All @@ -172,7 +173,7 @@ def test_planner_default_context(self):
"""Test planner with default_context"""
flow = create_test_flow()
FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy"), order=0
target=flow, stage=DummyStage.objects.create(name=generate_id()), order=0
)

user = User.objects.create(username="test-user")
Expand All @@ -191,7 +192,7 @@ def test_planner_marker_reevaluate(self):

FlowStageBinding.objects.create(
target=flow,
stage=DummyStage.objects.create(name="dummy1"),
stage=DummyStage.objects.create(name=generate_id()),
order=0,
re_evaluate_policies=True,
)
Expand All @@ -204,19 +205,22 @@ def test_planner_marker_reevaluate(self):
planner = FlowPlanner(flow)
plan = planner.plan(request)

self.assertIsInstance(plan.markers[0], ReevaluateMarker)
self.assertEqual(plan.markers[0].__class__, ReevaluateMarker)

def test_planner_reevaluate_actual(self):
"""Test planner with re-evaluate"""
flow = create_test_flow()
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
target=flow,
stage=DummyStage.objects.create(name=generate_id()),
order=0,
re_evaluate_policies=False,
)
binding2 = FlowStageBinding.objects.create(
target=flow,
stage=DummyStage.objects.create(name="dummy2"),
stage=DummyStage.objects.create(name=generate_id()),
order=1,
re_evaluate_policies=True,
)
Expand All @@ -240,6 +244,8 @@ def test_planner_reevaluate_actual(self):
self.assertEqual(plan.bindings[0], binding)
self.assertEqual(plan.bindings[1], binding2)

self.assertEqual(plan.markers[0].__class__, StageMarker)
self.assertEqual(plan.markers[1].__class__, ReevaluateMarker)
self.assertIsInstance(plan.markers[0], StageMarker)
self.assertIsInstance(plan.markers[1], ReevaluateMarker)

Expand Down

0 comments on commit ee64826

Please sign in to comment.