flows: add check if current plan matches current flow
This commit is contained in:
parent
a7567ad8c6
commit
99bab03cce
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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",),
|
||||||
|
]
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
Reference in New Issue