From 8de66b27ad6388735b3a20ed47001722a33cbcbb Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Thu, 7 May 2020 20:51:06 +0200 Subject: [PATCH] flows/*: Initial flows stage1 implementation --- passbook/flows/__init__.py | 0 passbook/flows/apps.py | 11 ++ passbook/flows/migrations/0001_initial.py | 108 +++++++++++++++++++ passbook/flows/migrations/__init__.py | 0 passbook/flows/models.py | 71 ++++++++++++ passbook/flows/urls.py | 2 + passbook/policies/apps.py | 10 ++ passbook/policies/migrations/0001_initial.py | 77 +++++++++++++ passbook/policies/migrations/__init__.py | 0 passbook/policies/models.py | 31 ++++++ passbook/root/settings.py | 2 + passbook/root/urls.py | 6 +- 12 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 passbook/flows/__init__.py create mode 100644 passbook/flows/apps.py create mode 100644 passbook/flows/migrations/0001_initial.py create mode 100644 passbook/flows/migrations/__init__.py create mode 100644 passbook/flows/models.py create mode 100644 passbook/flows/urls.py create mode 100644 passbook/policies/apps.py create mode 100644 passbook/policies/migrations/0001_initial.py create mode 100644 passbook/policies/migrations/__init__.py create mode 100644 passbook/policies/models.py diff --git a/passbook/flows/__init__.py b/passbook/flows/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/flows/apps.py b/passbook/flows/apps.py new file mode 100644 index 000000000..11f2d21d4 --- /dev/null +++ b/passbook/flows/apps.py @@ -0,0 +1,11 @@ +"""passbook flows app config""" +from django.apps import AppConfig + + +class PassbookFlowsConfig(AppConfig): + """passbook flows app config""" + + name = "passbook.flows" + label = "passbook_flows" + mountpoint = "flows/" + verbose_name = "passbook Flows" diff --git a/passbook/flows/migrations/0001_initial.py b/passbook/flows/migrations/0001_initial.py new file mode 100644 index 000000000..e07d2ce27 --- /dev/null +++ b/passbook/flows/migrations/0001_initial.py @@ -0,0 +1,108 @@ +# Generated by Django 3.0.3 on 2020-05-07 18:35 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("passbook_policies", "0001_initial"), + ("passbook_core", "0011_auto_20200222_1822"), + ] + + operations = [ + migrations.CreateModel( + name="Flow", + fields=[ + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("name", models.TextField()), + ("slug", models.SlugField(unique=True)), + ( + "designation", + models.CharField( + choices=[ + ("AUTHENTICATION", "authentication"), + ("ENROLLMENT", "enrollment"), + ("RECOVERY", "recovery"), + ], + max_length=100, + ), + ), + ( + "pbm", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + related_name="+", + to="passbook_policies.PolicyBindingModel", + ), + ), + ], + options={"verbose_name": "Flow", "verbose_name_plural": "Flows",}, + bases=("passbook_policies.policybindingmodel", models.Model), + ), + migrations.CreateModel( + name="FlowFactorBinding", + fields=[ + ( + "policybindingmodel_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + to="passbook_policies.PolicyBindingModel", + ), + ), + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("order", models.IntegerField()), + ( + "factor", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="passbook_core.Factor", + ), + ), + ( + "flow", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="passbook_flows.Flow", + ), + ), + ], + options={ + "verbose_name": "Flow Factor Binding", + "verbose_name_plural": "Flow Factor Bindings", + "unique_together": {("flow", "factor", "order")}, + }, + bases=("passbook_policies.policybindingmodel", models.Model), + ), + migrations.AddField( + model_name="flow", + name="factors", + field=models.ManyToManyField( + through="passbook_flows.FlowFactorBinding", to="passbook_core.Factor" + ), + ), + ] diff --git a/passbook/flows/migrations/__init__.py b/passbook/flows/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/flows/models.py b/passbook/flows/models.py new file mode 100644 index 000000000..a2d9a1a3a --- /dev/null +++ b/passbook/flows/models.py @@ -0,0 +1,71 @@ +"""Flow models""" +from enum import Enum +from typing import Tuple + +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from passbook.core.models import Factor +from passbook.lib.models import UUIDModel +from passbook.policies.models import PolicyBindingModel + + +class FlowDesignation(Enum): + """Designation of what a Flow should be used for. At a later point, this + should be replaced by a database entry.""" + + AUTHENTICATION = "authentication" + ENROLLMENT = "enrollment" + RECOVERY = "recovery" + + @staticmethod + def as_choices() -> Tuple[Tuple[str, str]]: + """Generate choices of actions used for database""" + return tuple( + (x, y.value) for x, y in getattr(FlowDesignation, "__members__").items() + ) + + +class Flow(PolicyBindingModel, UUIDModel): + """Flow describes how a series of Factors should be executed to authenticate/enroll/recover + a user. Additionally, policies can be applied, to specify which users + have access to this flow.""" + + name = models.TextField() + slug = models.SlugField(unique=True) + + designation = models.CharField(max_length=100, choices=FlowDesignation.as_choices()) + + factors = models.ManyToManyField(Factor, through="FlowFactorBinding") + + pbm = models.OneToOneField( + PolicyBindingModel, parent_link=True, on_delete=models.CASCADE, related_name="+" + ) + + def __str__(self) -> str: + return f"Flow {self.name} ({self.slug})" + + class Meta: + + verbose_name = _("Flow") + verbose_name_plural = _("Flows") + + +class FlowFactorBinding(PolicyBindingModel, UUIDModel): + """Relationship between Flow and Factor. Order is required and unique for + each flow-factor Binding. Additionally, policies can be specified, which determine if + this Binding applies to the current user""" + + flow = models.ForeignKey("Flow", on_delete=models.CASCADE) + factor = models.ForeignKey(Factor, on_delete=models.CASCADE) + + order = models.IntegerField() + + def __str__(self) -> str: + return f"Flow Factor Binding {self.flow} -> {self.factor}" + + class Meta: + + verbose_name = _("Flow Factor Binding") + verbose_name_plural = _("Flow Factor Bindings") + unique_together = (("flow", "factor", "order"),) diff --git a/passbook/flows/urls.py b/passbook/flows/urls.py new file mode 100644 index 000000000..a55895878 --- /dev/null +++ b/passbook/flows/urls.py @@ -0,0 +1,2 @@ +"""flow urls""" +urlpatterns = [] diff --git a/passbook/policies/apps.py b/passbook/policies/apps.py new file mode 100644 index 000000000..5795355b6 --- /dev/null +++ b/passbook/policies/apps.py @@ -0,0 +1,10 @@ +"""passbook policies app config""" +from django.apps import AppConfig + + +class PassbookPoliciesConfig(AppConfig): + """passbook policies app config""" + + name = "passbook.policies" + label = "passbook_policies" + verbose_name = "passbook Policies" diff --git a/passbook/policies/migrations/0001_initial.py b/passbook/policies/migrations/0001_initial.py new file mode 100644 index 000000000..bc0f6b7ef --- /dev/null +++ b/passbook/policies/migrations/0001_initial.py @@ -0,0 +1,77 @@ +# Generated by Django 3.0.3 on 2020-05-07 18:35 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("passbook_core", "0011_auto_20200222_1822"), + ] + + operations = [ + migrations.CreateModel( + name="PolicyBinding", + fields=[ + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("enabled", models.BooleanField(default=True)), + ("order", models.IntegerField(default=0)), + ( + "policy", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="passbook_core.Policy", + ), + ), + ], + options={ + "verbose_name": "Policy Binding", + "verbose_name_plural": "Policy Bindings", + }, + ), + migrations.CreateModel( + name="PolicyBindingModel", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "policies", + models.ManyToManyField( + related_name="_policybindingmodel_policies_+", + through="passbook_policies.PolicyBinding", + to="passbook_core.Policy", + ), + ), + ], + ), + migrations.AddField( + model_name="policybinding", + name="target", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="passbook_policies.PolicyBindingModel", + ), + ), + ] diff --git a/passbook/policies/migrations/__init__.py b/passbook/policies/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/policies/models.py b/passbook/policies/models.py new file mode 100644 index 000000000..1fae3ab4b --- /dev/null +++ b/passbook/policies/models.py @@ -0,0 +1,31 @@ +"""Policy base models""" +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from passbook.core.models import Policy +from passbook.lib.models import UUIDModel + + +class PolicyBindingModel(models.Model): + """Base Model for objects which have Policies applied to them""" + + policies = models.ManyToManyField(Policy, through="PolicyBinding", related_name="+") + + +class PolicyBinding(UUIDModel): + """Relationship between a Policy and a PolicyBindingModel.""" + + enabled = models.BooleanField(default=True) + + policy = models.ForeignKey(Policy, on_delete=models.CASCADE, related_name="+") + target = models.ForeignKey( + PolicyBindingModel, on_delete=models.CASCADE, related_name="+" + ) + + # default value and non-unique for compatibility + order = models.IntegerField(default=0) + + class Meta: + + verbose_name = _("Policy Binding") + verbose_name_plural = _("Policy Bindings") diff --git a/passbook/root/settings.py b/passbook/root/settings.py index 3f4ad769a..9b22a0174 100644 --- a/passbook/root/settings.py +++ b/passbook/root/settings.py @@ -83,6 +83,8 @@ INSTALLED_APPS = [ "passbook.admin.apps.PassbookAdminConfig", "passbook.api.apps.PassbookAPIConfig", "passbook.lib.apps.PassbookLibConfig", + "passbook.flows.apps.PassbookFlowsConfig", + "passbook.policies.apps.PassbookPoliciesConfig", "passbook.audit.apps.PassbookAuditConfig", "passbook.crypto.apps.PassbookCryptoConfig", "passbook.recovery.apps.PassbookRecoveryConfig", diff --git a/passbook/root/urls.py b/passbook/root/urls.py index b9741a118..b7e44d7cd 100644 --- a/passbook/root/urls.py +++ b/passbook/root/urls.py @@ -30,7 +30,11 @@ for _passbook_app in get_apps(): ), ) urlpatterns.append(_path) - LOGGER.debug("Mounted URLs", app_name=_passbook_app.name) + LOGGER.debug( + "Mounted URLs", + app_name=_passbook_app.name, + mountpoint=_passbook_app.mountpoint, + ) urlpatterns += [ # Administration