Restructure Alliance Auth package (#867)

* Refactor allianceauth into its own package

* Add setup

* Add missing default_app_config declarations

* Fix timerboard namespacing

* Remove obsolete future imports

* Remove py2 mock support

* Remove six

* Add experimental 3.7 support and multiple Dj versions

* Remove python_2_unicode_compatible

* Add navhelper as local package

* Update requirements
This commit is contained in:
Basraah
2017-09-19 09:46:40 +10:00
committed by GitHub
parent d10580b56b
commit 786859294d
538 changed files with 1197 additions and 1523 deletions

View File

@@ -0,0 +1 @@
default_app_config = 'allianceauth.permissions_tool.apps.PermissionsToolConfig'

View File

@@ -0,0 +1,7 @@
from django.apps import AppConfig
class PermissionsToolConfig(AppConfig):
name = 'allianceauth.permissions_tool'
label = 'permissions_tool'

View File

@@ -0,0 +1,29 @@
from . import urls
from allianceauth import hooks
from allianceauth.services.hooks import MenuItemHook, UrlHook
class PermissionsTool(MenuItemHook):
def __init__(self):
MenuItemHook.__init__(self,
'Permissions Audit',
'fa fa-key fa-id-card grayiconecolor',
'permissions_tool:overview',
order=400,
navactive=['permissions_tool:'])
def render(self, request):
if request.user.has_perm('permissions_tool.audit_permissions'):
return MenuItemHook.render(self, request)
return ''
@hooks.register('menu_item_hook')
def register_menu():
return PermissionsTool()
@hooks.register('url_hook')
def register_url():
return UrlHook(urls, 'permissions_tool', r'^permissions/')

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-02-06 08:58
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='PermissionsTool',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'managed': False,
'permissions': (('audit_permissions', 'Can audit permissions'),),
},
),
]

View File

@@ -0,0 +1,12 @@
from django.db import models
class PermissionsTool(models.Model):
"""
Dummy model for holding permissions
"""
class Meta:
managed = False
permissions = (
('audit_permissions', 'Can audit permissions'),
)

View File

@@ -0,0 +1,48 @@
{% extends "registered/base.html" %}
{% load bootstrap %}
{% load staticfiles %}
{% load i18n %}
{% block page_title %}{{ permission.permission.codename }} - {% trans "Permissions Audit" %}{% endblock page_title %}
{% block content %}
<div>
<h1 class="page-header">{% trans "Permissions Audit" %}: {{ permission.permission.codename }}</h1>
<a href="{% url 'permissions_tool:overview' %}" class="btn btn-default">
<i class="glyphicon glyphicon-chevron-left"></i> {% trans "Back" %}
</a>
<table class="table table-hover">
<thead>
<tr>
<th class="col-md-3">
{% trans "Group" %}
</th>
<th class="col-md-3">
{% trans "User" %}
</th>
</tr>
</thead>
<tbody>
{% for user in permission.users %}
<tr>
{% include 'permissions_tool/audit_row.html' with group="Permission Granted Directly (No Group)" %}
</tr>
{% endfor %}
{% for group in permission.groups %}
{% for user in group.user_set.all %}
{% include 'permissions_tool/audit_row.html' %}
{% endfor %}
{% endfor %}
{% for state in permission.states %}
{% for profile in state.userprofile_set.all %}
{% with profile.user as user %}
<tr>
{% include 'permissions_tool/audit_state_row.html' %}
</tr>
{% endwith %}
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
{% endblock content %}

View File

@@ -0,0 +1,10 @@
<tr>
<td>
{% if forloop.first %}
<b>{{ group }}</b>
{% endif %}
</td>
<td>
{{ user }}
</td>
</tr>

View File

@@ -0,0 +1,11 @@
{% load i18n %}
<tr>
<td>
{% if forloop.first %}
<b>{% trans 'State' %}: {{ state }}</b>
{% endif %}
</td>
<td>
{{ user }}
</td>
</tr>

View File

