policies/dummy: separate dummy policy from core into app
This commit is contained in:
parent
c0b05a62f4
commit
9bccf9bb0a
|
@ -1,4 +1,5 @@
|
||||||
"""api v2 urls"""
|
"""api v2 urls"""
|
||||||
|
from django.conf import settings
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
|
@ -31,7 +32,6 @@ from passbook.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProvider
|
||||||
from passbook.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
|
from passbook.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
|
||||||
from passbook.sources.oauth.api import OAuthSourceViewSet
|
from passbook.sources.oauth.api import OAuthSourceViewSet
|
||||||
from passbook.stages.captcha.api import CaptchaStageViewSet
|
from passbook.stages.captcha.api import CaptchaStageViewSet
|
||||||
from passbook.stages.dummy.api import DummyStageViewSet
|
|
||||||
from passbook.stages.email.api import EmailStageViewSet
|
from passbook.stages.email.api import EmailStageViewSet
|
||||||
from passbook.stages.identification.api import IdentificationStageViewSet
|
from passbook.stages.identification.api import IdentificationStageViewSet
|
||||||
from passbook.stages.login.api import LoginStageViewSet
|
from passbook.stages.login.api import LoginStageViewSet
|
||||||
|
@ -78,7 +78,6 @@ router.register("propertymappings/saml", SAMLPropertyMappingViewSet)
|
||||||
|
|
||||||
router.register("stages/all", StageViewSet)
|
router.register("stages/all", StageViewSet)
|
||||||
router.register("stages/captcha", CaptchaStageViewSet)
|
router.register("stages/captcha", CaptchaStageViewSet)
|
||||||
router.register("stages/dummy", DummyStageViewSet)
|
|
||||||
router.register("stages/email", EmailStageViewSet)
|
router.register("stages/email", EmailStageViewSet)
|
||||||
router.register("stages/otp", OTPStageViewSet)
|
router.register("stages/otp", OTPStageViewSet)
|
||||||
router.register("stages/password", PasswordStageViewSet)
|
router.register("stages/password", PasswordStageViewSet)
|
||||||
|
@ -88,6 +87,13 @@ router.register("stages/login", LoginStageViewSet)
|
||||||
router.register("flows", FlowViewSet)
|
router.register("flows", FlowViewSet)
|
||||||
router.register("flows/bindings", FlowStageBindingViewSet)
|
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)
|
||||||
|
|
||||||
info = openapi.Info(
|
info = openapi.Info(
|
||||||
title="passbook API",
|
title="passbook API",
|
||||||
default_version="v2",
|
default_version="v2",
|
||||||
|
|
14
passbook/core/migrations/0013_delete_debugpolicy.py
Normal file
14
passbook/core/migrations/0013_delete_debugpolicy.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 3.0.5 on 2020-05-10 00:08
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("passbook_core", "0012_delete_factor"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.DeleteModel(name="DebugPolicy",),
|
||||||
|
]
|
|
@ -1,7 +1,5 @@
|
||||||
"""passbook core models"""
|
"""passbook core models"""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from random import SystemRandom
|
|
||||||
from time import sleep
|
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
@ -198,29 +196,6 @@ class Policy(ExportModelOperationsMixin("policy"), UUIDModel, CreatedUpdatedMode
|
||||||
raise PolicyException()
|
raise PolicyException()
|
||||||
|
|
||||||
|
|
||||||
class DebugPolicy(Policy):
|
|
||||||
"""Policy used for debugging the PolicyEngine. Returns a fixed result,
|
|
||||||
but takes a random time to process."""
|
|
||||||
|
|
||||||
result = models.BooleanField(default=False)
|
|
||||||
wait_min = models.IntegerField(default=5)
|
|
||||||
wait_max = models.IntegerField(default=30)
|
|
||||||
|
|
||||||
form = "passbook.core.forms.policies.DebugPolicyForm"
|
|
||||||
|
|
||||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
|
||||||
"""Wait random time then return result"""
|
|
||||||
wait = SystemRandom().randrange(self.wait_min, self.wait_max)
|
|
||||||
LOGGER.debug("Policy waiting", policy=self, delay=wait)
|
|
||||||
sleep(wait)
|
|
||||||
return PolicyResult(self.result, "Debugging")
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
verbose_name = _("Debug Policy")
|
|
||||||
verbose_name_plural = _("Debug Policies")
|
|
||||||
|
|
||||||
|
|
||||||
class Invitation(ExportModelOperationsMixin("invitation"), UUIDModel):
|
class Invitation(ExportModelOperationsMixin("invitation"), UUIDModel):
|
||||||
"""Single-use invitation link"""
|
"""Single-use invitation link"""
|
||||||
|
|
||||||
|
|
0
passbook/policies/dummy/__init__.py
Normal file
0
passbook/policies/dummy/__init__.py
Normal file
21
passbook/policies/dummy/api.py
Normal file
21
passbook/policies/dummy/api.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
"""Dummy Policy API Views"""
|
||||||
|
from rest_framework.serializers import ModelSerializer
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from passbook.policies.dummy.models import DummyPolicy
|
||||||
|
from passbook.policies.forms import GENERAL_SERIALIZER_FIELDS
|
||||||
|
|
||||||
|
|
||||||
|
class DummyPolicySerializer(ModelSerializer):
|
||||||
|
"""Dummy Policy Serializer"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = DummyPolicy
|
||||||
|
fields = GENERAL_SERIALIZER_FIELDS + ["result", "wait_min", "wait_max"]
|
||||||
|
|
||||||
|
|
||||||
|
class DummyPolicyViewSet(ModelViewSet):
|
||||||
|
"""Dummy Viewset"""
|
||||||
|
|
||||||
|
queryset = DummyPolicy.objects.all()
|
||||||
|
serializer_class = DummyPolicySerializer
|
11
passbook/policies/dummy/apps.py
Normal file
11
passbook/policies/dummy/apps.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
"""Passbook policy dummy app config"""
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PassbookPolicyDummyConfig(AppConfig):
|
||||||
|
"""Passbook policy_dummy app config"""
|
||||||
|
|
||||||
|
name = "passbook.policies.dummy"
|
||||||
|
label = "passbook_policies_dummy"
|
||||||
|
verbose_name = "passbook Policies.Dummy"
|
|
@ -3,16 +3,16 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from passbook.core.models import DebugPolicy
|
from passbook.policies.dummy.models import DummyPolicy
|
||||||
from passbook.policies.forms import GENERAL_FIELDS
|
from passbook.policies.forms import GENERAL_FIELDS
|
||||||
|
|
||||||
|
|
||||||
class DebugPolicyForm(forms.ModelForm):
|
class DummyPolicyForm(forms.ModelForm):
|
||||||
"""DebugPolicyForm Form"""
|
"""DummyPolicyForm Form"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = DebugPolicy
|
model = DummyPolicy
|
||||||
fields = GENERAL_FIELDS + ["result", "wait_min", "wait_max"]
|
fields = GENERAL_FIELDS + ["result", "wait_min", "wait_max"]
|
||||||
widgets = {
|
widgets = {
|
||||||
"name": forms.TextInput(),
|
"name": forms.TextInput(),
|
40
passbook/policies/dummy/migrations/0001_initial.py
Normal file
40
passbook/policies/dummy/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# Generated by Django 3.0.5 on 2020-05-10 00:08
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("passbook_core", "0013_delete_debugpolicy"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="DummyPolicy",
|
||||||
|
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": "Dummy Policy",
|
||||||
|
"verbose_name_plural": "Dummy Policies",
|
||||||
|
},
|
||||||
|
bases=("passbook_core.policy",),
|
||||||
|
),
|
||||||
|
]
|
0
passbook/policies/dummy/migrations/__init__.py
Normal file
0
passbook/policies/dummy/migrations/__init__.py
Normal file
35
passbook/policies/dummy/models.py
Normal file
35
passbook/policies/dummy/models.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
"""Dummy policy"""
|
||||||
|
from random import SystemRandom
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
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.types import PolicyRequest, PolicyResult
|
||||||
|
|
||||||
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
class DummyPolicy(Policy):
|
||||||
|
"""Policy used for debugging the PolicyEngine. Returns a fixed result,
|
||||||
|
but takes a random time to process."""
|
||||||
|
|
||||||
|
result = models.BooleanField(default=False)
|
||||||
|
wait_min = models.IntegerField(default=5)
|
||||||
|
wait_max = models.IntegerField(default=30)
|
||||||
|
|
||||||
|
form = "passbook.policies.dummy.forms.DummyPolicyForm"
|
||||||
|
|
||||||
|
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||||
|
"""Wait random time then return result"""
|
||||||
|
wait = SystemRandom().randrange(self.wait_min, self.wait_max)
|
||||||
|
LOGGER.debug("Policy waiting", policy=self, delay=wait)
|
||||||
|
sleep(wait)
|
||||||
|
return PolicyResult(self.result, "dummy")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
verbose_name = _("Dummy Policy")
|
||||||
|
verbose_name_plural = _("Dummy Policies")
|
|
@ -1,4 +1,4 @@
|
||||||
"""Source API Views"""
|
"""Password Expiry Policy API Views"""
|
||||||
from rest_framework.serializers import ModelSerializer
|
from rest_framework.serializers import ModelSerializer
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ class PasswordExpiryPolicySerializer(ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class PasswordExpiryPolicyViewSet(ModelViewSet):
|
class PasswordExpiryPolicyViewSet(ModelViewSet):
|
||||||
"""Source Viewset"""
|
"""Password Expiry Viewset"""
|
||||||
|
|
||||||
queryset = PasswordExpiryPolicy.objects.all()
|
queryset = PasswordExpiryPolicy.objects.all()
|
||||||
serializer_class = PasswordExpiryPolicySerializer
|
serializer_class = PasswordExpiryPolicySerializer
|
||||||
|
|
|
@ -4,8 +4,8 @@ from django import forms
|
||||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from passbook.core.forms.policies import GENERAL_FIELDS
|
|
||||||
from passbook.policies.expiry.models import PasswordExpiryPolicy
|
from passbook.policies.expiry.models import PasswordExpiryPolicy
|
||||||
|
from passbook.policies.forms import GENERAL_FIELDS
|
||||||
|
|
||||||
|
|
||||||
class PasswordExpiryPolicyForm(forms.ModelForm):
|
class PasswordExpiryPolicyForm(forms.ModelForm):
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django import forms
|
||||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from passbook.core.forms.policies import GENERAL_FIELDS
|
from passbook.policies.forms import GENERAL_FIELDS
|
||||||
from passbook.policies.hibp.models import HaveIBeenPwendPolicy
|
from passbook.policies.hibp.models import HaveIBeenPwendPolicy
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from passbook.core.forms.policies import GENERAL_FIELDS
|
from passbook.policies.forms import GENERAL_FIELDS
|
||||||
from passbook.policies.reputation.models import ReputationPolicy
|
from passbook.policies.reputation.models import ReputationPolicy
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from passbook.core.models import DebugPolicy, Policy, User
|
from passbook.core.models import Policy, User
|
||||||
|
from passbook.policies.dummy.models import DummyPolicy
|
||||||
from passbook.policies.engine import PolicyEngine
|
from passbook.policies.engine import PolicyEngine
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,13 +13,13 @@ class PolicyTestEngine(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
cache.clear()
|
cache.clear()
|
||||||
self.user = User.objects.create_user(username="policyuser")
|
self.user = User.objects.create_user(username="policyuser")
|
||||||
self.policy_false = DebugPolicy.objects.create(
|
self.policy_false = DummyPolicy.objects.create(
|
||||||
result=False, wait_min=0, wait_max=1
|
result=False, wait_min=0, wait_max=1
|
||||||
)
|
)
|
||||||
self.policy_true = DebugPolicy.objects.create(
|
self.policy_true = DummyPolicy.objects.create(
|
||||||
result=True, wait_min=0, wait_max=1
|
result=True, wait_min=0, wait_max=1
|
||||||
)
|
)
|
||||||
self.policy_negate = DebugPolicy.objects.create(
|
self.policy_negate = DummyPolicy.objects.create(
|
||||||
negate=True, result=True, wait_min=0, wait_max=1
|
negate=True, result=True, wait_min=0, wait_max=1
|
||||||
)
|
)
|
||||||
self.policy_raises = Policy.objects.create(name="raises")
|
self.policy_raises = Policy.objects.create(name="raises")
|
||||||
|
@ -31,13 +32,13 @@ class PolicyTestEngine(TestCase):
|
||||||
def test_engine(self):
|
def test_engine(self):
|
||||||
"""Ensure all policies passes (Mix of false and true -> false)"""
|
"""Ensure all policies passes (Mix of false and true -> false)"""
|
||||||
engine = PolicyEngine(
|
engine = PolicyEngine(
|
||||||
DebugPolicy.objects.filter(negate__exact=False), self.user
|
DummyPolicy.objects.filter(negate__exact=False), self.user
|
||||||
)
|
)
|
||||||
self.assertEqual(engine.build().passing, False)
|
self.assertEqual(engine.build().passing, False)
|
||||||
|
|
||||||
def test_engine_negate(self):
|
def test_engine_negate(self):
|
||||||
"""Test negate flag"""
|
"""Test negate flag"""
|
||||||
engine = PolicyEngine(DebugPolicy.objects.filter(negate__exact=True), self.user)
|
engine = PolicyEngine(DummyPolicy.objects.filter(negate__exact=True), self.user)
|
||||||
self.assertEqual(engine.build().passing, False)
|
self.assertEqual(engine.build().passing, False)
|
||||||
|
|
||||||
def test_engine_policy_error(self):
|
def test_engine_policy_error(self):
|
||||||
|
@ -48,7 +49,7 @@ class PolicyTestEngine(TestCase):
|
||||||
def test_engine_cache(self):
|
def test_engine_cache(self):
|
||||||
"""Ensure empty policy list passes"""
|
"""Ensure empty policy list passes"""
|
||||||
engine = PolicyEngine(
|
engine = PolicyEngine(
|
||||||
DebugPolicy.objects.filter(negate__exact=False), self.user
|
DummyPolicy.objects.filter(negate__exact=False), self.user
|
||||||
)
|
)
|
||||||
self.assertEqual(len(cache.keys("policy_*")), 0)
|
self.assertEqual(len(cache.keys("policy_*")), 0)
|
||||||
self.assertEqual(engine.build().passing, False)
|
self.assertEqual(engine.build().passing, False)
|
||||||
|
|
|
@ -101,7 +101,6 @@ INSTALLED_APPS = [
|
||||||
"passbook.stages.otp.apps.PassbookStageOTPConfig",
|
"passbook.stages.otp.apps.PassbookStageOTPConfig",
|
||||||
"passbook.stages.captcha.apps.PassbookStageCaptchaConfig",
|
"passbook.stages.captcha.apps.PassbookStageCaptchaConfig",
|
||||||
"passbook.stages.password.apps.PassbookStagePasswordConfig",
|
"passbook.stages.password.apps.PassbookStagePasswordConfig",
|
||||||
"passbook.stages.dummy.apps.PassbookStageDummyConfig",
|
|
||||||
"passbook.stages.email.apps.PassbookStageEmailConfig",
|
"passbook.stages.email.apps.PassbookStageEmailConfig",
|
||||||
"passbook.policies.expiry.apps.PassbookPolicyExpiryConfig",
|
"passbook.policies.expiry.apps.PassbookPolicyExpiryConfig",
|
||||||
"passbook.policies.reputation.apps.PassbookPolicyReputationConfig",
|
"passbook.policies.reputation.apps.PassbookPolicyReputationConfig",
|
||||||
|
@ -391,4 +390,10 @@ if DEBUG:
|
||||||
INSTALLED_APPS.append("debug_toolbar")
|
INSTALLED_APPS.append("debug_toolbar")
|
||||||
MIDDLEWARE.append("debug_toolbar.middleware.DebugToolbarMiddleware")
|
MIDDLEWARE.append("debug_toolbar.middleware.DebugToolbarMiddleware")
|
||||||
|
|
||||||
|
# Load Dummy/Debug objects
|
||||||
|
INSTALLED_APPS += [
|
||||||
|
"passbook.stages.dummy.apps.PassbookStageDummyConfig",
|
||||||
|
"passbook.policies.dummy.apps.PassbookPolicyDummyConfig",
|
||||||
|
]
|
||||||
|
|
||||||
INSTALLED_APPS.append("passbook.core.apps.PassbookCoreConfig")
|
INSTALLED_APPS.append("passbook.core.apps.PassbookCoreConfig")
|
||||||
|
|
Reference in a new issue