rename control → access and add some more stuff

This commit is contained in:
Laura Klünder 2016-12-21 18:47:39 +01:00
parent 2fcbfe6312
commit 8026b78f42
21 changed files with 183 additions and 169 deletions

View file

@ -1,63 +0,0 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from c3nav.control.models import AccessOperator, AccessToken, AccessTokenInstance, AccessUser
class AccessOperatorInline(admin.StackedInline):
model = AccessOperator
can_delete = False
class UserAdmin(BaseUserAdmin):
fieldsets = (
(None, {'fields': ('username', 'password', 'email')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
'groups', 'user_permissions')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
)
readonly_fields = ('last_login', 'date_joined',)
inlines = (AccessOperatorInline, )
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
class AccessTokenInline(admin.TabularInline):
model = AccessToken
show_change_link = True
readonly_fields = ('author', 'permissions', 'description', 'creation_date', 'expires')
def has_add_permission(self, request):
return False
@admin.register(AccessUser)
class AccessUserAdmin(admin.ModelAdmin):
inlines = (AccessTokenInline,)
list_display = ('user_url', 'creation_date', 'author', 'description')
fields = ('user_url', 'creation_date', 'author', 'description')
readonly_fields = ('creation_date', )
class AccessTokenInstanceInline(admin.TabularInline):
model = AccessTokenInstance
fields = ('secret', 'creation_date', 'expires', )
readonly_fields = ('secret', 'creation_date', 'expires', )
def has_add_permission(self, request):
return False
@admin.register(AccessToken)
class AccessTokenAdmin(admin.ModelAdmin):
inlines = (AccessTokenInstanceInline,)
list_display = ('__str__', 'user', 'permissions', 'author', 'creation_date', 'expires')
fields = ('user', 'permissions', 'author', 'creation_date', 'expires')
readonly_fields = ('user', 'creation_date')
def has_add_permission(self, request):
return False

View file

@ -1,82 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2016-12-20 01:43
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):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='AccessOperator',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('description', models.TextField(verbose_name='description')),
('can_award_permissions', models.TextField(verbose_name='can award permissions')),
('access_from', models.DateTimeField(blank=True, null=True, verbose_name='has access from')),
('access_until', models.DateTimeField(blank=True, null=True, verbose_name='has access until')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='operator', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name_plural': 'Access Operator',
'verbose_name': 'Access Operator',
},
),
migrations.CreateModel(
name='AccessToken',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('permissions', models.TextField(verbose_name='permissions')),
('description', models.CharField(max_length=200, verbose_name='description')),
('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
('expipres', models.DateTimeField(blank=True, null=True)),
('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='creator')),
],
options={
'verbose_name_plural': 'Access Tokens',
'verbose_name': 'Access Token',
},
),
migrations.CreateModel(
name='AccessTokenInstance',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('secret', models.CharField(max_length=42, verbose_name='access secret')),
('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
('expipres', models.DateTimeField(null=True)),
('access_token', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='control.AccessToken', verbose_name='Access Token')),
],
options={
'verbose_name_plural': 'Access Tokens Instance',
'verbose_name': 'Access Token Instance',
},
),
migrations.CreateModel(
name='AccessUser',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('user_url', models.CharField(help_text='Usually an URL to a profile somewhere', max_length=200, verbose_name='access name')),
('description', models.TextField(blank=True, max_length=200, null=True, verbose_name='description')),
('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
('author', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='control.AccessOperator', verbose_name='creator')),
],
options={
'verbose_name_plural': 'Access Users',
'verbose_name': 'Access User',
},
),
migrations.AddField(
model_name='accesstoken',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tokens', to='control.AccessUser', verbose_name='Access User'),
),
]

View file

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2016-12-20 01:58
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('control', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='accessoperator',
name='description',
field=models.TextField(blank=True, null=True, verbose_name='description'),
),
]

View file

@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2016-12-20 02:14
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('control', '0002_auto_20161220_0158'),
]
operations = [
migrations.RenameField(
model_name='accesstoken',
old_name='expipres',
new_name='expires',
),
migrations.RenameField(
model_name='accesstokeninstance',
old_name='expipres',
new_name='expires',
),
migrations.AlterField(
model_name='accessuser',
name='user_url',
field=models.CharField(help_text='Usually an URL to a profile somewhere', max_length=200, unique=True, verbose_name='access name'),
),
]

