diff --git a/passbook/api/v2/urls.py b/passbook/api/v2/urls.py index 2a66dca99..55ccc07a3 100644 --- a/passbook/api/v2/urls.py +++ b/passbook/api/v2/urls.py @@ -1,4 +1,5 @@ """api v2 urls""" +from django.conf import settings from django.conf.urls import url from django.urls import path 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.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.login.api import LoginStageViewSet @@ -78,7 +78,6 @@ router.register("propertymappings/saml", SAMLPropertyMappingViewSet) router.register("stages/all", StageViewSet) router.register("stages/captcha", CaptchaStageViewSet) -router.register("stages/dummy", DummyStageViewSet) router.register("stages/email", EmailStageViewSet) router.register("stages/otp", OTPStageViewSet) router.register("stages/password", PasswordStageViewSet) @@ -88,6 +87,13 @@ router.register("stages/login", LoginStageViewSet) router.register("flows", 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) + info = openapi.Info( title="passbook API", default_version="v2", diff --git a/passbook/core/migrations/0013_delete_debugpolicy.py b/passbook/core/migrations/0013_delete_debugpolicy.py new file mode 100644 index 000000000..b34c7cfce --- /dev/null +++ b/passbook/core/migrations/0013_delete_debugpolicy.py @@ -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",), + ] diff --git a/passbook/core/models.py b/passbook/core/models.py index 29f489f2a..7cb523113 100644 --- a/passbook/core/models.py +++ b/passbook/core/models.py @@ -1,7 +1,5 @@ """passbook core models""" from datetime import timedelta -from random import SystemRandom -from time import sleep from typing import Any, Optional from uuid import uuid4 @@ -198,29 +196,6 @@ class Policy(ExportModelOperationsMixin("policy"), UUIDModel, CreatedUpdatedMode 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): """Single-use invitation link""" diff --git a/passbook/policies/dummy/__init__.py b/passbook/policies/dummy/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/policies/dummy/api.py b/passbook/policies/dummy/api.py new file mode 100644 index 000000000..dffe5b668 --- /dev/null +++ b/passbook/policies/dummy/api.py @@ -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 diff --git a/passbook/policies/dummy/apps.py b/passbook/policies/dummy/apps.py new file mode 100644 index 000000000..5f60a03f0 --- /dev/null +++ b/passbook/policies/dummy/apps.py @@ -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" diff --git a/passbook/core/forms/policies.py b/passbook/policies/dummy/forms.py similarity index 69% rename from passbook/core/forms/policies.py rename to passbook/policies/dummy/forms.py index b2d9cb5e8..05e6a6bdb 100644 --- a/passbook/core/forms/policies.py +++ b/passbook/policies/dummy/forms.py @@ -3,16 +3,16 @@ from django import forms 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 -class DebugPolicyForm(forms.ModelForm): - """DebugPolicyForm Form""" +class DummyPolicyForm(forms.ModelForm): + """DummyPolicyForm Form""" class Meta: - model = DebugPolicy + model = DummyPolicy fields = GENERAL_FIELDS + ["result", "wait_min", "wait_max"] widgets = { "name": forms.TextInput(), diff --git a/passbook/policies/dummy/migrations/0001_initial.py b/passbook/policies/dummy/migrations/0001_initial.py new file mode 100644 index 000000000..5e388616a --- /dev/null +++ b/passbook/policies/dummy/migrations/0001_initial.py @@ -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",), + ), + ] diff --git a/passbook/policies/dummy/migrations/__init__.py b/passbook/policies/dummy/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/passbook/policies/dummy/models.py b/passbook/policies/dummy/models.py new file mode 100644 index 000000000..98d9e22f5 --- /dev/null +++ b/passbook/policies/dummy/models.py @@ -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") diff --git a/passbook/policies/expiry/api.py b/passbook/policies/expiry/api.py index 545411cef..fd206371c 100644 --- a/passbook/policies/expiry/api.py +++ b/passbook/policies/expiry/api.py @@ -1,4 +1,4 @@ -"""Source API Views""" +"""Password Expiry Policy API Views""" from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet @@ -15,7 +15,7 @@ class PasswordExpiryPolicySerializer(ModelSerializer): class PasswordExpiryPolicyViewSet(ModelViewSet): - """Source Viewset""" + """Password Expiry Viewset""" queryset = PasswordExpiryPolicy.objects.all() serializer_class = PasswordExpiryPolicySerializer diff --git a/passbook/policies/expiry/forms.py b/passbook/policies/expiry/forms.py index 63c9a2bf6..e84794ac1 100644 --- a/passbook/policies/expiry/forms.py +++ b/passbook/policies/expiry/forms.py @@ -4,8 +4,8 @@ from django import forms from django.contrib.admin.widgets import FilteredSelectMultiple 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.forms import GENERAL_FIELDS class PasswordExpiryPolicyForm(forms.ModelForm): diff --git a/passbook/policies/hibp/forms.py b/passbook/policies/hibp/forms.py index bc31196ca..f9560bf14 100644 --- a/passbook/policies/hibp/forms.py +++ b/passbook/policies/hibp/forms.py @@ -4,7 +4,7 @@ from django import forms from django.contrib.admin.widgets import FilteredSelectMultiple 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 diff --git a/passbook/policies/reputation/forms.py b/passbook/policies/reputation/forms.py index 1b9ccd667..0c4af1b37 100644 --- a/passbook/policies/reputation/forms.py +++ b/passbook/policies/reputation/forms.py @@ -2,7 +2,7 @@ from django import forms 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 diff --git a/passbook/policies/tests/test_engine.py b/passbook/policies/tests/test_engine.py index 07a111ba8..31e4d6855 100644 --- a/passbook/policies/tests/test_engine.py +++ b/passbook/policies/tests/test_engine.py @@ -2,7 +2,8 @@ from django.core.cache import cache 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 @@ -12,13 +13,13 @@ class PolicyTestEngine(TestCase): def setUp(self): cache.clear() 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 ) - self.policy_true = DebugPolicy.objects.create( + self.policy_true = DummyPolicy.objects.create( 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 ) self.policy_raises = Policy.objects.create(name="raises") @@ -31,13 +32,13 @@ class PolicyTestEngine(TestCase): def test_engine(self): """Ensure all policies passes (Mix of false and true -> false)""" engine = PolicyEngine( - DebugPolicy.objects.filter(negate__exact=False), self.user + DummyPolicy.objects.filter(negate__exact=False), self.user ) self.assertEqual(engine.build().passing, False) def test_engine_negate(self): """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) def test_engine_policy_error(self): @@ -48,7 +49,7 @@ class PolicyTestEngine(TestCase): def test_engine_cache(self): """Ensure empty policy list passes""" 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(engine.build().passing, False) diff --git a/passbook/root/settings.py b/passbook/root/settings.py index 30ff8bbf8..ba5920f82 100644 --- a/passbook/root/settings.py +++ b/passbook/root/settings.py @@ -101,7 +101,6 @@ INSTALLED_APPS = [ "passbook.stages.otp.apps.PassbookStageOTPConfig", "passbook.stages.captcha.apps.PassbookStageCaptchaConfig", "passbook.stages.password.apps.PassbookStagePasswordConfig", - "passbook.stages.dummy.apps.PassbookStageDummyConfig", "passbook.stages.email.apps.PassbookStageEmailConfig", "passbook.policies.expiry.apps.PassbookPolicyExpiryConfig", "passbook.policies.reputation.apps.PassbookPolicyReputationConfig", @@ -391,4 +390,10 @@ if DEBUG: INSTALLED_APPS.append("debug_toolbar") 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")