flows: rename re_evaluate_policies to evaluate_on_call, add evaluate_on_plan

This commit is contained in:
Jens Langhammer 2020-10-20 15:06:36 +02:00
parent e2ca72adf0
commit 870e01f836
16 changed files with 138 additions and 60 deletions

View File

@ -137,7 +137,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -149,7 +149,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -161,7 +161,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -173,7 +173,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
}
]

View File

@ -156,7 +156,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -168,7 +168,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -180,7 +180,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -192,7 +192,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -204,7 +204,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
}
]

View File

@ -68,7 +68,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -80,7 +80,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -92,7 +92,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -104,7 +104,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
}
]

View File

@ -71,7 +71,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -83,7 +83,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -95,7 +95,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -107,7 +107,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{

View File

@ -130,7 +130,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -142,7 +142,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -154,7 +154,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -166,7 +166,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
},
{
@ -178,7 +178,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
}
]

View File

@ -30,7 +30,7 @@
},
"model": "passbook_flows.flowstagebinding",
"attrs": {
"re_evaluate_policies": false
"evaluate_on_call_policies": false
}
}
]

View File

@ -27,7 +27,15 @@ class FlowStageBindingSerializer(ModelSerializer):
class Meta:
model = FlowStageBinding
fields = ["pk", "target", "stage", "re_evaluate_policies", "order", "policies"]
fields = [
"pk",
"target",
"stage",
"evaluate_on_plan",
"evaluate_on_call",
"order",
"policies",
]
class FlowStageBindingViewSet(ModelViewSet):

View File

@ -50,12 +50,10 @@ class FlowStageBindingForm(forms.ModelForm):
fields = [
"target",
"stage",
"re_evaluate_policies",
"evaluate_on_plan",
"evaluate_on_call",
"order",
]
labels = {
"re_evaluate_policies": _("Re-evaluate Policies"),
}
widgets = {
"name": forms.TextInput(),
}

View File

@ -2,6 +2,7 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional
from django.http.request import HttpRequest
from structlog import get_logger
from passbook.core.models import User
@ -20,7 +21,9 @@ class StageMarker:
"""Base stage marker class, no extra attributes, and has no special handler."""
# pylint: disable=unused-argument
def process(self, plan: "FlowPlan", stage: Stage) -> Optional[Stage]:
def process(
self, plan: "FlowPlan", stage: Stage, http_request: Optional[HttpRequest]
) -> Optional[Stage]:
"""Process callback for this marker. This should be overridden by sub-classes.
If a stage should be removed, return None."""
return stage
@ -33,10 +36,14 @@ class ReevaluateMarker(StageMarker):
binding: PolicyBinding
user: User
def process(self, plan: "FlowPlan", stage: Stage) -> Optional[Stage]:
def process(
self, plan: "FlowPlan", stage: Stage, http_request: Optional[HttpRequest]
) -> Optional[Stage]:
"""Re-evaluate policies bound to stage, and if they fail, remove from plan"""
engine = PolicyEngine(self.binding, self.user)
engine.use_cache = False
if http_request:
engine.request.http_request = http_request
engine.request.context = plan.context
engine.build()
result = engine.result

View File

@ -0,0 +1,34 @@
# Generated by Django 3.1.2 on 2020-10-20 12:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_flows", "0014_auto_20200925_2332"),
]
operations = [
migrations.RenameField(
model_name="flowstagebinding",
old_name="re_evaluate_policies",
new_name="evaluate_on_call",
),
migrations.AlterField(
model_name="flowstagebinding",
name="evaluate_on_call",
field=models.BooleanField(
default=False,
help_text="Evaluate policies when the Stage is present to the user.",
),
),
migrations.AddField(
model_name="flowstagebinding",
name="evaluate_on_plan",
field=models.BooleanField(
default=True,
help_text="Evaluate policies during the Flow planning process. Disable this for input-based policies.",
),
),
]

View File

