diff --git a/src/c3nav/access/forms.py b/src/c3nav/access/forms.py
new file mode 100644
index 00000000..f3e47d18
--- /dev/null
+++ b/src/c3nav/access/forms.py
@@ -0,0 +1,52 @@
+from django.forms import ModelForm, MultipleChoiceField
+from django.utils.translation import ugettext_lazy as _
+
+from c3nav.access.models import AccessToken, AccessUser
+from c3nav.mapdata.models import AreaLocation
+
+
+class AccessUserForm(ModelForm):
+ class Meta:
+ model = AccessUser
+ fields = ['user_url', 'description']
+
+
+class AccessTokenForm(ModelForm):
+ def __init__(self, *args, request, **kwargs):
+ super().__init__(*args, **kwargs)
+ locations = AreaLocation.objects.filter(routing_inclusion='needs_permission')
+
+ has_operator = True
+ try:
+ request.user.operator
+ except:
+ has_operator = False
+
+ OPTIONS = []
+ can_full = False
+ if request.user.is_superuser:
+ can_full = True
+ elif has_operator:
+ can_award = request.user.operator.can_award_permissions.split(';')
+ can_full = ':full' in can_award
+ locations = locations.filter(name__in=can_award)
+ else:
+ locations = []
+
+ if can_full:
+ OPTIONS.append((':full', _('Full Permissions')))
+
+ OPTIONS += [(location.name, location.title) for location in locations]
+ print(OPTIONS)
+ self.fields['permissions'] = MultipleChoiceField(choices=OPTIONS, required=True)
+
+ class Meta:
+ model = AccessToken
+ fields = ['permissions', 'description', 'expires']
+
+ def clean_permissions(self):
+ data = self.cleaned_data['permissions']
+ if ':full' in data:
+ data = [':full']
+ data = ';'.join(data)
+ return data
diff --git a/src/c3nav/access/migrations/0003_auto_20161221_2311.py b/src/c3nav/access/migrations/0003_auto_20161221_2311.py
new file mode 100644
index 00000000..7815884d
--- /dev/null
+++ b/src/c3nav/access/migrations/0003_auto_20161221_2311.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.4 on 2016-12-21 23:11
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('access', '0002_auto_20161221_1739'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='accessoperator',
+ name='description',
+ field=models.TextField(blank=True, default='', verbose_name='description'),
+ preserve_default=False,
+ ),
+ migrations.AlterField(
+ model_name='accessuser',
+ name='description',
+ field=models.TextField(blank=True, default='', max_length=200, verbose_name='description'),
+ preserve_default=False,
+ ),
+ ]
diff --git a/src/c3nav/access/models.py b/src/c3nav/access/models.py
index 3e47723a..dc0214cd 100644
--- a/src/c3nav/access/models.py
+++ b/src/c3nav/access/models.py
@@ -10,10 +10,12 @@ from django.utils.crypto import get_random_string
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
+from c3nav.mapdata.models import AreaLocation
+
class AccessOperator(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='operator')
- description = models.TextField(_('description'), null=True, blank=True)
+ description = models.TextField(_('description'), blank=True)
can_award_permissions = models.CharField(_('can award permissions'), max_length=2048)
access_from = models.DateTimeField(_('has access from'), null=True, blank=True)
access_until = models.DateTimeField(_('has access until'), null=True, blank=True)
@@ -31,7 +33,7 @@ class AccessUser(models.Model):
help_text=_('Usually an URL to a profile somewhere'))
author = models.ForeignKey(AccessOperator, on_delete=models.PROTECT, null=True, blank=True,
verbose_name=_('creator'))
- description = models.TextField(_('description'), max_length=200, null=True, blank=True)
+ description = models.TextField(_('description'), max_length=200, blank=True)
creation_date = models.DateTimeField(_('creation date'), auto_now_add=True)
class Meta:
@@ -43,7 +45,7 @@ class AccessUser(models.Model):
return self.tokens.filter(Q(expired=False) | Q(expires__isnull=False, expires__lt=timezone.now()))
def new_token(self, **kwargs):
- kwargs['secret'] = get_random_string(42, string.ascii_letters + string.digits)
+ kwargs['secret'] = AccessToken.create_secret()
return self.tokens.create(**kwargs)
def __str__(self):
@@ -70,16 +72,28 @@ class AccessToken(models.Model):
def permissions_list(self):
return self.permissions.split(';')
+ @cached_property
+ def permissions_list_objects(self):
+ return AreaLocation.objects.filter(name__in=self.permissions_list)
+
@cached_property
def full_access(self):
return ':full' in self.permissions_list
+ @property
+ def is_expired(self):
+ return self.expired or (self.expires is not None and self.expires < timezone.now())
+
@property
def activation_url(self):
if self.activated:
return None
return reverse('access.activate', kwargs={'pk': self.pk, 'secret': self.secret})
+ @staticmethod
+ def create_secret():
+ return get_random_string(42, string.ascii_letters + string.digits)
+
def new_instance(self):
with transaction.atomic():
for instance in self.instances.filter(expires__isnull=True):
@@ -88,7 +102,7 @@ class AccessToken(models.Model):
self.instances.filter(expires__isnull=False, expires__lt=timezone.now()).delete()
- secret = get_random_string(42, string.ascii_letters+string.digits)
+ secret = self.create_secret()
self.instances.create(secret=secret)
return '%d:%s' % (self.pk, secret)
diff --git a/src/c3nav/access/static/access/css/c3nav-access.css b/src/c3nav/access/static/access/css/c3nav-access.css
index b765dd68..ea6ebba9 100644
--- a/src/c3nav/access/static/access/css/c3nav-access.css
+++ b/src/c3nav/access/static/access/css/c3nav-access.css
@@ -9,6 +9,10 @@ h1 {
.login .container {
max-width:420px;
}
+.pager .middle {
+ top:3px;
+ position:relative;
+}
footer {
@@ -25,3 +29,12 @@ footer {
border-width:0 !important;
padding:0;
}
+
+
+.table .btn {
+ font-size:14px;
+}
+.table tbody tr td {
+ min-height: 29px;
+ line-height:29px;
+}
diff --git a/src/c3nav/access/templates/access/base.html b/src/c3nav/access/templates/access/base.html
index 71060762..1ed95c6c 100644
--- a/src/c3nav/access/templates/access/base.html
+++ b/src/c3nav/access/templates/access/base.html
@@ -17,6 +17,9 @@
c3nav access control
+ {% block nav %}
+ {% endblock %}
+
{% block content %}
{% endblock %}
diff --git a/src/c3nav/access/templates/access/fragment_token.html b/src/c3nav/access/templates/access/fragment_token.html
index 87cece39..a71dc293 100644
--- a/src/c3nav/access/templates/access/fragment_token.html
+++ b/src/c3nav/access/templates/access/fragment_token.html
@@ -1,3 +1,5 @@
{% load i18n %}
-
{% trans 'Activate token on this device' %}
+
+ {% trans 'Activate token on this device' %}
+
diff --git a/src/c3nav/access/templates/access/loggedin_base.html b/src/c3nav/access/templates/access/loggedin_base.html
new file mode 100644
index 00000000..c551ed29
--- /dev/null
+++ b/src/c3nav/access/templates/access/loggedin_base.html
@@ -0,0 +1,10 @@
+{% extends 'access/base.html' %}
+
+{% load i18n %}
+
+{% block nav %}
+
+{% endblock %}
diff --git a/src/c3nav/access/templates/access/user.html b/src/c3nav/access/templates/access/user.html
new file mode 100644
index 00000000..a88a09e2
--- /dev/null
+++ b/src/c3nav/access/templates/access/user.html
@@ -0,0 +1,83 @@
+
+
+
+{% extends 'access/loggedin_base.html' %}
+
+{% load bootstrap3 %}
+{% load i18n %}
+
+{% block content %}
+
Access Tokens {{ user.user_url }}
+
{{ user.description }}
+
+
+
+{% endblock %}
diff --git a/src/c3nav/access/templates/access/user_token.html b/src/c3nav/access/templates/access/user_token.html
new file mode 100644
index 00000000..60d35cd9
--- /dev/null
+++ b/src/c3nav/access/templates/access/user_token.html
@@ -0,0 +1,13 @@
+{% extends 'access/loggedin_base.html' %}
+
+{% load bootstrap3 %}
+{% load i18n %}
+
+{% block content %}
+
+
+ {% include 'access/fragment_token.html' %}
+
+
+
{% trans 'back to user' %}
+{% endblock %}
diff --git a/src/c3nav/access/templates/access/users.html b/src/c3nav/access/templates/access/users.html
new file mode 100644
index 00000000..05d211f8
--- /dev/null
+++ b/src/c3nav/access/templates/access/users.html
@@ -0,0 +1,54 @@
+{% extends 'access/loggedin_base.html' %}
+
+{% load bootstrap3 %}
+{% load i18n %}
+
+{% block content %}
+
Users
+
+
+
+ {% trans 'ID' %} |
+ {% trans 'Name' %} |
+ {% trans 'Author' %} |
+ {% trans 'Description' %} |
+ {% trans 'Active Tokens' %} |
+ {% trans 'Creation Date' %} |
+ {% trans 'Details' %} |
+
+
+
+ {% for user in users %}
+
+ {{ user.id }} |
+ {{ user.user_url }} |
+ {% if user.author %}{{ user.author }}{% endif %} |
+ {{ user.description }} |
+ {{ user.valid_tokens.count }} |
+ {{ user.creation_date|date:"SHORT_DATETIME_FORMAT" }} |
+ {% trans 'Details' %} |
+
+ {% endfor %}
+
+
+
+
+{% endblock %}
diff --git a/src/c3nav/access/urls.py b/src/c3nav/access/urls.py
index 3259a946..e49b4a50 100644
--- a/src/c3nav/access/urls.py
+++ b/src/c3nav/access/urls.py
@@ -1,12 +1,16 @@
from django.conf.urls import url
from django.contrib.auth import views as auth_views
-from c3nav.access.views import activate_token, dashboard, prove
+from c3nav.access.views import activate_token, dashboard, prove, show_user_token, user_detail, user_list
urlpatterns = [
url(r'^$', dashboard, name='access.dashboard'),
url(r'^prove/$', prove, name='access.prove'),
url(r'^activate/(?P
[0-9]+):(?P[a-zA-Z0-9]+)/$', activate_token, name='access.activate'),
+ url(r'^users/$', user_list, name='access.users'),
+ url(r'^users/(?P[0-9]+)/$', user_list, name='access.users'),
+ url(r'^user/(?P[0-9]+)/$', user_detail, name='access.user'),
+ url(r'^user/(?P[0-9]+)/(?P[0-9]+)/$', show_user_token, name='access.user.token'),
url(r'^login/$', auth_views.login, {'template_name': 'access/login.html'}, name='access.login'),
url(r'^logout/$', auth_views.logout, name='access.logout'),
]
diff --git a/src/c3nav/access/views.py b/src/c3nav/access/views.py
index 381b3db0..5d226918 100644
--- a/src/c3nav/access/views.py
+++ b/src/c3nav/access/views.py
@@ -1,17 +1,19 @@
from collections import OrderedDict
from django.contrib.auth.decorators import login_required
+from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db import transaction
-from django.shortcuts import get_object_or_404, render
+from django.shortcuts import get_object_or_404, redirect, render
from c3nav.access.apply import get_nonpublic_packages
+from c3nav.access.forms import AccessTokenForm
from c3nav.access.models import AccessToken, AccessUser
from c3nav.editor.hosters import get_hoster_for_package
@login_required(login_url='/access/login/')
def dashboard(request):
- return render(request, 'access/dashboard.html')
+ return redirect('access.users')
def prove(request):
@@ -75,3 +77,70 @@ def activate_token(request, pk, secret):
return render(request, 'access/activate.html', context={
'success': True,
})
+
+
+@login_required(login_url='/access/login/')
+def user_list(request, page=1):
+ queryset = AccessUser.objects.all()
+ paginator = Paginator(queryset, 25)
+
+ try:
+ users = paginator.page(page)
+ except PageNotAnInteger:
+ return redirect('access.users')
+ except EmptyPage:
+ return redirect('access.users')
+
+ return render(request, 'access/users.html', {
+ 'users': users,
+ })
+
+
+@login_required(login_url='/access/login/')
+def user_detail(request, pk):
+ user = get_object_or_404(AccessUser, id=pk)
+
+ tokens = user.tokens.order_by('-creation_date')
+
+ if request.method == 'POST':
+ if 'expire' in request.POST:
+ token = get_object_or_404(AccessToken, user=user, id=request.POST['expire'])
+ token.expired = True
+ token.save()
+ return redirect('access.user', pk=user.id)
+
+ new_token_form = AccessTokenForm(data=request.POST, request=request)
+ if new_token_form.is_valid():
+ token = new_token_form.instance
+ token.user = user
+ token.secret = AccessToken.create_secret()
+
+ author = None
+ try:
+ author = request.user.operator
+ except:
+ pass
+
+ token.author = author
+ token.save()
+
+ return redirect('access.user.token', user=user.id, token=token.id)
+ else:
+ new_token_form = AccessTokenForm(request=request)
+
+ return render(request, 'access/user.html', {
+ 'user': user,
+ 'new_token_form': new_token_form,
+ 'tokens': tokens,
+ })
+
+
+@login_required(login_url='/access/login/')
+def show_user_token(request, user, token):
+ user = get_object_or_404(AccessUser, id=user)
+ token = get_object_or_404(AccessToken, user=user, id=token, activated=False)
+
+ return render(request, 'access/user_token.html', {
+ 'user': user,
+ 'tokens': token,
+ })