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

View File

@ -63,7 +63,20 @@ class FlowExecutorView(View):
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse: def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
# Early check if theres an active Plan for the current session # 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( LOGGER.debug(
"f(exec): No active Plan found, initiating planner", flow_slug=flow_slug "f(exec): No active Plan found, initiating planner", flow_slug=flow_slug
) )
@ -75,9 +88,6 @@ class FlowExecutorView(View):
except EmptyFlowException as exc: except EmptyFlowException as exc:
LOGGER.warning("f(exec): Flow is empty", exc=exc) LOGGER.warning("f(exec): Flow is empty", exc=exc)
return self.handle_invalid_flow(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 # We don't save the Plan after getting the next stage
# as it hasn't been successfully passed yet # as it hasn't been successfully passed yet
self.current_stage = self.plan.next() 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): def test_without_user(self):
"""Test without user""" """Test without user"""
plan = FlowPlan(stages=[self.stage]) plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
session.save() session.save()
@ -59,7 +59,7 @@ class TestPasswordStage(TestCase):
def test_valid_password(self): def test_valid_password(self):
"""Test with a valid pending user and valid password""" """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 plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
@ -77,7 +77,7 @@ class TestPasswordStage(TestCase):
def test_invalid_password(self): def test_invalid_password(self):
"""Test with a valid pending user and invalid password""" """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 plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
@ -99,9 +99,7 @@ class TestPasswordStage(TestCase):
def test_permission_denied(self): def test_permission_denied(self):
"""Test with a valid pending user and valid password. """Test with a valid pending user and valid password.
Backend is patched to return PermissionError""" Backend is patched to return PermissionError"""
# from django.contrib.auth.backends import ModelBackend plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
# ModelBackend().authenticate()
plan = FlowPlan(stages=[self.stage])
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan 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): def test_render(self):
"""Test render of form, check if all prompts are rendered correctly""" """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 = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
session.save() session.save()
@ -121,7 +121,7 @@ class TestPromptStage(TestCase):
def test_valid_form_request(self): def test_valid_form_request(self):
"""Test a request with valid form data""" """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 = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
session.save() session.save()

View File

@ -34,7 +34,7 @@ class TestUserCreateStage(TestCase):
def test_valid_create(self): def test_valid_create(self):
"""Test creation of user""" """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] = { plan.context[PLAN_CONTEXT_PROMPT] = {
"username": "test-user", "username": "test-user",
"name": "name", "name": "name",
@ -59,7 +59,7 @@ class TestUserCreateStage(TestCase):
def test_without_data(self): def test_without_data(self):
"""Test without data results in error""" """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 = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
session.save() session.save()

View File

@ -29,7 +29,7 @@ class TestUserLoginStage(TestCase):
def test_valid_password(self): def test_valid_password(self):
"""Test with a valid pending user and backend""" """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_PENDING_USER] = self.user
plan.context[ plan.context[
PLAN_CONTEXT_AUTHENTICATION_BACKEND PLAN_CONTEXT_AUTHENTICATION_BACKEND
@ -48,7 +48,7 @@ class TestUserLoginStage(TestCase):
def test_without_user(self): def test_without_user(self):
"""Test a plan without any pending user, resulting in a denied""" """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 = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan
session.save() session.save()
@ -63,7 +63,7 @@ class TestUserLoginStage(TestCase):
def test_without_backend(self): def test_without_backend(self):
"""Test a plan with pending user, without backend, resulting in a denied""" """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 plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
session = self.client.session session = self.client.session
session[SESSION_KEY_PLAN] = plan session[SESSION_KEY_PLAN] = plan