@@ -0,0 +1,79 @@
{% extends "registered/base.html" %}
{% load bootstrap %}
{% load staticfiles %}
{% load i18n %}
{% block page_title %}{% trans "Permissions Overview" %}{% endblock page_title %}
{% block content %}
<div>
<h1 class="page-header">{% trans "Permissions Overview" %}</h1>
{% if request.GET.all != 'yes' %}
<span class="pull-right">
{% blocktrans %}Showing only applied permissions{% endblocktrans %}
<a href="{% url 'permissions_tool:overview' %}?all=yes" class="btn btn-primary">{% trans "Show All" %}</a>
</span>
{% else %}
<span class="pull-right">
{% blocktrans %}Showing all permissions{% endblocktrans %}
<a href="{% url 'permissions_tool:overview' %}?all=no" class="btn btn-primary">{% trans "Show Applied" %}</a>
</span>
{% endif %}
<table class="table table-hover">
<thead>
<tr>
<th>
{% trans "App" %}
</th>
<th>
{% trans "Model" %}
</th>
<th>
{% trans "Code Name" %}
</th>
<th>
{% trans "Name" %}
</th>
<th class="col-md-1">
{% trans "Users" %}
</th>
<th class="col-md-1">
{% trans "Groups" %}
</th>
<th class="col-md-1">
{% trans "States" %}
</th>
</tr>
</thead>
<tbody>
{% for perm in permissions %}
<tr>
<td>
{{ perm.permission.content_type.app_label }}
</td>
<td>
{{ perm.permission.content_type.model }}
</td>
<td>
<a href="{% url "permissions_tool:audit" app_label=perm.permission.content_type.app_label model=perm.permission.content_type.model codename=perm.permission.codename %}">
{{ perm.permission.codename }}
</a>
</td>
<td>
{{ perm.permission.name }}
</td>
<td class="{% if perm.users > 0 %}info {% endif %}text-right">
{{ perm.users }}
</td>
<td class="{% if perm.groups > 0 %}info {% endif %}text-right">
{{ perm.groups }} ({{ perm.group_users }})
</td>
<td class="{% if perm.states > 0 %}info {% endif %}text-right">
{{ perm.states }} ({{ perm.state_users }})
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock content %}

View File

