diff --git a/passbook/flows/planner.py b/passbook/flows/planner.py index 48cb380f6..5ed272888 100644 --- a/passbook/flows/planner.py +++ b/passbook/flows/planner.py @@ -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() diff --git a/passbook/flows/views.py b/passbook/flows/views.py index a7a54d85a..d687c8800 100644 --- a/passbook/flows/views.py +++ b/passbook/flows/views.py @@ -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() diff --git a/passbook/stages/password/migrations/0002_remove_passwordstage_password_policies.py b/passbook/stages/password/migrations/0002_remove_passwordstage_password_policies.py new file mode 100644 index 000000000..2eff28c5d --- /dev/null +++ b/passbook/stages/password/migrations/0002_remove_passwordstage_password_policies.py @@ -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",), + ] diff --git a/passbook/stages/password/tests.py b/passbook/stages/password/tests.py index 1a98333e4..82cb4d947 100644 --- a/passbook/stages/password/tests.py +++ b/passbook/stages/password/tests.py @@ -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 diff --git a/passbook/stages/prompt/migrations/0002_auto_20200510_1648.py b/passbook/stages/prompt/migrations/0002_auto_20200510_1648.py new file mode 100644 index 000000000..439e201f1 --- /dev/null +++ b/passbook/stages/prompt/migrations/0002_auto_20200510_1648.py @@ -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, + ), + ), + ] diff --git a/passbook/stages/prompt/tests.py b/passbook/stages/prompt/tests.py index f94decf4a..3551a2e47 100644 --- a/passbook/stages/prompt/tests.py +++ b/passbook/stages/prompt/tests.py @@ -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() diff --git a/passbook/stages/user_create/tests.py b/passbook/stages/user_create/tests.py index d0f21ba88..ae0295822 100644 --- a/passbook/stages/user_create/tests.py +++ b/passbook/stages/user_create/tests.py @@ -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() diff --git a/passbook/stages/user_login/tests.py b/passbook/stages/user_login/tests.py index 83bb38b74..59b988d2b 100644 --- a/passbook/stages/user_login/tests.py +++ b/passbook/stages/user_login/tests.py @@ -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