View file

@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2016-12-20 02:53
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('control', '0003_auto_20161220_0214'),
]
operations = [
migrations.AlterField(
model_name='accessoperator',
name='can_award_permissions',
field=models.CharField(max_length=2048, verbose_name='can award permissions'),
),
migrations.AlterField(
model_name='accesstoken',
name='permissions',
field=models.CharField(max_length=2048, verbose_name='permissions'),
),
]

View file

@ -1,80 +0,0 @@
import string
from datetime import timedelta
from django.contrib.auth.models import User
from django.db import models, transaction
from django.utils import timezone
from django.utils.crypto import get_random_string
from django.utils.translation import ugettext_lazy as _
class AccessOperator(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='operator')
description = models.TextField(_('description'), null=True, 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)
class Meta:
verbose_name = _('Access Operator')
verbose_name_plural = _('Access Operator')
def __str__(self):
return str(self.user)
class AccessUser(models.Model):
user_url = models.CharField(_('access name'), unique=True, max_length=200,
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)
creation_date = models.DateTimeField(_('creation date'), auto_now_add=True)
class Meta:
verbose_name = _('Access User')
verbose_name_plural = _('Access Users')
def __str__(self):
return self.user_url
class AccessToken(models.Model):
user = models.ForeignKey(AccessUser, on_delete=models.CASCADE, related_name='tokens',
verbose_name=_('Access User'))
author = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name=_('creator'), null=True, blank=True)
permissions = models.CharField(_('permissions'), max_length=2048)
description = models.CharField(_('description'), max_length=200)
creation_date = models.DateTimeField(_('creation date'), auto_now_add=True)
expires = models.DateTimeField(null=True, blank=True)
class Meta:
verbose_name = _('Access Token')
verbose_name_plural = _('Access Tokens')
def new_instance(self):
with transaction.atomic():
for instance in self.instances.filter(expires__isnull=True):
instance.expires = timezone.now()+timedelta(seconds=5)
instance.save()
self.instances.filter(expires__isnull=False, expires__lt=timezone.now()).delete()
secret = get_random_string(42, string.ascii_letters+string.digits)
self.instances.create(secret=secret)
return '%d:%s' % (self.pk, secret)
def __str__(self):
return '%s #%d' % (_('Access Token'), self.id)
class AccessTokenInstance(models.Model):
access_token = models.ForeignKey(AccessToken, on_delete=models.CASCADE, related_name='instances',
verbose_name=_('Access Token'))
secret = models.CharField(_('access secret'), max_length=42)
creation_date = models.DateTimeField(_('creation date'), auto_now_add=True)
expires = models.DateTimeField(null=True)
class Meta:
verbose_name = _('Access Token Instance')
verbose_name_plural = _('Access Tokens Instance')

View file

@ -1,11 +0,0 @@
body, .btn {
font-size:16px;
}
h1 {
margin-bottom:20px;
}
.login .container {
max-width:420px;
}

View file

