Merge pull request #9 from BeryJu/db-reset

DB Reset
This commit is contained in:
Jens L 2020-05-16 18:20:03 +02:00 committed by GitHub
commit 5596caedbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 295 additions and 217 deletions

View File

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

View File

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

View File

@ -16,7 +16,7 @@ from guardian.mixins import (
)
from passbook.admin.forms.users import UserForm
from passbook.core.models import Nonce, User
from passbook.core.models import Token, User
from passbook.lib.views import CreateAssignPermView
@ -92,12 +92,12 @@ class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
permission_required = "passbook_core.reset_user_password"
def get(self, request, *args, **kwargs):
"""Create nonce for user and return link"""
"""Create token for user and return link"""
super().get(request, *args, **kwargs)
# TODO: create plan for user, get token
nonce = Nonce.objects.create(user=self.object)
token = Token.objects.create(user=self.object)
link = request.build_absolute_uri(
reverse("passbook_flows:default-recovery", kwargs={"nonce": nonce.uuid})
reverse("passbook_flows:default-recovery", kwargs={"token": token.uuid})
)
messages.success(
request, _("Password reset link: <pre>%(link)s</pre>" % {"link": link})

View File

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

View File

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

View File

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

View File

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

View File

@ -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",
@ -284,7 +261,7 @@ class Migration(migrations.Migration):
(
"expires",
models.DateTimeField(
default=passbook.core.models.default_nonce_duration
default=passbook.core.models.default_token_duration
),
),
("expiring", models.BooleanField(default=True)),
@ -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",

View File

@ -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 = []

View File

@ -0,0 +1,52 @@
# Generated by Django 3.0.5 on 2020-05-16 14:07
import uuid
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import passbook.core.models
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0014_delete_invitation"),
]
operations = [
migrations.CreateModel(
name="Token",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"expires",
models.DateTimeField(
default=passbook.core.models.default_token_duration
),
),
("expiring", models.BooleanField(default=True)),
("description", models.TextField(blank=True, default="")),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
options={"verbose_name": "Token", "verbose_name_plural": "Tokens",},
bases=(models.Model,),
),
migrations.DeleteModel(name="Nonce",),
]

View File

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

View File

@ -22,15 +22,14 @@ 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()
def default_nonce_duration():
"""Default duration a Nonce is valid"""
def default_token_duration():
"""Default duration a Token is valid"""
return now() + timedelta(minutes=30)
@ -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,45 +169,26 @@ 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 Nonce(ExportModelOperationsMixin("nonce"), UUIDModel):
class Token(ExportModelOperationsMixin("token"), UUIDModel):
"""One-time link for password resets/sign-up-confirmations"""
expires = models.DateTimeField(default=default_nonce_duration)
user = models.ForeignKey("User", on_delete=models.CASCADE)
expires = models.DateTimeField(default=default_token_duration)
user = models.ForeignKey("User", on_delete=models.CASCADE, related_name="+")
expiring = models.BooleanField(default=True)
description = models.TextField(default="", blank=True)
@property
def is_expired(self) -> bool:
"""Check if nonce is expired yet."""
"""Check if token is expired yet."""
return now() > self.expires
def __str__(self):
return f"Nonce f{self.uuid.hex} {self.description} (expires={self.expires})"
return f"Token f{self.uuid.hex} {self.description} (expires={self.expires})"
class Meta:
verbose_name = _("Nonce")
verbose_name_plural = _("Nonces")
verbose_name = _("Token")
verbose_name_plural = _("Tokens")
class PropertyMapping(UUIDModel):

View File

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

View File

@ -2,14 +2,14 @@
from django.utils.timezone import now
from structlog import get_logger
from passbook.core.models import Nonce
from passbook.core.models import Token
from passbook.root.celery import CELERY_APP
LOGGER = get_logger()
@CELERY_APP.task()
def clean_nonces():
"""Remove expired nonces"""
amount, _ = Nonce.objects.filter(expires__lt=now(), expiring=True).delete()
LOGGER.debug("Deleted expired nonces", amount=amount)
def clean_tokens():
"""Remove expired tokens"""
amount, _ = Token.objects.filter(expires__lt=now(), expiring=True).delete()
LOGGER.debug("Deleted expired tokens", amount=amount)

