stages/prompt: remove PolicyBindingModel from PromptStage *breaking*

This resolves issues caused by the multiple primary keys, but also requires re-creation of the model.
This commit is contained in:
Jens Langhammer 2020-09-09 17:16:43 +02:00
parent 8db60b3e83
commit 1776b72356
11 changed files with 64 additions and 191 deletions

View File

@ -16,8 +16,6 @@ def create_default_password_change(apps: Apps, schema_editor: BaseDatabaseSchema
Flow = apps.get_model("passbook_flows", "Flow") Flow = apps.get_model("passbook_flows", "Flow")
FlowStageBinding = apps.get_model("passbook_flows", "FlowStageBinding") FlowStageBinding = apps.get_model("passbook_flows", "FlowStageBinding")
PolicyBinding = apps.get_model("passbook_policies", "PolicyBinding")
ExpressionPolicy = apps.get_model( ExpressionPolicy = apps.get_model(
"passbook_policies_expression", "ExpressionPolicy" "passbook_policies_expression", "ExpressionPolicy"
) )
@ -58,17 +56,17 @@ def create_default_password_change(apps: Apps, schema_editor: BaseDatabaseSchema
"order": 1, "order": 1,
}, },
) )
prompt_stage.fields.add(password_prompt)
prompt_stage.fields.add(password_rep_prompt)
# Policy to only trigger prompt when no username is given # Policy to only trigger prompt when no username is given
prompt_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create( prompt_policy, _ = ExpressionPolicy.objects.using(db_alias).update_or_create(
name="default-password-change-password-equal", name="default-password-change-password-equal",
defaults={"expression": PROMPT_POLICY_EXPRESSION}, defaults={"expression": PROMPT_POLICY_EXPRESSION},
) )
PolicyBinding.objects.using(db_alias).update_or_create(
policy=prompt_policy, target=prompt_stage, defaults={"order": 0} prompt_stage.fields.add(password_prompt)
) prompt_stage.fields.add(password_rep_prompt)
prompt_stage.validation_policies.add(prompt_policy)
prompt_stage.save()
user_write, _ = UserWriteStage.objects.using(db_alias).update_or_create( user_write, _ = UserWriteStage.objects.using(db_alias).update_or_create(
name="default-password-change-write" name="default-password-change-write"
@ -103,9 +101,8 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
("passbook_flows", "0006_auto_20200629_0857"), ("passbook_flows", "0006_auto_20200629_0857"),
("passbook_policies_expression", "0001_initial"), ("passbook_policies_expression", "0001_initial"),
("passbook_policies", "0001_initial"),
("passbook_stages_password", "0001_initial"), ("passbook_stages_password", "0001_initial"),
("passbook_stages_prompt", "0004_auto_20200618_1735"), ("passbook_stages_prompt", "0001_initial"),
("passbook_stages_user_write", "0001_initial"), ("passbook_stages_user_write", "0001_initial"),
] ]

View File

@ -18,6 +18,7 @@ class PromptStageSerializer(ModelSerializer):
"pk", "pk",
"name", "name",
"fields", "fields",
"validation_policies",
] ]

View File

@ -1,14 +1,17 @@
"""Prompt forms""" """Prompt forms"""
from typing import Callable from email.policy import Policy
from typing import Callable, Iterator, List
from django import forms from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple from django.contrib.admin.widgets import FilteredSelectMultiple
from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from guardian.shortcuts import get_anonymous_user from guardian.shortcuts import get_anonymous_user
from passbook.core.models import User from passbook.core.models import User
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from passbook.policies.engine import PolicyEngine from passbook.policies.engine import PolicyEngine
from passbook.policies.models import PolicyBinding, PolicyBindingModel
from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage
@ -18,7 +21,7 @@ class PromptStageForm(forms.ModelForm):
class Meta: class Meta:
model = PromptStage model = PromptStage
fields = ["name", "fields"] fields = ["name", "fields", "validation_policies"]
widgets = { widgets = {
"name": forms.TextInput(), "name": forms.TextInput(),
"fields": FilteredSelectMultiple(_("prompts"), False), "fields": FilteredSelectMultiple(_("prompts"), False),
@ -45,6 +48,23 @@ class PromptAdminForm(forms.ModelForm):
} }
class ListPolicyEngine(PolicyEngine):
"""Slightly modified policy engine, which uses a list instead of a PolicyBindingModel"""
__list: List[Policy]
def __init__(
self, policies: List[Policy], user: User, request: HttpRequest = None
) -> None:
super().__init__(PolicyBindingModel(), user, request)
self.__list = policies
self.use_cache = False
def _iter_bindings(self) -> Iterator[PolicyBinding]:
for policy in self.__list:
yield PolicyBinding(policy=policy,)
class PromptForm(forms.Form): class PromptForm(forms.Form):
"""Dynamically created form based on PromptStage""" """Dynamically created form based on PromptStage"""
@ -73,7 +93,7 @@ class PromptForm(forms.Form):
def clean(self): def clean(self):
cleaned_data = super().clean() cleaned_data = super().clean()
user = self.plan.context.get(PLAN_CONTEXT_PENDING_USER, get_anonymous_user()) user = self.plan.context.get(PLAN_CONTEXT_PENDING_USER, get_anonymous_user())
engine = PolicyEngine(self.stage, user) engine = ListPolicyEngine(self.stage.validation_policies.all(), user)
engine.request.context = cleaned_data engine.request.context = cleaned_data
engine.build() engine.build()
result = engine.result result = engine.result

