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", "model": "passbook_flows.flowstagebinding",
"attrs": { "attrs": {
"re_evaluate_policies": false "evaluate_on_call_policies": false
} }
}, },
{ {
@ -149,7 +149,7 @@
}, },
"model": "passbook_flows.flowstagebinding", "model": "passbook_flows.flowstagebinding",
"attrs": { "attrs": {
"re_evaluate_policies": false "evaluate_on_call_policies": false
} }
}, },
{ {
@ -161,7 +161,7 @@
}, },
"model": "passbook_flows.flowstagebinding", "model": "passbook_flows.flowstagebinding",
"attrs": { "attrs": {
"re_evaluate_policies": false "evaluate_on_call_policies": false
} }
}, },
{ {
@ -173,7 +173,7 @@
}, },
"model": "passbook_flows.flowstagebinding", "model": "passbook_flows.flowstagebinding",
"attrs": { "attrs": {
"re_evaluate_policies": false "evaluate_on_call_policies": false
} }
} }
] ]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,7 +27,15 @@ class FlowStageBindingSerializer(ModelSerializer):
class Meta: class Meta:
model = FlowStageBinding 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): class FlowStageBindingViewSet(ModelViewSet):

View File

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

View File

@ -2,6 +2,7 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from django.http.request import HttpRequest
from structlog import get_logger from structlog import get_logger
from passbook.core.models import User from passbook.core.models import User
@ -20,7 +21,9 @@ class StageMarker:
"""Base stage marker class, no extra attributes, and has no special handler.""" """Base stage marker class, no extra attributes, and has no special handler."""
# pylint: disable=unused-argument # 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. """Process callback for this marker. This should be overridden by sub-classes.
If a stage should be removed, return None.""" If a stage should be removed, return None."""
return stage return stage
@ -33,10 +36,14 @@ class ReevaluateMarker(StageMarker):
binding: PolicyBinding binding: PolicyBinding
user: User 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""" """Re-evaluate policies bound to stage, and if they fail, remove from plan"""
engine = PolicyEngine(self.binding, self.user) engine = PolicyEngine(self.binding, self.user)
engine.use_cache = False engine.use_cache = False
if http_request:
engine.request.http_request = http_request
engine.request.context = plan.context engine.request.context = plan.context
engine.build() engine.build()
result = engine.result 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) target = models.ForeignKey("Flow", on_delete=models.CASCADE)
stage = InheritanceForeignKey(Stage, on_delete=models.CASCADE) stage = InheritanceForeignKey(Stage, on_delete=models.CASCADE)
re_evaluate_policies = models.BooleanField( evaluate_on_plan = models.BooleanField(
default=False, default=True,
help_text=_( help_text=_(
( (
"When this option is enabled, the planner will re-evaluate " "Evaluate policies during the Flow planning process. "
"policies bound to this binding." "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() order = models.IntegerField()

View File

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

View File

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

View File

@ -174,7 +174,7 @@ class TestFlowExecutor(TestCase):
target=flow, target=flow,
stage=DummyStage.objects.create(name="dummy2"), stage=DummyStage.objects.create(name="dummy2"),
order=1, order=1,
re_evaluate_policies=True, evaluate_on_call=True,
) )
PolicyBinding.objects.create(policy=false_policy, target=binding2, order=0) PolicyBinding.objects.create(policy=false_policy, target=binding2, order=0)
@ -225,7 +225,7 @@ class TestFlowExecutor(TestCase):
target=flow, target=flow,
stage=DummyStage.objects.create(name="dummy2"), stage=DummyStage.objects.create(name="dummy2"),
order=1, order=1,
re_evaluate_policies=True, evaluate_on_call=True,
) )
binding3 = FlowStageBinding.objects.create( binding3 = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy3"), order=2 target=flow, stage=DummyStage.objects.create(name="dummy3"), order=2
@ -292,13 +292,13 @@ class TestFlowExecutor(TestCase):
target=flow, target=flow,
stage=DummyStage.objects.create(name="dummy2"), stage=DummyStage.objects.create(name="dummy2"),
order=1, order=1,
re_evaluate_policies=True, evaluate_on_call=True,
) )
binding3 = FlowStageBinding.objects.create( binding3 = FlowStageBinding.objects.create(
target=flow, target=flow,
stage=DummyStage.objects.create(name="dummy3"), stage=DummyStage.objects.create(name="dummy3"),
order=2, order=2,
re_evaluate_policies=True, evaluate_on_call=True,
) )
binding4 = FlowStageBinding.objects.create( binding4 = FlowStageBinding.objects.create(
target=flow, stage=DummyStage.objects.create(name="dummy4"), order=2 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)) return to_stage_response(self.request, self.handle_invalid_flow(exc))
# 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
next_stage = self.plan.next() next_stage = self.plan.next(self.request)
if not next_stage: if not next_stage:
LOGGER.debug("f(exec): no more stages, flow is done.") LOGGER.debug("f(exec): no more stages, flow is done.")
return self._flow_done() return self._flow_done()

View File

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