@ -1,29 +0,0 @@
{% load static %}
{% load compress %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>c3nav control panel</title>
{% compress css %}
<link href="{% static 'bootstrap/css/bootstrap.css' %}" rel="stylesheet">
<link href="{% static 'control/css/c3nav-control.css' %}" rel="stylesheet">
{% endcompress %}
</head>
<body class="{% block bodyclass %}{% endblock %}">
<div class="container" id="main">
<h1>c3nav control panel</h1>
{% block content %}
{% endblock %}
</div>
{% compress js %}
<script type="text/javascript" src="{% static 'jquery/jquery.js' %}"></script>
<script type="text/javascript" src="{% static 'bootstrap/js/bootstrap.js' %}"></script>
{% endcompress %}
</body>
</html>

View file

@ -1,24 +0,0 @@
{% extends 'control/base.html' %}
{% load bootstrap3 %}
{% load i18n %}
{% block bodyclass %}login{% endblock %}
{% block content %}
<form method="POST">
{% csrf_token %}
<fieldset>
<legend>{% trans 'Log in' %}</legend>
{% bootstrap_form form %}
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block btn-lg">{% trans 'Log in' %}</button>
</div>
</fieldset>
<p>{% blocktrans %}This is the non-public backend for creation of auth tokens.{% endblocktrans %}</p>
<p><a class="btn btn-sm btn-default btn-block" href="{% url 'control.prove' %}">
{% blocktrans %}Prove that you should have access{% endblocktrans %}
</a></p>
</form>
{% endblock %}

View file

@ -1,46 +0,0 @@
{% extends 'control/base.html' %}
{% load bootstrap3 %}
{% load i18n %}
{% block bodyclass %}login{% endblock %}
{% block content %}
<form method="POST">
{% csrf_token %}
<fieldset>
<legend>{% trans 'Prove access rights' %}</legend>
<p>{% blocktrans %}Please enter a valid authentication code for the hosters of the following non-public map packages:{% endblocktrans %}</p>
{% if success %}
<div class="alert alert-success">
<strong>{% trans 'Thanks you get full access to the map!' %}</strong><br>
{{ token }}
</div>
{% elif hosters %}
{% if error %}
<div class="alert alert-dismissible alert-danger">
<button type="button" class="close" data-dismiss="alert">×</button>
{% if error == 'invalid' %}
<strong>{% trans 'Sorry.' %}</strong> {% trans 'One or more access tokens were not correct.' %}
{% elif error == 'duplicate' %}
<strong>{% trans 'Sorry.' %}</strong> {% trans 'You already have an access token.' %}
{% endif %}
</div>
{% endif %}
{% for package in hosters %}
<div class="form-group">
<label for="hoster{{ forloop.counter0 }}">{{ package.name }}</label>
<input type="password" class="form-control" id="hoster{{ forloop.counter0 }}" name="{{ package.name }}" placeholder="{% trans 'Access Token' %}">
</div>
{% endfor %}
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block btn-lg">{% trans 'Submit' %}</button>
</div>
{% else %}
<div class="alert alert-info">
<strong>{% trans 'Sorry, this service is currently not available.' %}</strong>
</div>
{% endif %}
</fieldset>
</form>
{% endblock %}

View file

@ -1,11 +0,0 @@
from django.conf.urls import url
from django.contrib.auth import views as auth_views
from c3nav.control.views import dashboard, prove
urlpatterns = [
url(r'^$', dashboard, name='control.dashboard'),
url(r'^prove/$', prove, name='control.prove'),
url(r'^login/$', auth_views.login, {'template_name': 'control/login.html'}, name='site.login'),
url(r'^logout/$', auth_views.logout, name='site.logout'),
]

View file

@ -1,58 +0,0 @@
from collections import OrderedDict
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from c3nav.control.models import AccessUser
from c3nav.editor.hosters import get_hoster_for_package
from c3nav.mapdata.permissions import get_nonpublic_packages
@login_required(login_url='/control/login/')
def dashboard(request):
return render(request, 'control/dashboard.html')
def prove(request):
hosters = OrderedDict((package, get_hoster_for_package(package)) for package in get_nonpublic_packages())
if not hosters or None in hosters.values():
return render(request, 'control/prove.html', context={'hosters': None})
error = None
if request.method == 'POST':
user_id = None
for package, hoster in hosters.items():
access_token = request.POST.get(package.name)
hoster_user_id = hoster.get_user_id_with_access_token(access_token)
if hoster_user_id is None:
return render(request, 'control/prove.html', context={
'hosters': hosters,
'error': 'invalid',
})
if user_id is None:
user_id = hoster_user_id
user = AccessUser.objects.filter(user_url=user_id).first()
if user is not None:
if user.tokens.count():
return render(request, 'control/prove.html', context={
'hosters': hosters,
'error': 'duplicate',
})
else:
user = AccessUser.objects.create(user_url=user_id)
token = user.tokens.create(permissions=':all', description='automatically created')
token_instance = token.new_instance()
return render(request, 'control/prove.html', context={
'hosters': hosters,
'success': True,
'token': token_instance,
})
return render(request, 'control/prove.html', context={
'hosters': hosters,
'error': error,
})