View File

@ -1,4 +1,4 @@
# Generated by Django 3.0.6 on 2020-05-19 22:08 # Generated by Django 3.1.1 on 2020-09-09 08:40
import uuid import uuid
@ -11,8 +11,8 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
("passbook_policies", "0001_initial"), ("passbook_flows", "0007_auto_20200703_2059"),
("passbook_flows", "0001_initial"), ("passbook_policies", "0003_auto_20200908_1542"),
] ]
operations = [ operations = [
@ -39,17 +39,30 @@ class Migration(migrations.Migration):
"type", "type",
models.CharField( models.CharField(
choices=[ choices=[
("text", "Text"), ("text", "Text: Simple Text input"),
("e-mail", "Email"), (
"username",
"Username: Same as Text input, but checks for and prevents duplicate usernames.",
),
("email", "Email: Text field with Email type."),
("password", "Password"), ("password", "Password"),
("number", "Number"), ("number", "Number"),
("hidden", "Hidden"), ("checkbox", "Checkbox"),
("data", "Date"),
("data-time", "Date Time"),
("separator", "Separator: Static Separator Line"),
(
"hidden",
"Hidden: Hidden field, can be used to insert data into form.",
),
("static", "Static: Static value, displayed as-is."),
], ],
max_length=100, max_length=100,
), ),
), ),
("required", models.BooleanField(default=True)), ("required", models.BooleanField(default=True)),
("placeholder", models.TextField()), ("placeholder", models.TextField(blank=True)),
("order", models.IntegerField(default=0)),
], ],
options={"verbose_name": "Prompt", "verbose_name_plural": "Prompts",}, options={"verbose_name": "Prompt", "verbose_name_plural": "Prompts",},
), ),
@ -58,30 +71,25 @@ class Migration(migrations.Migration):
fields=[ fields=[
( (
"stage_ptr", "stage_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
to="passbook_flows.Stage",
),
),
(
"policybindingmodel_ptr",
models.OneToOneField( models.OneToOneField(
auto_created=True, auto_created=True,
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
parent_link=True, parent_link=True,
primary_key=True, primary_key=True,
serialize=False, serialize=False,
to="passbook_policies.PolicyBindingModel", to="passbook_flows.stage",
), ),
), ),
("fields", models.ManyToManyField(to="passbook_stages_prompt.Prompt")), ("fields", models.ManyToManyField(to="passbook_stages_prompt.Prompt")),
(
"validation_policies",
models.ManyToManyField(blank=True, to="passbook_policies.Policy"),
),
], ],
options={ options={
"verbose_name": "Prompt Stage", "verbose_name": "Prompt Stage",
"verbose_name_plural": "Prompt Stages", "verbose_name_plural": "Prompt Stages",
}, },
bases=("passbook_policies.policybindingmodel", "passbook_flows.stage"), bases=("passbook_flows.stage",),
), ),
] ]

View File

@ -1,35 +0,0 @@
# Generated by Django 3.0.6 on 2020-05-28 20:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_stages_prompt", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="prompt", name="order", field=models.IntegerField(default=0),
),
migrations.AlterField(
model_name="prompt",
name="type",
field=models.CharField(
choices=[
("text", "Text"),
("e-mail", "Email"),
("password", "Password"),
("number", "Number"),
("checkbox", "Checkbox"),
("data", "Date"),
("data-time", "Date Time"),
("separator", "Separator"),
("hidden", "Hidden"),
("static", "Static"),
],
max_length=100,
),
),
]

View File

@ -1,33 +0,0 @@
# Generated by Django 3.0.7 on 2020-06-15 16:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_stages_prompt", "0002_auto_20200528_2059"),
]
operations = [
migrations.AlterField(
model_name="prompt",
name="type",
field=models.CharField(
choices=[
("text", "Text"),
("username", "Username"),
("e-mail", "Email"),
("password", "Password"),
("number", "Number"),
("checkbox", "Checkbox"),
("data", "Date"),
("data-time", "Date Time"),
("separator", "Separator"),
("hidden", "Hidden"),
("static", "Static"),
],
max_length=100,
),
),
]

View File

