policies/dummy: separate dummy policy from core into app

This commit is contained in:
Jens Langhammer 2020-05-10 02:14:55 +02:00
parent c0b05a62f4
commit 9bccf9bb0a
16 changed files with 152 additions and 44 deletions

View File

@ -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",

View 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",),
]

View File

@ -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"""

View File

View 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

View 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"

View File

@ -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(),

View 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",),
),
]

View 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")

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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")