manage access permissions
This commit is contained in:
parent
75381c47e9
commit
eb54ac7896
6 changed files with 214 additions and 5 deletions
|
@ -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()
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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')
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue