From 227966e72788730ee2039c55ddd34041379bb396 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 16 May 2020 16:11:53 +0200 Subject: [PATCH] core: rename nonce to token --- passbook/admin/views/users.py | 8 +-- passbook/core/migrations/0001_initial.py | 2 +- .../migrations/0015_auto_20200516_1407.py | 52 +++++++++++++++++++ passbook/core/models.py | 18 +++---- passbook/core/tasks.py | 10 ++-- .../commands/create_recovery_key.py | 16 +++--- passbook/recovery/tests.py | 14 ++--- passbook/recovery/urls.py | 4 +- passbook/recovery/views.py | 18 +++---- passbook/root/settings.py | 4 +- passbook/stages/email/stage.py | 14 ++--- passbook/stages/email/tests.py | 4 +- 12 files changed, 108 insertions(+), 56 deletions(-) create mode 100644 passbook/core/migrations/0015_auto_20200516_1407.py diff --git a/passbook/admin/views/users.py b/passbook/admin/views/users.py index 618bbf907..6acca6863 100644 --- a/passbook/admin/views/users.py +++ b/passbook/admin/views/users.py @@ -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:
%(link)s
" % {"link": link}) diff --git a/passbook/core/migrations/0001_initial.py b/passbook/core/migrations/0001_initial.py index 332c900cf..7257e3a3e 100644 --- a/passbook/core/migrations/0001_initial.py +++ b/passbook/core/migrations/0001_initial.py @@ -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)), diff --git a/passbook/core/migrations/0015_auto_20200516_1407.py b/passbook/core/migrations/0015_auto_20200516_1407.py new file mode 100644 index 000000000..d24a16ca3 --- /dev/null +++ b/passbook/core/migrations/0015_auto_20200516_1407.py @@ -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",), + ] diff --git a/passbook/core/models.py b/passbook/core/models.py index 6c0a6d5ea..693fee338 100644 --- a/passbook/core/models.py +++ b/passbook/core/models.py @@ -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): diff --git a/passbook/core/tasks.py b/passbook/core/tasks.py index e0c8898d8..4dc560aa8 100644 --- a/passbook/core/tasks.py +++ b/passbook/core/tasks.py @@ -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) diff --git a/passbook/recovery/management/commands/create_recovery_key.py b/passbook/recovery/management/commands/create_recovery_key.py index 060bd55f4..811c13b31 100644 --- a/passbook/recovery/management/commands/create_recovery_key.py +++ b/passbook/recovery/management/commands/create_recovery_key.py @@ -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)) diff --git a/passbook/recovery/tests.py b/passbook/recovery/tests.py index 3080b176e..b927c137e 100644 --- a/passbook/recovery/tests.py +++ b/passbook/recovery/tests.py @@ -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) diff --git a/passbook/recovery/urls.py b/passbook/recovery/urls.py index 1be39f0ff..60e2736dc 100644 --- a/passbook/recovery/urls.py +++ b/passbook/recovery/urls.py @@ -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//", UseNonceView.as_view(), name="use-nonce"), + path("use-token//", UseTokenView.as_view(), name="use-token"), ] diff --git a/passbook/recovery/views.py b/passbook/recovery/views.py index d16760e52..b12fee663 100644 --- a/passbook/recovery/views.py +++ b/passbook/recovery/views.py @@ -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") diff --git a/passbook/root/settings.py b/passbook/root/settings.py index e3ac2eeec..66ee81a99 100644 --- a/passbook/root/settings.py +++ b/passbook/root/settings.py @@ -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 } } diff --git a/passbook/stages/email/stage.py b/passbook/stages/email/stage.py index a1271ab18..252cc83dd 100644 --- a/passbook/stages/email/stage.py +++ b/passbook/stages/email/stage.py @@ -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) diff --git a/passbook/stages/email/tests.py b/passbook/stages/email/tests.py index 345316c18..69fd683ac 100644 --- a/passbook/stages/email/tests.py +++ b/passbook/stages/email/tests.py @@ -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)