@ -154,15 +154,19 @@ class FlowStageBinding(SerializerModel, PolicyBindingModel):
target = models.ForeignKey("Flow", on_delete=models.CASCADE)
stage = InheritanceForeignKey(Stage, on_delete=models.CASCADE)
re_evaluate_policies = models.BooleanField(
default=False,
evaluate_on_plan = models.BooleanField(
default=True,
help_text=_(
(
"When this option is enabled, the planner will re-evaluate "
"policies bound to this binding."
"Evaluate policies during the Flow planning process. "
"Disable this for input-based policies."
)
),
)
evaluate_on_call = models.BooleanField(
default=False,
help_text=_("Evaluate policies when the Stage is present to the user."),
)
order = models.IntegerField()

View File

@ -46,7 +46,7 @@ class FlowPlan:
self.stages.append(stage)
self.markers.append(marker or StageMarker())
def next(self) -> Optional[Stage]:
def next(self, http_request: Optional[HttpRequest]) -> Optional[Stage]:
"""Return next pending stage from the bottom of the list"""
if not self.has_stages:
return None
@ -55,7 +55,7 @@ class FlowPlan:
if marker.__class__ is not StageMarker:
LOGGER.debug("f(plan_inst): stage has marker", stage=stage, marker=marker)
marked_stage = marker.process(self, stage)
marked_stage = marker.process(self, stage, http_request)
if not marked_stage:
LOGGER.debug("f(plan_inst): marker returned none, next stage", stage=stage)
self.stages.remove(stage)
@ -63,7 +63,7 @@ class FlowPlan:
if not self.has_stages:
return None
# pylint: disable=not-callable
return self.next()
return self.next(http_request)
return marked_stage
def pop(self):
@ -159,23 +159,41 @@ class FlowPlanner:
for binding in FlowStageBinding.objects.filter(
target__pk=self.flow.pk
).order_by("order"):
engine = PolicyEngine(binding, user, request)
engine.request.context = plan.context
engine.build()
if engine.passing:
binding: FlowStageBinding
stage = binding.stage
marker = StageMarker()
if binding.evaluate_on_plan:
LOGGER.debug(
"f(plan): Stage passing", stage=binding.stage, flow=self.flow
"f(plan): evaluating on plan",
stage=binding.stage,
flow=self.flow,
)
plan.stages.append(binding.stage)
marker = StageMarker()
if binding.re_evaluate_policies:
engine = PolicyEngine(binding, user, request)
engine.request.context = plan.context
engine.build()
if engine.passing:
LOGGER.debug(
"f(plan): Stage has re-evaluate marker",
"f(plan): Stage passing",
stage=binding.stage,
flow=self.flow,
)
marker = ReevaluateMarker(binding=binding, user=user)
plan.markers.append(marker)
else:
stage = None
else:
LOGGER.debug(
"f(plan): not evaluating on plan",
stage=binding.stage,
flow=self.flow,
)
if binding.evaluate_on_call and stage:
LOGGER.debug(
"f(plan): Stage has re-evaluate marker",
stage=binding.stage,
flow=self.flow,
)
marker = ReevaluateMarker(binding=binding, user=user)
if stage:
plan.append(stage, marker)
LOGGER.debug(
"f(plan): Finished building",
flow=self.flow,

View File

@ -132,7 +132,7 @@ class TestFlowPlanner(TestCase):
target=flow,
stage=DummyStage.objects.create(name="dummy1"),
order=0,
re_evaluate_policies=True,
evaluate_on_call=True,
)
request = self.request_factory.get(
@ -161,7 +161,7 @@ class TestFlowPlanner(TestCase):
target=flow,
stage=DummyStage.objects.create(name="dummy2"),
order=1,
re_evaluate_policies=True,
evaluate_on_call=True,
)
PolicyBinding.objects.create(policy=false_policy, target=binding2, order=0)

View File

@ -174,7 +174,7 @@ class TestFlowExecutor(TestCase):
target=flow,
stage=DummyStage.objects.create(name="dummy2"),
order=1,
re_evaluate_policies=True,
evaluate_on_call=True,
)
PolicyBinding.objects.create(policy=false_policy, target=binding2, order=0)
@ -225,7 +225,7 @@ class TestFlowExecutor(TestCase):
target=flow,
stage=DummyStage.objects.create(name="dummy2"),
order=1,
re_evaluate_policies=True,
evaluate_on_call=True,
)
binding3 = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy3"), order=2
@ -292,13 +292,13 @@ class TestFlowExecutor(TestCase):
target=flow,
stage=DummyStage.objects.create(name="dummy2"),
order=1,
re_evaluate_policies=True,
evaluate_on_call=True,
)
binding3 = FlowStageBinding.objects.create(
target=flow,
stage=DummyStage.objects.create(name="dummy3"),
order=2,
re_evaluate_policies=True,
evaluate_on_call=True,
)
binding4 = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy4"), order=2

View File

@ -86,7 +86,7 @@ class FlowExecutorView(View):
return to_stage_response(self.request, self.handle_invalid_flow(exc))
# We don't save the Plan after getting the next stage
# as it hasn't been successfully passed yet
next_stage = self.plan.next()
next_stage = self.plan.next(self.request)
if not next_stage:
LOGGER.debug("f(exec): no more stages, flow is done.")
return self._flow_done()

View File

@ -833,7 +833,12 @@ paths:
description: ''
required: false
type: string
- name: re_evaluate_policies
- name: evaluate_on_plan
in: query
description: ''
required: false
type: string
- name: evaluate_on_call
in: query
description: ''
required: false
@ -6337,10 +6342,14 @@ definitions:
title: Stage
type: string
format: uuid
re_evaluate_policies:
title: Re evaluate policies
description: When this option is enabled, the planner will re-evaluate policies
bound to this binding.
evaluate_on_plan:
title: Evaluate on plan
description: Evaluate policies during the Flow planning process. Disable this
for input-based policies.
type: boolean
evaluate_on_call:
title: Evaluate on call
description: Evaluate policies when the Stage is present to the user.
type: boolean
order:
title: Order