diff --git a/passbook/admin/views/overview.py b/passbook/admin/views/overview.py index 1b1b0a88b..0436c71f9 100644 --- a/passbook/admin/views/overview.py +++ b/passbook/admin/views/overview.py @@ -5,8 +5,9 @@ from django.views.generic import TemplateView from passbook import __version__ from passbook.admin.mixins import AdminRequiredMixin -from passbook.core.models import Application, Policy, Provider, Source, User +from passbook.core.models import Application, Provider, Source, User from passbook.flows.models import Flow, Stage +from passbook.policies.models import Policy from passbook.root.celery import CELERY_APP from passbook.stages.invitation.models import Invitation diff --git a/passbook/admin/views/policy.py b/passbook/admin/views/policy.py index 7c65c1d5c..5faa67c5f 100644 --- a/passbook/admin/views/policy.py +++ b/passbook/admin/views/policy.py @@ -13,10 +13,10 @@ from django.views.generic.detail import DetailView from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from passbook.admin.forms.policies import PolicyTestForm -from passbook.core.models import Policy from passbook.lib.utils.reflection import all_subclasses, path_to_class from passbook.lib.views import CreateAssignPermView from passbook.policies.engine import PolicyEngine +from passbook.policies.models import Policy class PolicyListView(LoginRequiredMixin, PermissionListMixin, ListView): diff --git a/passbook/api/permissions.py b/passbook/api/permissions.py index 90a7a02af..b9da70d82 100644 --- a/passbook/api/permissions.py +++ b/passbook/api/permissions.py @@ -1,8 +1,8 @@ """permission classes for django restframework""" from rest_framework.permissions import BasePermission, DjangoObjectPermissions -from passbook.core.models import PolicyModel from passbook.policies.engine import PolicyEngine +from passbook.policies.models import PolicyBindingModel class CustomObjectPermissions(DjangoObjectPermissions): @@ -24,8 +24,7 @@ class PolicyPermissions(BasePermission): policy_engine: PolicyEngine - def has_object_permission(self, request, view, obj: PolicyModel) -> bool: - # if not obj.po + def has_object_permission(self, request, view, obj: PolicyBindingModel) -> bool: self.policy_engine = PolicyEngine(obj.policies, request.user, request) self.policy_engine.request.obj = obj return self.policy_engine.build().passing diff --git a/passbook/api/v2/urls.py b/passbook/api/v2/urls.py index 9e5c8604c..f0150b367 100644 --- a/passbook/api/v2/urls.py +++ b/passbook/api/v2/urls.py @@ -1,5 +1,4 @@ """api v2 urls""" -from django.conf import settings from django.conf.urls import url from django.urls import path from drf_yasg import openapi @@ -19,6 +18,7 @@ from passbook.core.api.users import UserViewSet from passbook.flows.api import FlowStageBindingViewSet, FlowViewSet, StageViewSet from passbook.lib.utils.reflection import get_apps from passbook.policies.api import PolicyBindingViewSet +from passbook.policies.dummy.api import DummyPolicyViewSet from passbook.policies.expiry.api import PasswordExpiryPolicyViewSet from passbook.policies.expression.api import ExpressionPolicyViewSet from passbook.policies.hibp.api import HaveIBeenPwendPolicyViewSet @@ -31,6 +31,7 @@ from passbook.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProvider from passbook.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet from passbook.sources.oauth.api import OAuthSourceViewSet from passbook.stages.captcha.api import CaptchaStageViewSet +from passbook.stages.dummy.api import DummyStageViewSet from passbook.stages.email.api import EmailStageViewSet from passbook.stages.identification.api import IdentificationStageViewSet from passbook.stages.invitation.api import InvitationStageViewSet, InvitationViewSet @@ -97,12 +98,8 @@ router.register("stages/user_write", UserWriteStageViewSet) router.register("flows/instances", FlowViewSet) router.register("flows/bindings", FlowStageBindingViewSet) -if settings.DEBUG: - from passbook.stages.dummy.api import DummyStageViewSet - from passbook.policies.dummy.api import DummyPolicyViewSet - - router.register("stages/dummy", DummyStageViewSet) - router.register("policies/dummy", DummyPolicyViewSet) +router.register("stages/dummy", DummyStageViewSet) +router.register("policies/dummy", DummyPolicyViewSet) info = openapi.Info( title="passbook API", diff --git a/passbook/audit/tests/test_event.py b/passbook/audit/tests/test_event.py index 5e32b2d09..9272a3a35 100644 --- a/passbook/audit/tests/test_event.py +++ b/passbook/audit/tests/test_event.py @@ -5,7 +5,7 @@ from django.test import TestCase from guardian.shortcuts import get_anonymous_user from passbook.audit.models import Event, EventAction -from passbook.core.models import Policy +from passbook.policies.dummy.models import DummyPolicy class TestAuditEvent(TestCase): @@ -23,7 +23,7 @@ class TestAuditEvent(TestCase): def test_new_with_uuid_model(self): """Create a new Event passing a model (with UUID PK) as kwarg""" - temp_model = Policy.objects.create() + temp_model = DummyPolicy.objects.create(name="test", result=True) event = Event.new(EventAction.CUSTOM, model=temp_model) event.save() # We save to ensure nothing is un-saveable model_content_type = ContentType.objects.get_for_model(temp_model) diff --git a/passbook/core/api/policies.py b/passbook/core/api/policies.py index bf154d063..13d6d7ad9 100644 --- a/passbook/core/api/policies.py +++ b/passbook/core/api/policies.py @@ -2,8 +2,8 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField from rest_framework.viewsets import ReadOnlyModelViewSet -from passbook.core.models import Policy from passbook.policies.forms import GENERAL_FIELDS +from passbook.policies.models import Policy class PolicySerializer(ModelSerializer): diff --git a/passbook/core/migrations/0001_initial.py b/passbook/core/migrations/0001_initial.py index 7257e3a3e..a0bae5402 100644 --- a/passbook/core/migrations/0001_initial.py +++ b/passbook/core/migrations/0001_initial.py @@ -19,6 +19,7 @@ class Migration(migrations.Migration): dependencies = [ ("auth", "0011_update_proxy_permissions"), + ("passbook_policies", "0001_initial"), ] operations = [ @@ -158,7 +159,7 @@ class Migration(migrations.Migration): ), ( "policies", - models.ManyToManyField(blank=True, to="passbook_core.Policy"), + models.ManyToManyField(blank=True, to="passbook_policies.Policy"), ), ], options={"abstract": False,}, @@ -182,30 +183,6 @@ class Migration(migrations.Migration): "verbose_name_plural": "Property Mappings", }, ), - migrations.CreateModel( - name="DebugPolicy", - fields=[ - ( - "policy_ptr", - models.OneToOneField( - auto_created=True, - on_delete=django.db.models.deletion.CASCADE, - parent_link=True, - primary_key=True, - serialize=False, - to="passbook_core.Policy", - ), - ), - ("result", models.BooleanField(default=False)), - ("wait_min", models.IntegerField(default=5)), - ("wait_max", models.IntegerField(default=30)), - ], - options={ - "verbose_name": "Debug Policy", - "verbose_name_plural": "Debug Policies", - }, - bases=("passbook_core.policy",), - ), migrations.CreateModel( name="Factor", fields=[ @@ -217,7 +194,7 @@ class Migration(migrations.Migration): parent_link=True, primary_key=True, serialize=False, - to="passbook_core.PolicyModel", + to="passbook_policies.PolicyBindingModel", ), ), ("name", models.TextField()), @@ -226,7 +203,7 @@ class Migration(migrations.Migration): ("enabled", models.BooleanField(default=True)), ], options={"abstract": False,}, - bases=("passbook_core.policymodel",), + bases=("passbook_policies.policybindingmodel",), ), migrations.CreateModel( name="Source", @@ -239,7 +216,7 @@ class Migration(migrations.Migration): parent_link=True, primary_key=True, serialize=False, - to="passbook_core.PolicyModel", + to="passbook_policies.PolicyBindingModel", ), ), ("name", models.TextField()), @@ -247,7 +224,7 @@ class Migration(migrations.Migration): ("enabled", models.BooleanField(default=True)), ], options={"abstract": False,}, - bases=("passbook_core.policymodel",), + bases=("passbook_policies.policybindingmodel",), ), migrations.CreateModel( name="Provider", @@ -418,7 +395,7 @@ class Migration(migrations.Migration): parent_link=True, primary_key=True, serialize=False, - to="passbook_core.PolicyModel", + to="passbook_policies.PolicyBindingModel", ), ), ("name", models.TextField()), @@ -438,7 +415,7 @@ class Migration(migrations.Migration): ), ], options={"abstract": False,}, - bases=("passbook_core.policymodel",), + bases=("passbook_policies.policybindingmodel",), ), migrations.AddField( model_name="user", diff --git a/passbook/core/migrations/0013_delete_debugpolicy.py b/passbook/core/migrations/0013_delete_debugpolicy.py index 4e658f01f..88952c2fe 100644 --- a/passbook/core/migrations/0013_delete_debugpolicy.py +++ b/passbook/core/migrations/0013_delete_debugpolicy.py @@ -6,11 +6,7 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ("passbook_policies", "0003_auto_20200508_1642"), - ("passbook_stages_password", "0001_initial"), ("passbook_core", "0012_delete_factor"), ] - operations = [ - migrations.DeleteModel(name="DebugPolicy",), - ] + operations = [] diff --git a/passbook/core/migrations/0016_auto_20200516_1446.py b/passbook/core/migrations/0016_auto_20200516_1446.py new file mode 100644 index 000000000..90f911c1e --- /dev/null +++ b/passbook/core/migrations/0016_auto_20200516_1446.py @@ -0,0 +1,48 @@ +# Generated by Django 3.0.5 on 2020-05-16 14:46 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("passbook_policies", "__first__"), + ("passbook_core", "0015_auto_20200516_1407"), + ] + + operations = [ + migrations.RemoveField(model_name="policymodel", name="policies",), + migrations.RemoveField(model_name="application", name="policymodel_ptr",), + migrations.RemoveField(model_name="source", name="policymodel_ptr",), + migrations.AddField( + model_name="application", + name="policybindingmodel_ptr", + field=models.OneToOneField( + auto_created=True, + default=None, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="passbook_policies.PolicyBindingModel", + ), + preserve_default=False, + ), + migrations.AddField( + model_name="source", + name="policybindingmodel_ptr", + field=models.OneToOneField( + auto_created=True, + default=None, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="passbook_policies.PolicyBindingModel", + ), + preserve_default=False, + ), + migrations.DeleteModel(name="Policy",), + migrations.DeleteModel(name="PolicyModel",), + ] diff --git a/passbook/core/models.py b/passbook/core/models.py index 693fee338..5187c6f26 100644 --- a/passbook/core/models.py +++ b/passbook/core/models.py @@ -22,8 +22,7 @@ from passbook.core.exceptions import PropertyMappingExpressionException from passbook.core.signals import password_changed from passbook.core.types import UILoginButton, UIUserSettings from passbook.lib.models import CreatedUpdatedModel, UUIDModel -from passbook.policies.exceptions import PolicyException -from passbook.policies.types import PolicyRequest, PolicyResult +from passbook.policies.models import PolicyBindingModel LOGGER = get_logger() NATIVE_ENVIRONMENT = NativeEnvironment() @@ -94,13 +93,7 @@ class Provider(ExportModelOperationsMixin("provider"), models.Model): return super().__str__() -class PolicyModel(UUIDModel, CreatedUpdatedModel): - """Base model which can have policies applied to it""" - - policies = models.ManyToManyField("Policy", blank=True) - - -class Application(ExportModelOperationsMixin("application"), PolicyModel): +class Application(ExportModelOperationsMixin("application"), PolicyBindingModel): """Every Application which uses passbook for authentication/identification/authorization needs an Application record. Other authentication types can subclass this Model to add custom fields and other properties""" @@ -129,7 +122,7 @@ class Application(ExportModelOperationsMixin("application"), PolicyModel): return self.name -class Source(ExportModelOperationsMixin("source"), PolicyModel): +class Source(ExportModelOperationsMixin("source"), PolicyBindingModel): """Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server""" name = models.TextField(help_text=_("Source's display Name.")) @@ -176,25 +169,6 @@ class UserSourceConnection(CreatedUpdatedModel): unique_together = (("user", "source"),) -class Policy(ExportModelOperationsMixin("policy"), UUIDModel, CreatedUpdatedModel): - """Policies which specify if a user is authorized to use an Application. Can be overridden by - other types to add other fields, more logic, etc.""" - - name = models.TextField(blank=True, null=True) - negate = models.BooleanField(default=False) - order = models.IntegerField(default=0) - timeout = models.IntegerField(default=30) - - objects = InheritanceManager() - - def __str__(self): - return f"Policy {self.name}" - - def passes(self, request: PolicyRequest) -> PolicyResult: - """Check if user instance passes this policy""" - raise PolicyException() - - class Token(ExportModelOperationsMixin("token"), UUIDModel): """One-time link for password resets/sign-up-confirmations""" diff --git a/passbook/core/signals.py b/passbook/core/signals.py index d450e8bb8..c3a50b4cc 100644 --- a/passbook/core/signals.py +++ b/passbook/core/signals.py @@ -17,7 +17,7 @@ password_changed = Signal(providing_args=["user", "password"]) # pylint: disable=unused-argument def invalidate_policy_cache(sender, instance, **_): """Invalidate Policy cache when policy is updated""" - from passbook.core.models import Policy + from passbook.policies.models import Policy from passbook.policies.process import cache_key if isinstance(instance, Policy): diff --git a/passbook/flows/migrations/0001_initial.py b/passbook/flows/migrations/0001_initial.py index df7121778..5253b8b6c 100644 --- a/passbook/flows/migrations/0001_initial.py +++ b/passbook/flows/migrations/0001_initial.py @@ -11,7 +11,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ("passbook_policies", "0003_auto_20200508_1642"), + # ("passbook_policies", "0001_initial"), ] operations = [ diff --git a/passbook/policies/dummy/migrations/0001_initial.py b/passbook/policies/dummy/migrations/0001_initial.py index 14e77000e..bc6b920a1 100644 --- a/passbook/policies/dummy/migrations/0001_initial.py +++ b/passbook/policies/dummy/migrations/0001_initial.py @@ -9,8 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ("passbook_policies", "0003_auto_20200508_1642"), - ("passbook_core", "0013_delete_debugpolicy"), + ("passbook_policies", "0001_initial"), ] operations = [ @@ -25,7 +24,7 @@ class Migration(migrations.Migration): parent_link=True, primary_key=True, serialize=False, - to="passbook_core.Policy", + to="passbook_policies.Policy", ), ), ("result", models.BooleanField(default=False)), @@ -36,6 +35,6 @@ class Migration(migrations.Migration): "verbose_name": "Dummy Policy", "verbose_name_plural": "Dummy Policies", }, - bases=("passbook_core.policy",), + bases=("passbook_policies.policy",), ), ] diff --git a/passbook/policies/dummy/models.py b/passbook/policies/dummy/models.py index 98d9e22f5..5e7ca8c8b 100644 --- a/passbook/policies/dummy/models.py +++ b/passbook/policies/dummy/models.py @@ -6,7 +6,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from structlog import get_logger -from passbook.core.models import Policy +from passbook.policies.models import Policy from passbook.policies.types import PolicyRequest, PolicyResult LOGGER = get_logger() diff --git a/passbook/policies/engine.py b/passbook/policies/engine.py index 62455b0f0..24aaae59f 100644 --- a/passbook/policies/engine.py +++ b/passbook/policies/engine.py @@ -7,7 +7,8 @@ from django.core.cache import cache from django.http import HttpRequest from structlog import get_logger -from passbook.core.models import Policy, User +from passbook.core.models import User +from passbook.policies.models import Policy from passbook.policies.process import PolicyProcess, cache_key from passbook.policies.types import PolicyRequest, PolicyResult diff --git a/passbook/policies/expiry/migrations/0001_initial.py b/passbook/policies/expiry/migrations/0001_initial.py index a9bf5142b..79cdfdcb5 100644 --- a/passbook/policies/expiry/migrations/0001_initial.py +++ b/passbook/policies/expiry/migrations/0001_initial.py @@ -9,7 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ("passbook_core", "0001_initial"), + ("passbook_policies", "0001_initial"), ] operations = [ @@ -24,7 +24,7 @@ class Migration(migrations.Migration): parent_link=True, primary_key=True, serialize=False, - to="passbook_core.Policy", + to="passbook_policies.Policy", ), ), ("deny_only", models.BooleanField(default=False)), @@ -34,6 +34,6 @@ class Migration(migrations.Migration): "verbose_name": "Password Expiry Policy", "verbose_name_plural": "Password Expiry Policies", }, - bases=("passbook_core.policy",), + bases=("passbook_policies.policy",), ), ] diff --git a/passbook/policies/expiry/models.py b/passbook/policies/expiry/models.py index 102360dad..34565188b 100644 --- a/passbook/policies/expiry/models.py +++ b/passbook/policies/expiry/models.py @@ -6,7 +6,7 @@ from django.utils.timezone import now from django.utils.translation import gettext as _ from structlog import get_logger -from passbook.core.models import Policy +from passbook.policies.models import Policy from passbook.policies.types import PolicyRequest, PolicyResult LOGGER = get_logger() diff --git a/passbook/policies/expression/migrations/0001_initial.py b/passbook/policies/expression/migrations/0001_initial.py index fa9e907b4..cdb776065 100644 --- a/passbook/policies/expression/migrations/0001_initial.py +++ b/passbook/policies/expression/migrations/0001_initial.py @@ -9,7 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ("passbook_core", "0007_auto_20200217_1934"), + ("passbook_policies", "0001_initial"), ] operations = [ @@ -24,7 +24,7 @@ class Migration(migrations.Migration): parent_link=True, primary_key=True, serialize=False, - to="passbook_core.Policy", + to="passbook_policies.Policy", ), ), ("expression", models.TextField()), @@ -33,6 +33,6 @@ class Migration(migrations.Migration): "verbose_name": "Expression Policy", "verbose_name_plural": "Expression Policies", }, - bases=("passbook_core.policy",), + bases=("passbook_policies.policy",), ), ] diff --git a/passbook/policies/expression/models.py b/passbook/policies/expression/models.py index 2ba3827a6..edf9b629b 100644 --- a/passbook/policies/expression/models.py +++ b/passbook/policies/expression/models.py @@ -2,8 +2,8 @@ from django.db import models from django.utils.translation import gettext as _ -from passbook.core.models import Policy from passbook.policies.expression.evaluator import Evaluator +from passbook.policies.models import Policy from passbook.policies.types import PolicyRequest, PolicyResult diff --git a/passbook/policies/hibp/migrations/0001_initial.py b/passbook/policies/hibp/migrations/0001_initial.py index cb3f04898..c6621100e 100644 --- a/passbook/policies/hibp/migrations/0001_initial.py +++ b/passbook/policies/hibp/migrations/0001_initial.py @@ -9,7 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ("passbook_core", "0001_initial"), + ("passbook_policies", "0001_initial"), ] operations = [ @@ -24,7 +24,7 @@ class Migration(migrations.Migration): parent_link=True, primary_key=True, serialize=False, - to="passbook_core.Policy", + to="passbook_policies.Policy", ), ), ("allowed_count", models.IntegerField(default=0)), @@ -33,6 +33,6 @@ class Migration(migrations.Migration): "verbose_name": "Have I Been Pwned Policy", "verbose_name_plural": "Have I Been Pwned Policies", }, - bases=("passbook_core.policy",), + bases=("passbook_policies.policy",), ), ] diff --git a/passbook/policies/hibp/models.py b/passbook/policies/hibp/models.py index 5ff10c838..18b395e66 100644 --- a/passbook/policies/hibp/models.py +++ b/passbook/policies/hibp/models.py @@ -6,7 +6,8 @@ from django.utils.translation import gettext as _ from requests import get from structlog import get_logger -from passbook.core.models import Policy, PolicyResult, User +from passbook.core.models import User +from passbook.policies.models import Policy, PolicyResult LOGGER = get_logger() diff --git a/passbook/policies/migrations/0001_initial.py b/passbook/policies/migrations/0001_initial.py index bc0f6b7ef..0b3e7aace 100644 --- a/passbook/policies/migrations/0001_initial.py +++ b/passbook/policies/migrations/0001_initial.py @@ -10,11 +10,28 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ("passbook_core", "0011_auto_20200222_1822"), - ] - operations = [ + migrations.CreateModel( + name="Policy", + fields=[ + ("created", models.DateTimeField(auto_now_add=True)), + ("last_updated", models.DateTimeField(auto_now=True)), + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("name", models.TextField(blank=True, null=True)), + ("negate", models.BooleanField(default=False)), + ("order", models.IntegerField(default=0)), + ("timeout", models.IntegerField(default=30)), + ], + options={"abstract": False,}, + ), migrations.CreateModel( name="PolicyBinding", fields=[ @@ -34,7 +51,7 @@ class Migration(migrations.Migration): models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="+", - to="passbook_core.Policy", + to="passbook_policies.Policy", ), ), ], @@ -60,7 +77,7 @@ class Migration(migrations.Migration): models.ManyToManyField( related_name="_policybindingmodel_policies_+", through="passbook_policies.PolicyBinding", - to="passbook_core.Policy", + to="passbook_policies.Policy", ), ), ], diff --git a/passbook/policies/migrations/0003_auto_20200508_1642.py b/passbook/policies/migrations/0003_auto_20200516_1516.py similarity index 78% rename from passbook/policies/migrations/0003_auto_20200508_1642.py rename to passbook/policies/migrations/0003_auto_20200516_1516.py index a9e6128ba..5238ef06f 100644 --- a/passbook/policies/migrations/0003_auto_20200508_1642.py +++ b/passbook/policies/migrations/0003_auto_20200516_1516.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.3 on 2020-05-08 16:42 +# Generated by Django 3.0.5 on 2020-05-16 15:16 from django.db import migrations, models @@ -6,7 +6,6 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ("passbook_core", "0011_auto_20200222_1822"), ("passbook_policies", "0002_auto_20200508_1230"), ] @@ -18,7 +17,7 @@ class Migration(migrations.Migration): blank=True, related_name="_policybindingmodel_policies_+", through="passbook_policies.PolicyBinding", - to="passbook_core.Policy", + to="passbook_policies.Policy", ), ), ] diff --git a/passbook/policies/models.py b/passbook/policies/models.py index e2c35786b..ec61e4119 100644 --- a/passbook/policies/models.py +++ b/passbook/policies/models.py @@ -1,16 +1,18 @@ """Policy base models""" from django.db import models from django.utils.translation import gettext_lazy as _ +from model_utils.managers import InheritanceManager -from passbook.core.models import Policy -from passbook.lib.models import UUIDModel +from passbook.lib.models import CreatedUpdatedModel, UUIDModel +from passbook.policies.exceptions import PolicyException +from passbook.policies.types import PolicyRequest, PolicyResult class PolicyBindingModel(models.Model): """Base Model for objects that have policies applied to them.""" policies = models.ManyToManyField( - Policy, through="PolicyBinding", related_name="+", blank=True + "Policy", through="PolicyBinding", related_name="+", blank=True ) class Meta: @@ -24,7 +26,7 @@ class PolicyBinding(UUIDModel): enabled = models.BooleanField(default=True) - policy = models.ForeignKey(Policy, on_delete=models.CASCADE, related_name="+") + policy = models.ForeignKey("Policy", on_delete=models.CASCADE, related_name="+") target = models.ForeignKey( PolicyBindingModel, on_delete=models.CASCADE, related_name="+" ) @@ -39,3 +41,22 @@ class PolicyBinding(UUIDModel): verbose_name = _("Policy Binding") verbose_name_plural = _("Policy Bindings") + + +class Policy(UUIDModel, CreatedUpdatedModel): + """Policies which specify if a user is authorized to use an Application. Can be overridden by + other types to add other fields, more logic, etc.""" + + name = models.TextField(blank=True, null=True) + negate = models.BooleanField(default=False) + order = models.IntegerField(default=0) + timeout = models.IntegerField(default=30) + + objects = InheritanceManager() + + def __str__(self): + return f"Policy {self.name}" + + def passes(self, request: PolicyRequest) -> PolicyResult: + """Check if user instance passes this policy""" + raise PolicyException() diff --git a/passbook/policies/password/migrations/0001_initial.py b/passbook/policies/password/migrations/0001_initial.py index ef73a1871..c7563735a 100644 --- a/passbook/policies/password/migrations/0001_initial.py +++ b/passbook/policies/password/migrations/0001_initial.py @@ -9,7 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ("passbook_core", "0001_initial"), + ("passbook_policies", "0001_initial"), ] operations = [ @@ -24,7 +24,7 @@ class Migration(migrations.Migration): parent_link=True, primary_key=True, serialize=False, - to="passbook_core.Policy", + to="passbook_policies.Policy", ), ), ("amount_uppercase", models.IntegerField(default=0)), @@ -41,6 +41,6 @@ class Migration(migrations.Migration): "verbose_name": "Password Policy", "verbose_name_plural": "Password Policies", }, - bases=("passbook_core.policy",), + bases=("passbook_policies.policy",), ), ] diff --git a/passbook/policies/password/models.py b/passbook/policies/password/models.py index 03b552a21..4f14da98d 100644 --- a/passbook/policies/password/models.py +++ b/passbook/policies/password/models.py @@ -5,7 +5,7 @@ from django.db import models from django.utils.translation import gettext as _ from structlog import get_logger -from passbook.core.models import Policy +from passbook.policies.models import Policy from passbook.policies.types import PolicyRequest, PolicyResult LOGGER = get_logger() diff --git a/passbook/policies/process.py b/passbook/policies/process.py index 64ccf4962..f6c2a1265 100644 --- a/passbook/policies/process.py +++ b/passbook/policies/process.py @@ -6,8 +6,9 @@ from typing import Optional from django.core.cache import cache from structlog import get_logger -from passbook.core.models import Policy, User +from passbook.core.models import User from passbook.policies.exceptions import PolicyException +from passbook.policies.models import Policy from passbook.policies.types import PolicyRequest, PolicyResult LOGGER = get_logger() diff --git a/passbook/policies/reputation/migrations/0001_initial.py b/passbook/policies/reputation/migrations/0001_initial.py index 7b9dd1c2e..0f6426329 100644 --- a/passbook/policies/reputation/migrations/0001_initial.py +++ b/passbook/policies/reputation/migrations/0001_initial.py @@ -10,7 +10,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ("passbook_core", "0001_initial"), + ("passbook_policies", "0001_initial"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] @@ -43,7 +43,7 @@ class Migration(migrations.Migration): parent_link=True, primary_key=True, serialize=False, - to="passbook_core.Policy", + to="passbook_policies.Policy", ), ), ("check_ip", models.BooleanField(default=True)), @@ -54,7 +54,7 @@ class Migration(migrations.Migration): "verbose_name": "Reputation Policy", "verbose_name_plural": "Reputation Policies", }, - bases=("passbook_core.policy",), + bases=("passbook_policies.policy",), ), migrations.CreateModel( name="UserReputation", diff --git a/passbook/policies/reputation/models.py b/passbook/policies/reputation/models.py index dfc9014c3..a4b22dff9 100644 --- a/passbook/policies/reputation/models.py +++ b/passbook/policies/reputation/models.py @@ -2,8 +2,9 @@ from django.db import models from django.utils.translation import gettext as _ -from passbook.core.models import Policy, User +from passbook.core.models import User from passbook.lib.utils.http import get_client_ip +from passbook.policies.models import Policy from passbook.policies.types import PolicyRequest, PolicyResult diff --git a/passbook/policies/tests/test_engine.py b/passbook/policies/tests/test_engine.py index 31e4d6855..0f4c63a83 100644 --- a/passbook/policies/tests/test_engine.py +++ b/passbook/policies/tests/test_engine.py @@ -2,9 +2,10 @@ from django.core.cache import cache from django.test import TestCase -from passbook.core.models import Policy, User +from passbook.core.models import User from passbook.policies.dummy.models import DummyPolicy from passbook.policies.engine import PolicyEngine +from passbook.policies.models import Policy class PolicyTestEngine(TestCase): diff --git a/passbook/providers/app_gw/migrations/0001_initial.py b/passbook/providers/app_gw/migrations/0001_initial.py index 4fb55e41c..00edf725b 100644 --- a/passbook/providers/app_gw/migrations/0001_initial.py +++ b/passbook/providers/app_gw/migrations/0001_initial.py @@ -11,6 +11,7 @@ class Migration(migrations.Migration): dependencies = [ ("passbook_core", "0001_initial"), + ("passbook_policies", "0001_initial"), ] operations = [ @@ -87,7 +88,7 @@ class Migration(migrations.Migration): ), ( "conditions", - models.ManyToManyField(blank=True, to="passbook_core.Policy"), + models.ManyToManyField(blank=True, to="passbook_policies.Policy"), ), ], options={ diff --git a/passbook/stages/password/migrations/0001_initial.py b/passbook/stages/password/migrations/0001_initial.py index b2c740c1d..191cd0836 100644 --- a/passbook/stages/password/migrations/0001_initial.py +++ b/passbook/stages/password/migrations/0001_initial.py @@ -11,7 +11,7 @@ class Migration(migrations.Migration): dependencies = [ ("passbook_flows", "0001_initial"), - ("passbook_core", "0012_delete_factor"), + ("passbook_policies", "0001_initial"), ] operations = [ @@ -39,7 +39,7 @@ class Migration(migrations.Migration): ), ( "password_policies", - models.ManyToManyField(blank=True, to="passbook_core.Policy"), + models.ManyToManyField(blank=True, to="passbook_policies.Policy"), ), ], options={ diff --git a/passbook/stages/prompt/migrations/0001_initial.py b/passbook/stages/prompt/migrations/0001_initial.py index 2809be16c..324503f86 100644 --- a/passbook/stages/prompt/migrations/0001_initial.py +++ b/passbook/stages/prompt/migrations/0001_initial.py @@ -12,7 +12,7 @@ class Migration(migrations.Migration): dependencies = [ ("passbook_flows", "0005_auto_20200512_1158"), - ("passbook_policies", "0003_auto_20200508_1642"), + ("passbook_policies", "0001_initial"), ] operations = [ diff --git a/swagger.yaml b/swagger.yaml index 4a9b3057e..42e011fbe 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -154,7 +154,7 @@ paths: tags: - core parameters: [] - /core/applications/{uuid}/: + /core/applications/{id}/: get: operationId: core_applications_read description: Application Viewset @@ -208,12 +208,11 @@ paths: tags: - core parameters: - - name: uuid + - name: id in: path - description: A UUID string identifying this application. + description: A unique integer value identifying this application. required: true - type: string - format: uuid + type: integer /core/groups/: get: operationId: core_groups_list @@ -2658,7 +2657,7 @@ paths: tags: - sources parameters: [] - /sources/all/{uuid}/: + /sources/all/{id}/: get: operationId: sources_all_read description: Source Viewset @@ -2671,12 +2670,11 @@ paths: tags: - sources parameters: - - name: uuid + - name: id in: path - description: A UUID string identifying this source. + description: A unique integer value identifying this source. required: true - type: string - format: uuid + type: integer /sources/ldap/: get: operationId: sources_ldap_list @@ -2744,7 +2742,7 @@ paths: tags: - sources parameters: [] - /sources/ldap/{uuid}/: + /sources/ldap/{id}/: get: operationId: sources_ldap_read description: LDAP Source Viewset @@ -2798,12 +2796,11 @@ paths: tags: - sources parameters: - - name: uuid + - name: id in: path - description: A UUID string identifying this LDAP Source. + description: A unique integer value identifying this LDAP Source. required: true - type: string - format: uuid + type: integer /sources/oauth/: get: operationId: sources_oauth_list @@ -2871,7 +2868,7 @@ paths: tags: - sources parameters: [] - /sources/oauth/{uuid}/: + /sources/oauth/{id}/: get: operationId: sources_oauth_read description: Source Viewset @@ -2925,12 +2922,11 @@ paths: tags: - sources parameters: - - name: uuid + - name: id in: path - description: A UUID string identifying this Generic OAuth Source. + description: A unique integer value identifying this Generic OAuth Source. required: true - type: string - format: uuid + type: integer /stages/all/: get: operationId: stages_all_list @@ -4837,9 +4833,8 @@ definitions: type: object properties: pk: - title: Uuid - type: string - format: uuid + title: ID + type: integer readOnly: true name: title: Name @@ -4878,8 +4873,8 @@ definitions: policies: type: array items: - type: string - format: uuid + type: integer + readOnly: true uniqueItems: true Group: required: @@ -5610,9 +5605,8 @@ definitions: type: object properties: pk: - title: Uuid - type: string - format: uuid + title: ID + type: integer readOnly: true name: title: Name @@ -5647,9 +5641,8 @@ definitions: type: object properties: pk: - title: Uuid - type: string - format: uuid + title: ID + type: integer readOnly: true name: title: Name @@ -5743,9 +5736,8 @@ definitions: type: object properties: pk: - title: Uuid - type: string - format: uuid + title: ID + type: integer readOnly: true name: title: Name