@ -1,33 +0,0 @@
# Generated by Django 3.0.7 on 2020-06-18 17:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_stages_prompt", "0003_auto_20200615_1641"),
]
operations = [
migrations.AlterField(
model_name="prompt",
name="type",
field=models.CharField(
choices=[
("text", "Text"),
("username", "Username"),
("email", "Email"),
("password", "Password"),
("number", "Number"),
("checkbox", "Checkbox"),
("data", "Date"),
("data-time", "Date Time"),
("separator", "Separator"),
("hidden", "Hidden"),
("static", "Static"),
],
max_length=100,
),
),
]

View File

@ -1,39 +0,0 @@
# Generated by Django 3.0.8 on 2020-07-09 16:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_stages_prompt", "0004_auto_20200618_1735"),
]
operations = [
migrations.AlterField(
model_name="prompt",
name="type",
field=models.CharField(
choices=[
("text", "Text: Simple Text input"),
(
"username",
"Username: Same as Text input, but checks for and prevents duplicate usernames.",
),
("email", "Email: Text field with Email type."),
("password", "Password"),
("number", "Number"),
("checkbox", "Checkbox"),
("data", "Date"),
("data-time", "Date Time"),
("separator", "Separator: Static Separator Line"),
(
"hidden",
"Hidden: Hidden field, can be used to insert data into form.",
),
("static", "Static: Static value, displayed as-is."),
],
max_length=100,
),
),
]

View File

@ -1,16 +0,0 @@
# Generated by Django 3.1 on 2020-08-23 22:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_stages_prompt", "0005_auto_20200709_1608"),
]
operations = [
migrations.AlterField(
model_name="prompt", name="placeholder", field=models.TextField(blank=True),
),
]

View File

@ -11,7 +11,7 @@ from rest_framework.serializers import BaseSerializer
from passbook.flows.models import Stage from passbook.flows.models import Stage
from passbook.lib.models import SerializerModel from passbook.lib.models import SerializerModel
from passbook.policies.models import PolicyBindingModel from passbook.policies.models import Policy
from passbook.stages.prompt.widgets import HorizontalRuleWidget, StaticTextWidget from passbook.stages.prompt.widgets import HorizontalRuleWidget, StaticTextWidget
@ -123,11 +123,13 @@ class Prompt(SerializerModel):
verbose_name_plural = _("Prompts") verbose_name_plural = _("Prompts")
class PromptStage(PolicyBindingModel, Stage): class PromptStage(Stage):
"""Define arbitrary prompts for the user.""" """Define arbitrary prompts for the user."""
fields = models.ManyToManyField(Prompt) fields = models.ManyToManyField(Prompt)
validation_policies = models.ManyToManyField(Policy, blank=True)
@property @property
def serializer(self) -> BaseSerializer: def serializer(self) -> BaseSerializer:
from passbook.stages.prompt.api import PromptStageSerializer from passbook.stages.prompt.api import PromptStageSerializer

View File

@ -11,7 +11,6 @@ from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
from passbook.flows.planner import FlowPlan from passbook.flows.planner import FlowPlan
from passbook.flows.views import SESSION_KEY_PLAN from passbook.flows.views import SESSION_KEY_PLAN
from passbook.policies.expression.models import ExpressionPolicy from passbook.policies.expression.models import ExpressionPolicy
from passbook.policies.models import PolicyBinding
from passbook.stages.prompt.forms import PromptForm from passbook.stages.prompt.forms import PromptForm
from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage
from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT
@ -124,7 +123,8 @@ class TestPromptStage(TestCase):
expr_policy = ExpressionPolicy.objects.create( expr_policy = ExpressionPolicy.objects.create(
name="validate-form", expression=expr name="validate-form", expression=expr
) )
PolicyBinding.objects.create(policy=expr_policy, target=self.stage, order=0) self.stage.validation_policies.set([expr_policy])
self.stage.save()
form = PromptForm(stage=self.stage, plan=plan, data=self.prompt_data) form = PromptForm(stage=self.stage, plan=plan, data=self.prompt_data)
self.assertEqual(form.is_valid(), True) self.assertEqual(form.is_valid(), True)
return form return form
@ -138,7 +138,8 @@ class TestPromptStage(TestCase):
expr_policy = ExpressionPolicy.objects.create( expr_policy = ExpressionPolicy.objects.create(
name="validate-form", expression=expr name="validate-form", expression=expr
) )
PolicyBinding.objects.create(policy=expr_policy, target=self.stage, order=0) self.stage.validation_policies.set([expr_policy])
self.stage.save()
form = PromptForm(stage=self.stage, plan=plan, data=self.prompt_data) form = PromptForm(stage=self.stage, plan=plan, data=self.prompt_data)
self.assertEqual(form.is_valid(), False) self.assertEqual(form.is_valid(), False)
return form return form