flows: add check if current plan matches current flow

This commit is contained in:
Jens Langhammer 2020-05-10 20:15:24 +02:00
parent a7567ad8c6
commit 99bab03cce
8 changed files with 77 additions and 21 deletions

View file

@ -21,6 +21,7 @@ class FlowPlan:
"""This data-class is the output of a FlowPlanner. It holds a flat list
of all Stages that should be run."""
flow_pk: str
stages: List[Stage] = field(default_factory=list)
context: Dict[str, Any] = field(default_factory=dict)
@ -46,9 +47,9 @@ class FlowPlanner:
def plan(self, request: HttpRequest) -> FlowPlan:
"""Check each of the flows' policies, check policies for each stage with PolicyBinding
and return ordered list"""
LOGGER.debug("Starting planning process", flow=self.flow)
LOGGER.debug("f(plan): Starting planning process", flow=self.flow)
start_time = time()
plan = FlowPlan()
plan = FlowPlan(flow_pk=self.flow.pk.hex)
# First off, check the flow's direct policy bindings
# to make sure the user even has access to the flow
root_passing, root_passing_messages = self._check_flow_root_policies(request)
@ -65,11 +66,13 @@ class FlowPlanner:
engine.build()
passing, _ = engine.result
if passing:
LOGGER.debug("Stage passing", stage=stage)
LOGGER.debug("f(plan): Stage passing", stage=stage)
plan.stages.append(stage)
end_time = time()
LOGGER.debug(
"Finished planning", flow=self.flow, duration_s=end_time - start_time
"f(plan): Finished planning",
flow=self.flow,
duration_s=end_time - start_time,
)
if not plan.stages:
raise EmptyFlowException()

View file

@ -63,7 +63,20 @@ class FlowExecutorView(View):
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
# Early check if theres an active Plan for the current session
if SESSION_KEY_PLAN not in self.request.session:
if SESSION_KEY_PLAN in self.request.session:
self.plan = self.request.session[SESSION_KEY_PLAN]
if self.plan.flow_pk != self.flow.pk.hex:
LOGGER.warning(
"f(exec): Found existing plan for other flow, deleteing plan",
flow_slug=flow_slug,
)
# Existing plan is deleted from session and instance
self.plan = None
self.cancel()
LOGGER.debug("f(exec): Continuing existing plan", flow_slug=flow_slug)
# Don't check session again as we've either already loaded the plan or we need to plan
if not self.plan:
LOGGER.debug(
"f(exec): No active Plan found, initiating planner", flow_slug=flow_slug
)
@ -75,9 +88,6 @@ class FlowExecutorView(View):
except EmptyFlowException as exc:
LOGGER.warning("f(exec): Flow is empty", exc=exc)
return self.handle_invalid_flow(exc)
else:
LOGGER.debug("f(exec): Continuing existing plan", flow_slug=flow_slug)
self.plan = self.request.session[SESSION_KEY_PLAN]
# We don't save the Plan after getting the next stage
# as it hasn't been successfully passed yet
self.current_stage = self.plan.next()

View file

@ -0,0 +1,14 @@
# Generated by Django 3.0.5 on 2020-05-10 16:48
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("passbook_stages_password", "0001_initial"),
]
operations = [
migrations.RemoveField(model_name="passwordstage", name="password_policies",),
]

View file

@ -42,7 +42,7 @@ class TestPasswordStage(TestCase):
def test_without_user(self):
"""Test without user"""
plan = FlowPlan(stages=[self.stage])
plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
@ -59,7 +59,7 @@ class TestPasswordStage(TestCase):
def test_valid_password(self):
"""Test with a valid pending user and valid password"""
plan = FlowPlan(stages=[self.stage])
plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
@ -77,7 +77,7 @@ class TestPasswordStage(TestCase):
def test_invalid_password(self):
"""Test with a valid pending user and invalid password"""
plan = FlowPlan(stages=[self.stage])
plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan
@ -99,9 +99,7 @@ class TestPasswordStage(TestCase):
def test_permission_denied(self):
"""Test with a valid pending user and valid password.
Backend is patched to return PermissionError"""
# from django.contrib.auth.backends import ModelBackend
# ModelBackend().authenticate()
plan = FlowPlan(stages=[self.stage])
plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan

View file

@ -0,0 +1,31 @@
# Generated by Django 3.0.5 on 2020-05-10 16:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_stages_prompt", "0001_initial"),
]
operations = [
migrations.AlterModelOptions(
name="prompt",
options={"verbose_name": "Prompt", "verbose_name_plural": "Prompts"},
),
migrations.AlterField(
model_name="prompt",
name="type",
field=models.CharField(
choices=[
("text", "Text"),
("e-mail", "Email"),
("password", "Password"),
("number", "Number"),
("hidden", "Hidden"),
],
max_length=100,
),
),
]

View file

@ -97,7 +97,7 @@ class TestPromptStage(TestCase):
def test_render(self):
"""Test render of form, check if all prompts are rendered correctly"""
plan = FlowPlan(stages=[self.stage])
plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
@ -121,7 +121,7 @@ class TestPromptStage(TestCase):
def test_valid_form_request(self):
"""Test a request with valid form data"""
plan = FlowPlan(stages=[self.stage])
plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()

View file

@ -34,7 +34,7 @@ class TestUserCreateStage(TestCase):
def test_valid_create(self):
"""Test creation of user"""
plan = FlowPlan(stages=[self.stage])
plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
plan.context[PLAN_CONTEXT_PROMPT] = {
"username": "test-user",
"name": "name",
@ -59,7 +59,7 @@ class TestUserCreateStage(TestCase):
def test_without_data(self):
"""Test without data results in error"""
plan = FlowPlan(stages=[self.stage])
plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()

View file

@ -29,7 +29,7 @@ class TestUserLoginStage(TestCase):
def test_valid_password(self):
"""Test with a valid pending user and backend"""
plan = FlowPlan(stages=[self.stage])
plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
plan.context[
PLAN_CONTEXT_AUTHENTICATION_BACKEND
@ -48,7 +48,7 @@ class TestUserLoginStage(TestCase):
def test_without_user(self):
"""Test a plan without any pending user, resulting in a denied"""
plan = FlowPlan(stages=[self.stage])
plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
@ -63,7 +63,7 @@ class TestUserLoginStage(TestCase):
def test_without_backend(self):
"""Test a plan with pending user, without backend, resulting in a denied"""
plan = FlowPlan(stages=[self.stage])
plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session
session[SESSION_KEY_PLAN] = plan