added authority app

This commit is contained in:
Jannis Leidel 2009-06-08 12:00:44 +02:00
parent efe086a508
commit d0ebca03a2
12 changed files with 657 additions and 0 deletions

View file

@ -20,3 +20,4 @@ downloads/*
.installed.cfg
bin/*
develop-eggs/*.egg-link
src/authority/_models/*

31
src/authority/__init__.py Normal file
View file

@ -0,0 +1,31 @@
from inspect import isfunction, getmembers
from django.utils.importlib import import_module
from django.core.exceptions import ImproperlyConfigured
LOADING = False
def autodiscover():
"""
Goes and imports the permissions submodule of every app in INSTALLED_APPS
to make sure the permission set classes are registered correctly.
"""
global LOADING
if LOADING:
return
LOADING = True
import imp
from django.conf import settings
for app in settings.INSTALLED_APPS:
print "checking %s" % app
try:
app_path = import_module(app).__path__
except AttributeError:
continue
try:
imp.find_module('permissions', app_path)
except ImportError:
continue
import_module("%s.permissions" % app)
LOADING = False

32
src/authority/admin.py Normal file
View file

@ -0,0 +1,32 @@
from django.contrib.admin import site, ModelAdmin
from django.contrib.contenttypes import generic
from authority.models import Permission
class PermissionInline(generic.GenericTabularInline):
model = Permission
extra = 1
#exclude = ('creator',)
class PermissionAdmin(ModelAdmin):
model = Permission
list_display = ('codename', 'content_type', 'user', 'group')
list_filter = ('codename', 'content_type')
search_fields = ['object_id', 'content_type', 'user', 'group']
raw_id_fields = ['user', 'group']
fieldsets = (
(None, {
'fields': ('codename', ('content_type', 'object_id'))
}),
('granted for', {
'fields': ('user', 'group', 'creator')
}),
)
def queryset(self, request):
user = request.user
if user.is_superuser or \
user.has_perm('permissions.change_foreign_permissions'):
return super(PermissionAdmin, self).queryset(request)
return super(PermissionAdmin, self).queryset(request).filter(creator=user)
site.register(Permission, PermissionAdmin)

94
src/authority/forms.py Normal file
View file

@ -0,0 +1,94 @@
from django import forms
from django.forms.models import ModelForm
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User, Group
from authority.models import Permission
from authority.permissions import BasePermission
class BasePermissionForm(ModelForm):
class Meta:
model = Permission
def __init__(self, perm=None, obj=None, *args, **kwargs):
self.perm = perm
self.obj = obj
super(BasePermissionForm, self).__init__(*args, **kwargs)
def save(self, request, commit=True, *args, **kwargs):
self.instance.creator = request.user
self.instance.content_type = ContentType.objects.get_for_model(self.obj)
self.instance.object_id = self.obj.id
self.instance.codename = self.perm
return super(BasePermissionForm, self).save(commit)
class UserPermissionForm(BasePermissionForm):
codename = forms.CharField(widget=forms.HiddenInput())
user = forms.CharField(label=_('User'))
class Meta(BasePermissionForm.Meta):
fields = ('user',)
def clean_user(self):
username = self.cleaned_data["user"]
try:
user = User.objects.get(username__iexact=username)
except User.DoesNotExist:
raise forms.ValidationError(
_("A user with that username does not exist."))
check = BasePermission(user)
if check.has_perm(self.perm, self.obj):
raise forms.ValidationError(
_("This user already has permission '%(perm)s' on %(obj)s") % {
'perm': self.perm,
'obj': self.obj
})
return user
class GroupPermissionForm(BasePermissionForm):
name = forms.CharField(label=_('Group'))
class Meta(BasePermissionForm.Meta):
fields = ('group',)
def clean_group(self):
name = self.cleaned_data["name"]
try:
group = Group.objects.get(name__iexact=name)
except Group.DoesNotExist:
raise forms.ValidationError(
_("A group with that name does not exist."))
self.instance.group = group
return name
def save(self, request, commit=True):
group=self.cleaned_data.get("group", None)
check = BasePermission(group=group)
if check.has_perm(self.perm, self.obj):
raise forms.ValidationError(
_("This group already has permission '%(perm)s' on %(obj)s") % {
'perm': self.perm,
'obj': self.obj,
})
self.instance.group = group
return super(GroupPermissionForm, self).save(request, self.obj, commit)
# def del_row_perm(self, instance, perm, check_groups=False,
# fail_silently=False):
# """
# Remove granular permission perm from user on an object instance
# """
# if not self.has_row_perm(instance, perm, not check_groups):
# if not fail_silently:
# raise DoesNotHavePermission(self, perm, instance)
# else:
# return
# content_type = ContentType.objects.get_for_model(instance)
# objects = Permission.objects.filter(user=self,
# content_type__pk=content_type.id,
# object_id=instance.id, name=perm)
# objects.delete()
#
#

76
src/authority/models.py Normal file
View file

@ -0,0 +1,76 @@
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.contrib.auth.models import User, Group
from django.utils.translation import ugettext_lazy as _
class AlreadyHasPermission(Exception):
"""Defining exception for an already existing permission"""
def __init__(self, user_or_group, name, obj=None):
self.user_or_group = user_or_group
self.perm_name = name
self.obj = obj
def __str__(self):
if self.obj:
return "%s has already permission:\"%s\" on %s" % (self.user_or_group,
self.perm_name,
self.obj)
return "%s has already permission:\"%s\"" % (self.user_or_group,
self.perm_name)
class DoesNotHavePermission(Exception):
"""Defining exception for an already existing permission"""
def __init__(self, user_or_group, name, obj=None):
self.user_or_group = user_or_group
self.perm_name = name
self.obj = obj
def __str__(self):
if self.obj:
return "%s has not permission:\"%s\" on %s" % (self.user_or_group,
self.perm_name,
self.obj)
return "%s has not permission:\"%s\" " % (self.user_or_group,
self.perm_name)
class PermissionManager(models.Manager):
def permissions_for_object(self, obj):
object_type = ContentType.objects.get_for_model(obj)
return self.filter(content_type__pk=object_type.id,
object_id=obj.id)
class Permission(models.Model):
"""
A granular permission model, per-object permission in other words.
This kind of permission is associated with a user/group and an object
of any content type.
"""
codename = models.CharField(_('codename'), max_length=100)
content_type = models.ForeignKey(ContentType, related_name="row_permissions")
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
user = models.ForeignKey(User, null=True, blank=True, related_name='granted_permissions')
group = models.ForeignKey(Group, null=True, blank=True)
creator = models.ForeignKey(User, null=True, blank=True, related_name='created_permissions')
objects = PermissionManager()
def __unicode__(self):
return "%s.%s" % (self.content_type.app_label, self.codename)
class Meta:
verbose_name = _('permission')
verbose_name_plural = _('permissions')
permissions = (
('change_foreign_permissions', 'Can change foreign permissions'),
('delete_foreign_permissions', 'Can delete foreign permissions'),
)
from authority import autodiscover
autodiscover()

View file

@ -0,0 +1,171 @@
from inspect import isfunction, getmembers
from django.utils.importlib import import_module
from django.core.exceptions import ImproperlyConfigured
from django.db.models import Q
from django.db.models.base import ModelBase
from django.contrib.contenttypes.models import ContentType
from authority.models import Permission
registry = {}
class AlreadyRegistered(Exception):
pass
class NotRegistered(Exception):
pass
class PermissionMetaclass(type):
"""
Used to generate the default set of permission checks "add", "change" and
"delete".
"""
def __new__(cls, name, bases, attrs):
new_class = super(
PermissionMetaclass, cls).__new__(cls, name, bases, attrs)
if new_class.__name__ == "BasePermission":
return new_class
if not new_class.model:
raise ImproperlyConfigured(
"Permission %s requires a model attribute." % new_class)
if not new_class.label:
new_class.label = "%s_permission" % new_class.__name__.lower()
if new_class in registry:
raise AlreadyRegistered(
"The permission %s is already registered" % new_class)
if new_class.label in registry.values():
raise ImproperlyConfigured(
"The name of %s conflicts with %s" % \
(new_class, registry[new_class.label]))
# registry[PollPermission] = Poll
registry[new_class] = new_class.label
if new_class.checks is None:
new_class.checks = ()
# automatically add following default checks and the other
all_checks = ['add', 'change', 'delete'] + list(new_class.checks)
for check_name in all_checks:
def func(self, obj=None):
if obj is None:
obj = self.model
# first check Django's permission system
perm = '%s.%s_%s' % (obj._meta.app_label, # polls.add_poll
check_name.lower(),
obj._meta.object_name.lower())
perms = self.has_perm(perm)
if obj is not None and not isinstance(obj, ModelBase):
# only check the authority if not model instance
return (perms or
self.can_admin(obj) or
self.has_perm(perm, obj))
return perms
setattr(new_class, 'can_%s' % check_name.lower(), func)
return new_class
def get_permission_by_label(label):
for perm_cls, perm_label in registry.items():
if perm_label == label:
return perm_cls
return None
class BasePermission(object):
"""
Base Permission class to be used to define app permissions.
check = BasePermission(request.user)
"""
__metaclass__ = PermissionMetaclass
checks = ()
label = None
model = None
def __init__(self, user=None, group=None, *args, **kwargs):
self.user = user
self.group = group
super(BasePermission, self).__init__(*args, **kwargs)
@classmethod
def signature(cls):
"""
Used to determine the name of the permission class which then can be
used in the template tag to check.
Format: <app_label>.<object_name>
e.g. "polls.poll"
"""
return '%s.%s' % (cls.model._meta.app_label,
cls.model._meta.object_name.lower())
def has_user_perms(self, perm, obj, check_groups=True):
if self.user:
if self.user.is_superuser:
return True
if not self.user.is_active:
return False
# check if a Permission object exists for the given params
print obj
content_type = ContentType.objects.get_for_model(obj)
perms = Permission.objects.filter(user=self.user, codename=perm,
content_type=content_type,
object_id=obj.id)
if perms:
return True
if check_groups:
# look if one of the user's group have the permissions
for group in self.user.groups.all():
if self.has_group_perms(perm, obj):
return True
return False
def has_group_perms(self, perm, obj):
"""
Check if group has the permission for the given object
"""
perms = self.get_group_perms(perm, obj).filter(object_id=obj.id)
if perms:
return True
return False
def has_perm(self, perm, obj, check_groups=True):
"""
Check if user has the permission for the given object
"""
if self.user:
if self.has_user_perms(perm, obj, check_groups):
return True
if self.group:
return self.has_group_perms(perm, obj)
return False
def get_perms(self, obj):
content_type = ContentType.objects.get_for_model(obj)
perms = Permission.objects.filter(
Q(user=self.user) | Q(
group__in=self.user.groups.all()
), content_type=content_type)
return perms
def get_user_perms(self, perm, obj):
"""
Get objects that User perm permission on
"""
perms = self.get_perms(obj)
return perms.filter(codename=perm)
def get_group_perms(self, perm, obj):
"""
Get objects that Group perm permission on
"""
content_type = ContentType.objects.get_for_model(obj)
perms = Permission.objects.filter(group=self.group, codename=perm,
content_type=content_type)
return perms
def clean_perms(self, obj):
"""
Delete permissions related to an object instance
"""
perms = self.get_perms(obj)
perms.delete()
def can_admin(self, obj):
return self.has_perm('%s.admin' % obj._meta.app_label, obj)

View file

@ -0,0 +1,2 @@
{% load i18n %}
{% if delete_url %}<a href="{{ delete_url }}?next={{ next }}">{% trans "Revoke permission" %}</a>{% endif %}

View file

@ -0,0 +1,10 @@
{% load i18n %}
{% if form %}
<form method="post" enctype="multipart/form-data" action="{{ form_url }}" class="add-permission">
<input type="hidden" name="next" value="{{ next }}"/>
{{ form.as_p }}
<p class="submit">
<input type="submit" value="{% trans "Grant permission" %}"/>
</p>
</form>
{% endif %}

View file

View file

@ -0,0 +1,174 @@
from django import template
from django.db.models import get_app
from django.utils.importlib import import_module
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse
from django.template import Library, Node, Variable
from authority import permissions
from authority.views import add_url_for_obj
from authority.models import Permission
from authority.forms import UserPermissionForm
register = template.Library()
class ComparisonNode(template.Node):
"""
Implements a node to provide an "if user/group has permission on object"
"""
def __init__(self, user, permission, nodelist_true, nodelist_false, *objs):
self.user = user
self.objs = objs
# poll_permission.can_change
self.perm_label, self.check_name = permission.strip('"').split('.')
self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
def render(self, context):
try:
user = template.Variable(self.user).resolve(context)
if self.objs:
objs = []
for obj in self.objs:
if obj is not None:
objs.append(
template.Variable(obj).resolve(context))
else:
objs = None
# get permission set first
perm_cls = permissions.get_permission_by_label(self.perm_label)
if perm_cls is not None:
# create a permission instance
perm_instance = perm_cls(user)
# and try to find the correct check method
check = getattr(perm_instance, self.check_name, None)
if check is not None:
# check
if check(*objs):
# profit
return self.nodelist_true.render(context)
# If the app couldn't be found
except (ImproperlyConfigured, ImportError):
return ''
# If either variable fails to resolve, return nothing.
except template.VariableDoesNotExist:
return ''
# If the types don't permit comparison, return nothing.
except (TypeError, AttributeError):
return ''
return self.nodelist_false.render(context)
@register.tag('ifhasperm')
def do_if_has_perm(parser, token):
"""
This function provides funcitonality for the 'ifhasperm' template tag
{% ifhasperm [permission_label].[check_name] [user] [*objs] %}
lalala
{% else %}
meh
{% endifhasperm %}
{% if hasperm poll_permission.can_change request.user %}
lalala
{% else %}
meh
{% endifhasperm %}
"""
bits = token.contents.split()
if 5 < len(bits) < 3:
raise template.TemplateSyntaxError("'%s' tag takes three,\
four or five arguments" % bits[0])
end_tag = 'endifhasperm'
nodelist_true = parser.parse(('else', end_tag))
token = parser.next_token()
if token.contents == 'else': # there is an 'else' clause in the tag
nodelist_false = parser.parse((end_tag,))
parser.delete_first_token()
else:
nodelist_false = template.NodeList()
if len(bits) == 3: # this tag requires at most 2 objects . None is given
objs = (None, None)
elif len(bits) == 4:# one is given
objs = (bits[3], None)
else: #two are given
objs = (bits[3], bits[4])
return ComparisonNode(bits[2], bits[1], nodelist_true, nodelist_false, *objs)
@register.inclusion_tag('authority/permission_delete_link.html', takes_context=True)
def permission_delete_link(context, perm):
"""
Renders a html link to the delete view of the given permission. Returns
no content if the request-user has no permission to delete foreign
permissions.
"""
if context['request'].user.has_perm('delete_foreign_permissions') \
or context['request'].user == perm.creator:
return {
'next': context['request'].build_absolute_uri(),
'delete_url': reverse('authority-delete-permission', kwargs={
'permission_pk': perm.pk})
}
return {'delete_url': None}
@register.inclusion_tag('authority/permission_form.html', takes_context=True)
def permission_form(context, obj, perm):
"""
Renders an "add permissions" form
{% permission_form [obj] add_lesson %}
{% permission_form lesson add_lesson %}
"""
if context['request'].user.is_authenticated():
return {
'form': UserPermissionForm(perm, initial={'codename': perm}),
'form_url': add_url_for_obj(obj),
'next': context['request'].build_absolute_uri(),
}
return {'form': None}
class PermissionForObjectNode(Node):
def __init__(self, obj, var_name):
self.obj = obj
self.var_name = var_name
def resolve(self, var, context):
"""Resolves a variable out of context if it's not in quotes"""
if var[0] in ('"', "'") and var[-1] == var[0]:
return var[1:-1]
else:
return Variable(var).resolve(context)
def render(self, context):
obj = self.resolve(self.obj, context)
var_name = self.resolve(self.var_name, context)
#check = permissions.BasePermission(user=user)
perms = Permission.objects.permissions_for_object(obj)
context[var_name] = perms
return ''
@register.tag
def get_permissions_for(parser, token):
"""
Syntax::
{% get_permissions_for obj %}
{% for perm in permissions %}
{{ perm }}
{% endfor %}
{% get_permissions_for obj as "my_permissions" %}
"""
def next_bit_for(bits, key, if_none=None):
try:
return bits[bits.index(key)+1]
except ValueError:
return if_none
bits = token.contents.split()
kwargs = {
'obj': next_bit_for(bits, 'get_permissions_for'),
'var_name': next_bit_for(bits, 'as', '"permissions"'),
}
return PermissionForObjectNode(**kwargs)

7
src/authority/urls.py Normal file
View file

@ -0,0 +1,7 @@
from django.conf.urls.defaults import *
from authority.views import add_permission, delete_permission
urlpatterns = patterns('',
url(r'^add/(?P<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<pk>\d+)/$', add_permission, name="authority-add-permission"),
url(r'^delete/(?P<permission_pk>\d+)/$', delete_permission, name="authority-delete-permission"),
)

59
src/authority/views.py Normal file
View file

@ -0,0 +1,59 @@
from django.shortcuts import render_to_response, get_object_or_404
from django.views.decorators.http import require_POST
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.db.models.loading import get_model
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext, ugettext_lazy as _
from django.template.context import RequestContext
from django.contrib.auth.decorators import login_required
from authority.models import Permission
from authority.forms import UserPermissionForm
def add_url_for_obj(obj):
return reverse('authority-add-permission',
kwargs={'app_label': obj._meta.app_label,
'module_name': obj._meta.module_name,
'pk': obj.pk})
@require_POST
@login_required
def add_permission(request, app_label, module_name, pk, extra_context={},
template_name='authority/permission_form.html'):
next = request.POST.get('next', '/')
codename = request.POST.get('codename', None)
if codename is None:
return HttpResponseForbidden(next)
model = get_model(app_label, module_name)
if model is None:
return HttpResponseRedirect(next)
obj = get_object_or_404(model, pk=pk)
form = UserPermissionForm(data=request.POST, obj=obj,
perm=codename, initial={'codename': codename})
if form.is_valid():
form.save(request)
request.user.message_set.create(
message=ugettext('You added a permission.'))
return HttpResponseRedirect(next)
else:
context = {
'form': form,
'form_url': add_url_for_obj(obj),
'next': next,
'perm': codename,
}
context.update(extra_context)
return render_to_response(template_name, context,
context_instance=RequestContext(request))
@login_required
def delete_permission(request, permission_pk):
permission = get_object_or_404(Permission, pk=permission_pk)
if request.user.has_perm('delete_foreign_permissions') \
or request.user == g.creator:
permission.delete()
request.user.message_set.create(
message=ugettext('You removed the permission.'))
next = request.REQUEST.get('next') or '/'
return HttpResponseRedirect(next)