From cca01e584a68e86df873587143f91e5eaa0006a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Sun, 10 Dec 2017 03:16:07 +0100 Subject: [PATCH] AccessPermissionToken --- src/c3nav/control/forms.py | 41 ++------------- src/c3nav/control/views.py | 4 +- .../migrations/0056_accesspermissiontoken.py | 33 ++++++++++++ src/c3nav/mapdata/models/access.py | 50 +++++++++++++++++++ 4 files changed, 90 insertions(+), 38 deletions(-) create mode 100644 src/c3nav/mapdata/migrations/0056_accesspermissiontoken.py diff --git a/src/c3nav/control/forms.py b/src/c3nav/control/forms.py index 1ec405fd..a3b3c923 100644 --- a/src/c3nav/control/forms.py +++ b/src/c3nav/control/forms.py @@ -1,10 +1,6 @@ -import time -import uuid from datetime import timedelta from itertools import chain -from django.core.cache import cache -from django.db import transaction from django.db.models import Q from django.forms import ChoiceField, Form, ModelForm from django.utils import timezone @@ -12,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ungettext_lazy from c3nav.control.models import UserPermissions -from c3nav.mapdata.models.access import AccessPermission, AccessRestriction +from c3nav.mapdata.models.access import AccessPermissionToken, AccessRestriction class UserPermissionsForm(ModelForm): @@ -100,21 +96,7 @@ class AccessPermissionForm(Form): def save(self, user): self._save_code(self._create_code(), user) - def create_code(self, timeout=30): - code = uuid.uuid4() - cache.set('access:code:%s' % code, (self._create_code(), time.time()+timeout), timeout) - - def save_code(self, code, user): - cache_key = 'access:code:%s' % code - with transaction.atomic(): - AccessPermission.objects.select_for_update().first() - code, expires = cache.get(cache_key, (None, None)) - if code is None or expires < time.time(): - raise ValueError - self._save_code(code, user) - cache.delete(cache_key) - - def _create_code(self): + def get_token(self): restrictions = [] for restriction in self.cleaned_data['access_restrictions']: expires = self.cleaned_data['expires'] @@ -122,19 +104,6 @@ class AccessPermissionForm(Form): if author_expires is not None: expires = author_expires if expires is None else min(expires, author_expires) restrictions.append((restriction.pk, expires)) - return (tuple(restrictions), self.author.pk, self.cleaned_data.get('can_grant', '0') == '1') - - @classmethod - def _save_code(cls, code, user): - restrictions, author_id, can_grant = code - print(code) - with transaction.atomic(): - for pk, expire_date in restrictions: - obj, created = AccessPermission.objects.get_or_create( - user=user, - access_restriction_id=pk - ) - obj.author_id = author_id - obj.expire_date = expire_date - obj.can_grant = can_grant - obj.save() + return AccessPermissionToken(author=self.author, + can_grant=self.cleaned_data.get('can_grant', '0') == '1', + restrictions=tuple(restrictions)) diff --git a/src/c3nav/control/views.py b/src/c3nav/control/views.py index 99fe5773..1bbcad58 100644 --- a/src/c3nav/control/views.py +++ b/src/c3nav/control/views.py @@ -101,8 +101,8 @@ def user_detail(request, user): if request.method == 'POST' and request.POST.get('submit_access_permissions'): form = AccessPermissionForm(request=request, data=request.POST) if form.is_valid(): - form.save(user) - messages.success(request, _('Access permissions successfully updated.')) + form.get_token().redeem(user) + messages.success(request, _('Access permissions successfully granted.')) return redirect(request.path_info) else: form = AccessPermissionForm(request=request) diff --git a/src/c3nav/mapdata/migrations/0056_accesspermissiontoken.py b/src/c3nav/mapdata/migrations/0056_accesspermissiontoken.py new file mode 100644 index 00000000..35e5ebf0 --- /dev/null +++ b/src/c3nav/mapdata/migrations/0056_accesspermissiontoken.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2017-12-10 02:10 +from __future__ import unicode_literals + +import c3nav.mapdata.models.access +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('mapdata', '0055_grant_access_permissions'), + ] + + operations = [ + migrations.CreateModel( + name='AccessPermissionToken', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('valid_until', models.DateTimeField(db_index=True, default=c3nav.mapdata.models.access.default_valid_until, verbose_name='valid until')), + ('unlimited', models.BooleanField(db_index=True, default=False, verbose_name='unlimited')), + ('redeemed', models.BooleanField(db_index=True, default=False, verbose_name='redeemed')), + ('can_grant', models.BooleanField(db_index=True, default=False, verbose_name='can grant')), + ('data', models.BinaryField()), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='created_accesspermission_tokens', to=settings.AUTH_USER_MODEL, verbose_name='author')), + ('redeemed_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='redeemed_accesspermission_tokens', to=settings.AUTH_USER_MODEL, verbose_name='redeemed by')), + ], + ), + ] diff --git a/src/c3nav/mapdata/models/access.py b/src/c3nav/mapdata/models/access.py index 215f7b44..caba60db 100644 --- a/src/c3nav/mapdata/models/access.py +++ b/src/c3nav/mapdata/models/access.py @@ -1,3 +1,5 @@ +import pickle +import uuid from datetime import timedelta from django.conf import settings @@ -29,6 +31,54 @@ class AccessRestriction(TitledMixin, models.Model): return cls.objects.all() +def default_valid_until(): + return timezone.now()+timedelta(seconds=20) + + +class AccessPermissionToken(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, + related_name='created_accesspermission_tokens', + verbose_name=_('author')) + valid_until = models.DateTimeField(db_index=True, default=default_valid_until, + verbose_name=_('valid until')) + unlimited = models.BooleanField(default=False, db_index=True, verbose_name=_('unlimited')) + redeemed = models.BooleanField(default=False, db_index=True, verbose_name=_('redeemed')) + redeemed_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL, + related_name='redeemed_accesspermission_tokens', + verbose_name=_('redeemed by')) + + can_grant = models.BooleanField(default=False, db_index=True, verbose_name=_('can grant')) + data = models.BinaryField() + + @property + def restrictions(self): + return pickle.loads(self.data) + + @restrictions.setter + def restrictions(self, value): + self.data = pickle.dumps(value) + + def redeem(self, user=None): + if self.redeemed_by_id or (user is None and self.redeemed): + raise TypeError('Already redeemed.') + self.redeemed = True + if user: + for pk, expire_date in self.restrictions: + obj, created = AccessPermission.objects.get_or_create( + user=user, + access_restriction_id=pk + ) + obj.author_id = self.author_id + obj.expire_date = expire_date + obj.can_grant = self.can_grant + obj.save() + self.redeemed_by = user + + if self.pk: + self.save() + + class AccessPermission(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) access_restriction = models.ForeignKey(AccessRestriction, on_delete=models.CASCADE)