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.control.models import UserPermissions
from c3nav.mapdata.models.access import AccessPermission, AccessRestriction
class UserPermissionsForm(ModelForm): class UserPermissionsForm(ModelForm):
class Meta: class Meta:
model = UserPermissions model = UserPermissions
exclude = ('user', ) 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"> <form method="POST" class="user-permissions-form">
{% csrf_token %} {% csrf_token %}
{% for field in user_permissions_form %} {% for field in user_permissions_form %}
<label>{{ field }} {{ field.label }}</label> <label>{{ field }} {{ field.label }}</label><br>
{% endfor %} {% endfor %}
<button type="submit" name="submit_user_permissions" value="1">{% trans 'Save' %}</button> <button type="submit" name="submit_user_permissions" value="1">{% trans 'Save' %}</button>
</form> </form>
@ -22,4 +22,27 @@
{% endfor %} {% endfor %}
</p> </p>
{% endif %} {% 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 %} {% endblock %}

View file

@ -5,11 +5,13 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.db.models import Prefetch
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.utils.translation import ugettext_lazy as _ 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.control.models import UserPermissions
from c3nav.mapdata.models.access import AccessPermission
def control_panel_view(func): def control_panel_view(func):
@ -48,7 +50,11 @@ def user_list(request):
@login_required @login_required
@control_panel_view @control_panel_view
def user_detail(request, user): 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) user = get_object_or_404(qs, pk=user)
ctx = { ctx = {
@ -79,4 +85,18 @@ def user_detail(request, user):
'user_permissions_form': form '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) 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 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')) open = models.BooleanField(default=False, verbose_name=_('open'))
class Meta: class Meta:
@ -34,6 +35,9 @@ class AccessPermission(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
access_restriction = models.ForeignKey(AccessRestriction, on_delete=models.CASCADE) access_restriction = models.ForeignKey(AccessRestriction, on_delete=models.CASCADE)
expire_date = models.DateTimeField(null=True, verbose_name=_('expires')) 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: class Meta:
verbose_name = _('Access Permission') verbose_name = _('Access Permission')

View file

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