View File

@ -11,7 +11,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
("passbook_policies", "0003_auto_20200508_1642"),
# ("passbook_policies", "0001_initial"),
]
operations = [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,14 +8,14 @@ from django.utils.timezone import now
from django.utils.translation import gettext as _
from structlog import get_logger
from passbook.core.models import Nonce, User
from passbook.core.models import Token, User
from passbook.lib.config import CONFIG
LOGGER = get_logger()
class Command(BaseCommand):
"""Create Nonce used to recover access"""
"""Create Token used to recover access"""
help = _("Create a Key which can be used to restore access to passbook.")
@ -30,22 +30,22 @@ class Command(BaseCommand):
"user", action="store", help="Which user the Token gives access to."
)
def get_url(self, nonce: Nonce) -> str:
def get_url(self, token: Token) -> str:
"""Get full recovery link"""
path = reverse("passbook_recovery:use-nonce", kwargs={"uuid": str(nonce.uuid)})
path = reverse("passbook_recovery:use-token", kwargs={"uuid": str(token.uuid)})
return f"https://{CONFIG.y('domain')}{path}"
def handle(self, *args, **options):
"""Create Nonce used to recover access"""
"""Create Token used to recover access"""
duration = int(options.get("duration", 1))
delta = timedelta(days=duration * 365.2425)
_now = now()
expiry = _now + delta
user = User.objects.get(username=options.get("user"))
nonce = Nonce.objects.create(
token = Token.objects.create(
expires=expiry,
user=user,
description=f"Recovery Nonce generated by {getuser()} on {_now}",
description=f"Recovery Token generated by {getuser()} on {_now}",
)
self.stdout.write(
(
@ -53,4 +53,4 @@ class Command(BaseCommand):
f" anyone to access passbook as {user}."
)
)
self.stdout.write(self.get_url(nonce))
self.stdout.write(self.get_url(token))

View File

@ -5,7 +5,7 @@ from django.core.management import call_command
from django.shortcuts import reverse
from django.test import TestCase
from passbook.core.models import Nonce, User
from passbook.core.models import Token, User
from passbook.lib.config import CONFIG
@ -19,17 +19,17 @@ class TestRecovery(TestCase):
"""Test creation of a new key"""
CONFIG.update_from_dict({"domain": "testserver"})
out = StringIO()
self.assertEqual(len(Nonce.objects.all()), 0)
self.assertEqual(len(Token.objects.all()), 0)
call_command("create_recovery_key", "1", self.user.username, stdout=out)
self.assertIn("https://testserver/recovery/use-nonce/", out.getvalue())
self.assertEqual(len(Nonce.objects.all()), 1)
self.assertIn("https://testserver/recovery/use-token/", out.getvalue())
self.assertEqual(len(Token.objects.all()), 1)
def test_recovery_view(self):
"""Test recovery view"""
out = StringIO()
call_command("create_recovery_key", "1", self.user.username, stdout=out)
nonce = Nonce.objects.first()
token = Token.objects.first()
self.client.get(
reverse("passbook_recovery:use-nonce", kwargs={"uuid": str(nonce.uuid)})
reverse("passbook_recovery:use-token", kwargs={"uuid": str(token.uuid)})
)
self.assertEqual(int(self.client.session["_auth_user_id"]), nonce.user.pk)
self.assertEqual(int(self.client.session["_auth_user_id"]), token.user.pk)

View File

@ -2,8 +2,8 @@
from django.urls import path
from passbook.recovery.views import UseNonceView
from passbook.recovery.views import UseTokenView
urlpatterns = [
path("use-nonce/<uuid:uuid>/", UseNonceView.as_view(), name="use-nonce"),
path("use-token/<uuid:uuid>/", UseTokenView.as_view(), name="use-token"),
]

View File

@ -6,19 +6,19 @@ from django.shortcuts import get_object_or_404, redirect
from django.utils.translation import gettext as _
from django.views import View
from passbook.core.models import Nonce
from passbook.core.models import Token
class UseNonceView(View):
"""Use nonce to login"""
class UseTokenView(View):
"""Use token to login"""
def get(self, request: HttpRequest, uuid: str) -> HttpResponse:
"""Check if nonce exists, log user in and delete nonce."""
nonce: Nonce = get_object_or_404(Nonce, pk=uuid)
if nonce.is_expired:
nonce.delete()
"""Check if token exists, log user in and delete token."""
token: Token = get_object_or_404(Token, pk=uuid)
if token.is_expired:
token.delete()
raise Http404
login(request, nonce.user, backend="django.contrib.auth.backends.ModelBackend")
nonce.delete()
login(request, token.user, backend="django.contrib.auth.backends.ModelBackend")
token.delete()
messages.warning(request, _("Used recovery-link to authenticate."))
return redirect("passbook_core:overview")

View File

@ -228,8 +228,8 @@ USE_TZ = True
# Add a 10 minute timeout to all Celery tasks.
CELERY_TASK_SOFT_TIME_LIMIT = 600
CELERY_BEAT_SCHEDULE = {
"clean_nonces": {
"task": "passbook.core.tasks.clean_nonces",
"clean_tokens": {
"task": "passbook.core.tasks.clean_tokens",
"schedule": crontab(minute="*/5"), # Run every 5 minutes
}
}

View File

@ -10,7 +10,7 @@ from django.utils.translation import gettext as _
from django.views.generic import FormView
from structlog import get_logger
from passbook.core.models import Nonce
from passbook.core.models import Token
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER
from passbook.flows.stage import AuthenticationStage
from passbook.stages.email.forms import EmailStageSendForm
@ -38,9 +38,9 @@ class EmailStageView(FormView, AuthenticationStage):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
if QS_KEY_TOKEN in request.GET:
nonce = get_object_or_404(Nonce, pk=request.GET[QS_KEY_TOKEN])
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = nonce.user
nonce.delete()
token = get_object_or_404(Token, pk=request.GET[QS_KEY_TOKEN])
self.executor.plan.context[PLAN_CONTEXT_PENDING_USER] = token.user
token.delete()
messages.success(request, _("Successfully verified E-Mail."))
return self.executor.stage_ok()
return super().get(request, *args, **kwargs)
@ -50,16 +50,16 @@ class EmailStageView(FormView, AuthenticationStage):
valid_delta = timedelta(
minutes=self.executor.current_stage.token_expiry + 1
) # + 1 because django timesince always rounds down
nonce = Nonce.objects.create(user=pending_user, expires=now() + valid_delta)
token = Token.objects.create(user=pending_user, expires=now() + valid_delta)
# Send mail to user
message = TemplateEmailMessage(
subject=_("passbook - Password Recovery"),
template_name=self.executor.current_stage.template,
to=[pending_user.email],
template_context={
"url": self.get_full_url(**{QS_KEY_TOKEN: nonce.pk.hex}),
"url": self.get_full_url(**{QS_KEY_TOKEN: token.pk.hex}),
"user": pending_user,
"expires": nonce.expires,
"expires": token.expires,
},
)
send_mails(self.executor.current_stage, message)

View File

@ -5,7 +5,7 @@ from django.core import mail
from django.shortcuts import reverse
from django.test import Client, TestCase
from passbook.core.models import Nonce, User
from passbook.core.models import Token, User
from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from passbook.flows.views import SESSION_KEY_PLAN
@ -77,7 +77,7 @@ class TestEmailStage(TestCase):
url = reverse(
"passbook_flows:flow-executor", kwargs={"flow_slug": self.flow.slug}
)
token = Nonce.objects.get(user=self.user)
token = Token.objects.get(user=self.user)
url += f"?{QS_KEY_TOKEN}={token.pk.hex}"
response = self.client.get(url)
self.assertEqual(response.status_code, 302)

View File

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

View File

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

View File

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