core: rename nonce to token
This commit is contained in:
parent
406f69080b
commit
227966e727
|
@ -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})
|
||||
|
|
|
@ -284,7 +284,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)),
|
||||
|
|
52
passbook/core/migrations/0015_auto_20200516_1407.py
Normal file
52
passbook/core/migrations/0015_auto_20200516_1407.py
Normal 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",),
|
||||
]
|
|
@ -29,8 +29,8 @@ 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)
|
||||
|
||||
|
||||
|
@ -195,26 +195,26 @@ class Policy(ExportModelOperationsMixin("policy"), UUIDModel, CreatedUpdatedMode
|
|||
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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"),
|
||||
]
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Reference in a new issue