@@ -0,0 +1,111 @@
from unittest import mock
from django.test import TestCase
from django import urls
from django.contrib.auth.models import Group, Permission
from allianceauth.tests.auth_utils import AuthUtils
class PermissionsToolViewsTestCase(TestCase):
def setUp(self):
self.member = AuthUtils.create_member('auth_member')
self.member.set_password('password')
self.member.email = 'auth_member@example.com'
self.member.save()
self.none_user = AuthUtils.create_user('none_user', disconnect_signals=True)
self.none_user2 = AuthUtils.create_user('none_user2', disconnect_signals=True)
self.none_user3 = AuthUtils.create_user('none_user3', disconnect_signals=True)
self.no_perm_user = AuthUtils.create_user('no_perm_user', disconnect_signals=True)
self.no_perm_user.set_password('password')
AuthUtils.disconnect_signals()
self.no_perm_group = Group.objects.create(name="No Permission Group")
self.test_group = Group.objects.create(name="Test group")
self.test_group.user_set.add(self.none_user)
self.test_group.user_set.add(self.none_user2)
self.test_group.user_set.add(self.none_user3)
self.permission = Permission.objects.get_by_natural_key(codename='audit_permissions',
app_label='permissions_tool',
model='permissionstool')
self.test_group.permissions.add(self.permission)
self.member.user_permissions.add(self.permission)
AuthUtils.connect_signals()
def test_menu_item(self):
self.client.login(username=self.member.username, password='password')
response = self.client.get(urls.reverse('permissions_tool:overview'))
response_content = str(response.content, encoding='utf8')
self.assertInHTML(
'<li><a class="active" href="/permissions/overview/"><i class="fa fa-key fa-id-card grayiconecolor"></i> Permissions Audit</a></li>',
response_content)
def test_permissions_overview(self):
self.client.login(username=self.member.username, password='password')
response = self.client.get(urls.reverse('permissions_tool:overview'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed('permissions_tool/overview.html')
self.assertContains(response, self.permission.codename)
self.assertContains(response, self.permission.content_type.app_label)
self.assertContains(response, self.permission.content_type.model)
tested_context = False
# Test the context
for perm in response.context['permissions']:
if perm['permission'] == self.permission:
tested_context = True
self.assertDictContainsSubset({'users': 1}, perm)
self.assertDictContainsSubset({'groups': 1}, perm)
self.assertDictContainsSubset({'group_users': 3}, perm)
break
self.assertTrue(tested_context)
def test_permissions_overview_perms(self):
# Ensure permission effectively denys access
self.client.login(username=self.no_perm_user.username, password='password')
response = self.client.get(urls.reverse('permissions_tool:overview'))
self.assertEqual(response.status_code, 302)
def test_permissions_audit(self):
self.client.login(username=self.member.username, password='password')
response = self.client.get(urls.reverse('permissions_tool:audit',
kwargs={
'app_label': self.permission.content_type.app_label,
'model': self.permission.content_type.model,
'codename': self.permission.codename,
}))
self.assertTemplateUsed('permissions_tool/audit.html')
self.assertTemplateUsed('permissions_tool/audit_row.html')
self.assertContains(response, self.permission.codename)
self.assertContains(response, self.none_user)
self.assertContains(response, self.none_user3)
self.assertContains(response, self.test_group)
self.assertNotContains(response, self.no_perm_user)
def test_permissions_audit_perms(self):
# Ensure permission effectively denys access
self.client.login(username=self.no_perm_user.username, password='password')
response = self.client.get(urls.reverse('permissions_tool:audit',
kwargs={
'app_label': self.permission.content_type.app_label,
'model': self.permission.content_type.model,
'codename': self.permission.codename,
}))
self.assertEqual(response.status_code, 302)

View File

@@ -0,0 +1,9 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^overview/$', views.permissions_overview, name='overview'),
url(r'^audit/(?P<app_label>[\w\-_]+)/(?P<model>[\w\-_]+)/(?P<codename>[\w\-_]+)/$', views.permissions_audit,
name='audit'),
]

View File

@@ -0,0 +1,60 @@
import logging
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.models import Permission, User
from django.db.models import Count
from django.shortcuts import render, get_object_or_404
from allianceauth.authentication.models import UserProfile
logger = logging.getLogger(__name__)
@login_required
@permission_required('permissions_tool.audit_permissions')
def permissions_overview(request):
logger.debug("permissions_overview called by user %s" % request.user)
perms = Permission.objects.all()
get_all = True if request.GET.get('all', 'no') == 'yes' else False
context = {'permissions': []}
for perm in perms:
this_perm = {
'users': perm.user_set.all().count(),
'groups': perm.group_set.all().count(),
'states': perm.state_set.all().count(),
'permission': perm
}
if get_all or this_perm['users'] > 0 or this_perm['groups'] > 0 or this_perm['states'] > 0:
# Only add if we're getting everything or one of the objects has this permission
# Add group_users separately to improve performance
this_perm['group_users'] = sum(group.user_count for group in
perm.group_set.annotate(user_count=Count('user')))
this_perm['state_users'] = UserProfile.objects.filter(state__in=perm.state_set.all()).count()
context['permissions'].append(this_perm)
return render(request, 'permissions_tool/overview.html', context=context)
@login_required
@permission_required('permissions_tool.audit_permissions')
def permissions_audit(request, app_label, model, codename):
logger.debug("permissions_audit called by user {} on {}:{}:{}".format(request.user, app_label, model, codename))
perm = get_object_or_404(Permission,
content_type__app_label=app_label,
content_type__model=model,
codename=codename)
context = {'permission': {
'permission': perm,
'users': perm.user_set.all(),
'groups': perm.group_set.all(),
'states': perm.state_set.all(),
'group_users': [group.user_set.all() for group in perm.group_set.all()],
'state_users': User.objects.filter(profile__state__in=perm.state_set.all()),
}
}
return render(request, 'permissions_tool/audit.html', context=context)