manage access permissions

This commit is contained in:
Laura Klünder 2017-12-08 21:31:53 +01:00
parent 75381c47e9
commit eb54ac7896
6 changed files with 214 additions and 5 deletions

View file

@ -1,9 +1,141 @@
from django.forms import ModelForm
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 BooleanField, ChoiceField, Form, ModelForm
from django.utils import timezone
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
class UserPermissionsForm(ModelForm):
class Meta:
model = UserPermissions
exclude = ('user', )
class AccessPermissionForm(Form):
def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.author = request.user
if not request.user_permissions.access_all:
self.author_access_permissions = {
pk: expire_date for pk, expire_date in self.author.accesspermissions.filter(
Q(can_grant=True) & (Q(expire_date__isnull=True) | Q(expire_date__lt=timezone.now()))
).values_list('access_restriction_id', 'expire_date')
}
access_restrictions = AccessRestriction.objects.filter(
pk__in=self.author_access_permissions.keys()
)
else:
self.author_access_permissions = {}
access_restrictions = AccessRestriction.objects.all()
self.access_restrictions = {
access_restriction.pk: access_restriction
for access_restriction in access_restrictions
}
self.access_restriction_choices = {
'all': self.access_restrictions.values(),
**{str(pk): (access_restriction, ) for pk, access_restriction in self.access_restrictions.items()}
}
choices = [('', _('choose permissions…')),
('all', ungettext_lazy('everything possible (%d permission)',
'everything possible (%d permissions)',
len(access_restrictions)) % len(access_restrictions))]
choices.append((_('Access Permissions'), tuple(
(str(pk), access_restriction.title)
for pk, access_restriction in self.access_restrictions.items()
)))
self.fields['access_restrictions'] = ChoiceField(label=_('Access Permission'),
choices=choices, required=True)
expire_choices = [
('', _('never')),
]
for minutes in range(15, 60, 15):
expire_choices.append(
(str(minutes), ungettext_lazy('in %d minute', 'in %d minutes', minutes) % minutes))
for hours in chain(range(1, 6), range(6, 24, 6)):
expire_choices.append(
(str(hours*60), ungettext_lazy('in %d hour', 'in %d hours', hours) % hours)
)
expire_choices.insert(
5, (str(90), _('in 1½ hour'))
)
for days in range(1, 14):
expire_choices.append(
(str(days*24*60), ungettext_lazy('in %d day', 'in %d days', days) % days)
)
self.fields['expires'] = ChoiceField(label=_('expires'), required=False, initial='60',
choices=expire_choices)
if request.user_permissions.access_all:
self.fields['can_grant'] = BooleanField(label=_('can grant'), required=False)
def clean_access_restrictions(self):
data = self.cleaned_data['access_restrictions']
return self.access_restriction_choices[data]
def clean_expires(self):
data = self.cleaned_data['expires']
if data == '':
return None
return timezone.now()+timedelta(minutes=int(data))
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):
restrictions = []
for restriction in self.cleaned_data['access_restrictions']:
expires = self.cleaned_data['expires']
author_expires = self.author_access_permissions.get(restriction.pk)
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', False))
@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()

View file

@ -9,7 +9,7 @@
<form method="POST" class="user-permissions-form">
{% csrf_token %}
{% for field in user_permissions_form %}
<label>{{ field }} {{ field.label }}</label>
<label>{{ field }} {{ field.label }}</label><br>
{% endfor %}
<button type="submit" name="submit_user_permissions" value="1">{% trans 'Save' %}</button>
</form>
@ -22,4 +22,27 @@
{% endfor %}
</p>
{% endif %}
<h4>{% trans 'Access Permissions' %}</h4>
{% if user.accesspermissions.all %}
<table>
<tr>
<th>{% trans 'Access Restriction' %}</th>
<th>{% trans 'expires' %}</th>
</tr>
{% for access_permission in user.accesspermissions.all %}
<tr>
<td>{{ access_permission.access_restriction.title }}</td>
<td>{{ access_permission.expire_date }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<p><em>{% trans 'none' %}</em></p>
{% endif %}
<form method="POST" class="access-permissions-form">
{% csrf_token %}
{{ add_access_permission_form }}
<button type="submit" name="submit_access_permissions" value="1">{% trans 'Save' %}</button>
</form>
{% endblock %}

View file

@ -5,11 +5,13 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.core.paginator import Paginator
from django.db.models import Prefetch
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.translation import ugettext_lazy as _
from c3nav.control.forms import UserPermissionsForm
from c3nav.control.forms import AccessPermissionForm, UserPermissionsForm
from c3nav.control.models import UserPermissions
from c3nav.mapdata.models.access import AccessPermission
def control_panel_view(func):
@ -48,7 +50,11 @@ def user_list(request):
@login_required
@control_panel_view
def user_detail(request, user):
qs = User.objects.select_related('permissions').prefetch_related('accesspermissions')
qs = User.objects.select_related(
'permissions',
).prefetch_related(
Prefetch('accesspermissions', AccessPermission.objects.select_related('access_restriction'))
)
user = get_object_or_404(qs, pk=user)
ctx = {
@ -79,4 +85,18 @@ def user_detail(request, user):
'user_permissions_form': form
})
# access permissions
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.'))
return redirect(request.path_info)
else:
form = AccessPermissionForm(request=request)
ctx.update({
'add_access_permission_form': form
})
return render(request, 'control/user.html', ctx)

View file

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2017-12-08 18:23
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('mapdata', '0054_title_plural'),
]
operations = [
migrations.AddField(
model_name='accesspermission',
name='author',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='authored_access_permissions', to=settings.AUTH_USER_MODEL, verbose_name='Author'),
),
migrations.AddField(
model_name='accesspermission',
name='can_grant',
field=models.BooleanField(default=False, verbose_name='can grant'),
),
]

View file

@ -15,7 +15,8 @@ class AccessRestriction(TitledMixin, models.Model):
"""
An access restriction
"""
users = models.ManyToManyField(settings.AUTH_USER_MODEL, through='AccessPermission')
users = models.ManyToManyField(settings.AUTH_USER_MODEL, through='AccessPermission',
through_fields=('access_restriction', 'user'))
open = models.BooleanField(default=False, verbose_name=_('open'))
class Meta:
@ -34,6 +35,9 @@ class AccessPermission(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
access_restriction = models.ForeignKey(AccessRestriction, on_delete=models.CASCADE)
expire_date = models.DateTimeField(null=True, verbose_name=_('expires'))
can_grant = models.BooleanField(default=False, verbose_name=_('can grant'))
author = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL,
related_name='authored_access_permissions', verbose_name=_('Author'))
class Meta:
verbose_name = _('Access Permission')

View file

@ -703,4 +703,6 @@ main.control h4 {
.user-permissions-form label {
font-weight: 400;
width: auto;
display: inline;
}