Added Permission Request feature

- Permissions are considered requests until the 'approved'
   field be set to True
This commit is contained in:
Diego Búrigo Zacarão 2009-07-22 12:05:07 -03:00
parent 9680b4a805
commit a92e3da0fd
12 changed files with 295 additions and 83 deletions

View file

@ -35,6 +35,17 @@
</li>
<li>The permissions requested for this flatpage:
{% get_permission_requests flatpage as "all_perm_requests" %}
<ul><h4>get_permission_requests flatpage as "all_perm_requests"</h4>
{% for perm_request in all_perm_requests %}
<li>{{ perm_request.user }}: {{ perm_request }} {% permission_request_approve_link perm_request %}{% permission_request_delete_link perm_request %}</li>
{% endfor %}
</ul>
</li>
<li>Permission form for adding a specific permission "add_flatpage"
{% permission_form flatpage "flatpage_permission.add_flatpage" %}
</li>
@ -47,6 +58,13 @@
{% add_url_for_obj flatpage %}
</li>
<li>Request a kind of access:
{% permission_request_form flatpage %}
</li>
<li>Permission requuest form for adding a specific permission "add_flatpage"
{% permission_request_form flatpage "flatpage_permission.add_flatpage" %}
</li>
<li><h2>Detailed tests</h2>

View file

@ -2,6 +2,7 @@ from django import forms
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User, Group
from django.forms.util import ErrorList
from authority import permissions, get_choices_for
from authority.models import Permission
@ -12,49 +13,76 @@ class BasePermissionForm(forms.ModelForm):
class Meta:
model = Permission
def __init__(self, perm=None, obj=None, *args, **kwargs):
def __init__(self, perm=None, obj=None, approved=False, *args, **kwargs):
self.perm = perm
self.obj = obj
self.approved = approved
if not self.approved:
self.base_fields['user'].widget = forms.HiddenInput()
else:
self.base_fields['user'].widget = forms.TextInput()
if obj and perm:
self.base_fields['codename'].widget = forms.HiddenInput()
elif obj and not perm:
perm_choices = get_choices_for(self.obj)
self.base_fields['codename'].widget = forms.Select(choices=perm_choices)
self.base_fields['codename'].widget = forms.Select(
choices=perm_choices)
super(BasePermissionForm, self).__init__(*args, **kwargs)
def save(self, request, commit=True, *args, **kwargs):
if not self.approved:
self.instance.user = request.user
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
self.instance.approved = self.approved
return super(BasePermissionForm, self).save(commit)
class UserPermissionForm(BasePermissionForm):
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 = permissions.BasePermission(user=user)
if check.has_perm(self.perm, self.obj):
def clean(self):
cleaned_data = self.cleaned_data
user = self.cleaned_data.get("user", None)
if user:
try:
user = User.objects.get(username__iexact=user)
except User.DoesNotExist:
raise forms.ValidationError(
_("A user with that username does not exist."))
check = permissions.BasePermission(user=user)
error_msg = None
if user.is_superuser:
error_msg = _("The super user %(user)s already has the permission '%(perm)s' for %(object_name)s '%(obj)s'")
else:
error_msg = _("The user %(user)s already has the permission '%(perm)s' for %(object_name)s '%(obj)s'")
raise forms.ValidationError(error_msg % {
'object_name': self.obj._meta.object_name.lower(),
'perm': self.perm,
'obj': self.obj,
'user': user,
})
return user
error_msg = _("The user %(user)s do not need to request " \
"access to any permission as it is a super user.")
elif check.has_perm(self.perm, self.obj):
error_msg = _("The user %(user)s already has the permission " \
"'%(perm)s' for %(object_name)s '%(obj)s'")
elif check.has_request(self.perm, self.obj):
error_msg = _("The user %(user)s already has a permission " \
"request '%(perm)s' for %(object_name)s '%(obj)s'")
if error_msg:
msg = error_msg % {
'object_name': self.obj._meta.object_name.lower(),
'perm': self.perm,
'obj': self.obj,
'user': user,
}
# Only display the error for the user field when it is not hidden
if self.approved:
self._errors["user"] = ErrorList([msg])
else:
raise forms.ValidationError(msg)
cleaned_data['user'] = user
return cleaned_data
class GroupPermissionForm(BasePermissionForm):
group = forms.CharField(label=_('Group'))

View file

@ -10,10 +10,10 @@ class PermissionManager(models.Manager):
def get_for_model(self, obj):
return self.filter(content_type=self.get_content_type(obj))
def for_object(self, obj):
def for_object(self, obj, approved=True):
return self.get_for_model(obj).select_related(
'user', 'creator', 'group', 'content_type'
).filter(object_id=obj.id)
).filter(object_id=obj.id,approved=approved)
def for_user(self, user, obj, check_groups=True):
perms = self.get_for_model(obj)
@ -22,15 +22,17 @@ class PermissionManager(models.Manager):
return perms.select_related('user', 'user__groups', 'creator').filter(
Q(user=user) | Q(group__in=user.groups.all()))
def user_permissions(self, user, perm, obj, check_groups=True):
return self.for_user(user, obj, check_groups).filter(codename=perm)
def user_permissions(self, user, perm, obj, approved=True, check_groups=True):
return self.for_user(user, obj, check_groups).filter(codename=perm,
approved=approved)
def group_permissions(self, group, perm, obj):
def group_permissions(self, group, perm, obj, approved=True):
"""
Get objects that have Group perm permission on
"""
return self.get_for_model(obj).select_related(
'user', 'group', 'creator').filter(group=group, codename=perm)
'user', 'group', 'creator').filter(group=group, codename=perm,
approved=approved)
def delete_objects_permissions(self, obj):
"""
@ -48,3 +50,5 @@ class PermissionManager(models.Manager):
return
perms = self.user_permissions(user, perm, obj).filter(object_id=obj.id)
perms.delete()

View file

@ -21,15 +21,29 @@ class Permission(models.Model):
group = models.ForeignKey(Group, null=True, blank=True)
creator = models.ForeignKey(User, null=True, blank=True, related_name='created_permissions')
approved = models.BooleanField(default=False)
objects = PermissionManager()
def __unicode__(self):
return self.codename
class Meta:
unique_together = ("codename", "object_id", "content_type", "user", "group")
verbose_name = _('permission')
verbose_name_plural = _('permissions')
permissions = (
('change_foreign_permissions', 'Can change foreign permissions'),
('delete_foreign_permissions', 'Can delete foreign permissions'),
('approve_permission_request', 'Can approve permission requests'),
)
def approve_perm_request(self, creator):
"""
Approve granular permission request setting a Permission entry as
approved=True for a specific action from an user on an object instance.
"""
self.approved=True
self.creator=creator
self.save()

View file

@ -35,7 +35,7 @@ class BasePermission(object):
self.group = group
super(BasePermission, self).__init__(*args, **kwargs)
def has_user_perms(self, perm, obj, check_groups=True):
def has_user_perms(self, perm, obj, approved, check_groups=True):
if self.user:
if self.user.is_superuser:
return True
@ -43,15 +43,16 @@ class BasePermission(object):
return False
# check if a Permission object exists for the given params
return Permission.objects.user_permissions(self.user, perm, obj,
check_groups).filter(object_id=obj.id)
approved, check_groups).filter(object_id=obj.id)
return False
def has_group_perms(self, perm, obj):
def has_group_perms(self, perm, obj, approved):
"""
Check if group has the permission for the given object
"""
if self.group:
perms = Permission.objects.group_permissions(self.group, perm, obj)
perms = Permission.objects.group_permissions(self.group, perm, obj,
approved)
return perms.filter(object_id=obj.id)
return False
@ -60,12 +61,24 @@ class BasePermission(object):
Check if user has the permission for the given object
"""
if self.user:
if self.has_user_perms(perm, obj, check_groups):
if self.has_user_perms(perm, obj, True, check_groups):
return True
if self.group:
return self.has_group_perms(perm, obj)
return self.has_group_perms(perm, obj, True)
return False
def has_request(self, perm, obj, check_groups=True):
"""
Check if user has the permission request for the given object
"""
if self.user:
if self.has_user_perms(perm, obj, False, check_groups):
return True
if self.group:
return self.has_group_perms(perm, obj, False)
return False
def can(self, check, generic=False, *args, **kwargs):
if not args:
args = [self.model]

View file

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

View file

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

View file

@ -0,0 +1,2 @@
{% load i18n %}
{% if url %}<a href="{{ url }}?next={{ next }}">{% trans "Deny request" %}</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 "Request permission" %}"/>
</p>
</form>
{% endif %}

View file

@ -10,6 +10,20 @@ from authority.forms import UserPermissionForm
register = template.Library()
def _base_link(context, perm, view_name):
return {
'next': context['request'].build_absolute_uri(),
'url': reverse(view_name, kwargs={'permission_pk': perm.pk,}),
}
def _base_permission_form(context, obj, perm, user, approved, view_name):
return {
'form': UserPermissionForm(perm, obj, approved,
initial=dict(codename=perm, user=user)),
'form_url': url_for_obj(view_name, obj),
'next': context['request'].build_absolute_uri(),
}
def next_bit_for(bits, key, if_none=None):
try:
return bits[bits.index(key)+1]
@ -30,12 +44,16 @@ class ResolverNode(template.Node):
return template.Variable(var).resolve(context)
@register.simple_tag
def add_url_for_obj(obj):
return reverse('authority-add-permission', kwargs={
def url_for_obj(view_name, obj):
return reverse(view_name, kwargs={
'app_label': obj._meta.app_label,
'module_name': obj._meta.module_name,
'pk': obj.pk})
@register.simple_tag
def add_url_for_obj(obj):
return url_for_obj('authority-add-permission', obj)
class ComparisonNode(ResolverNode):
"""
Implements a node to provide an "if user/group has permission on object"
@ -124,14 +142,11 @@ def permission_delete_link(context, perm):
"""
user = context['request'].user
if user.is_authenticated():
if user.has_perm('delete_foreign_permissions') or user.pk == perm.creator.pk:
return {
'next': context['request'].build_absolute_uri(),
'delete_url': reverse('authority-delete-permission', kwargs={
'permission_pk': perm.pk,
})
}
return {'delete_url': None}
if user.has_perm('authority.delete_foreign_permissions') \
or user.pk == perm.creator.pk:
return _base_link(context, perm, 'authority-delete-permission')
return {'url': None}
@register.inclusion_tag('authority/permission_form.html', takes_context=True)
def permission_form(context, obj, perm=None):
@ -148,19 +163,37 @@ def permission_form(context, obj, perm=None):
user = context['request'].user
if user.is_authenticated():
if user.has_perm('authority.add_permission'):
return {
'form': UserPermissionForm(perm, obj, initial=dict(codename=perm)),
'form_url': add_url_for_obj(obj),
'next': context['request'].build_absolute_uri(),
}
return _base_permission_form(context, obj, perm, None, True,
'authority-add-permission')
return {'form': None}
@register.inclusion_tag('authority/permission_request_form.html', takes_context=True)
def permission_request_form(context, obj, perm=None):
"""
Renders an "add permission requests" form for the given object. If no perm
is given it will render a select box to choose from.
Syntax::
{% permission_request_form [obj] [permission_label].[check_name] %}
{% permission_request_form lesson "lesson_permission.add_lesson" %}
"""
user = context['request'].user
if user.is_authenticated() and not user.is_superuser:
return _base_permission_form(context, obj, perm, user, False,
'authority-add-request')
return {'form': None}
class PermissionsForObjectNode(ResolverNode):
def __init__(self, obj, user, var_name, perm=None, objs=None):
def __init__(self, obj, user, var_name, approved, perm=None, objs=None):
self.obj = obj
self.user = user
self.perm = perm
self.var_name = var_name
self.approved = approved
def render(self, context):
obj = self.resolve(self.obj, context)
@ -168,7 +201,7 @@ class PermissionsForObjectNode(ResolverNode):
user = self.resolve(self.user, context)
perms = []
if not isinstance(user, AnonymousUser):
perms = Permission.objects.for_object(obj)
perms = Permission.objects.for_object(obj, self.approved)
if isinstance(user, User):
perms = perms.filter(user=user)
context[var_name] = perms
@ -196,6 +229,34 @@ def get_permissions(parser, token):
'obj': next_bit_for(bits, 'get_permissions'),
'user': next_bit_for(bits, 'for'),
'var_name': next_bit_for(bits, 'as', '"permissions"'),
'approved': True,
}
return PermissionsForObjectNode(**kwargs)
@register.tag
def get_permission_requests(parser, token):
"""
Retrieves all permissions requests associated with the given obj and user
and assigns the result to a context variable.
Syntax::
{% get_permission_requests obj %}
{% for perm in permissions %}
{{ perm }}
{% endfor %}
{% get_permission_requests obj as "my_permissions" %}
{% get_permission_requests obj for request.user as "my_permissions" %}
"""
bits = token.contents.split()
kwargs = {
'obj': next_bit_for(bits, 'get_permission_requests'),
'user': next_bit_for(bits, 'for'),
'var_name': next_bit_for(bits, 'as', '"permission_requests"'),
'approved': False,
}
return PermissionsForObjectNode(**kwargs)
@ -247,3 +308,31 @@ def get_permission(parser, token):
'var_name': next_bit_for(bits, 'as', '"permission"'),
}
return PermissionForObjectNode(**kwargs)
@register.inclusion_tag('authority/permission_request_delete_link.html', takes_context=True)
def permission_request_delete_link(context, perm):
"""
Renders a html link to the delete view of the given permission request.
Returns no content if the request-user has no permission to delete foreign
permissions.
"""
user = context['request'].user
if user.is_authenticated():
if user.has_perm('authority.delete_permission'):
return _base_link(context, perm, 'authority-delete-request')
return {'url': None}
@register.inclusion_tag('authority/permission_request_approve_link.html', takes_context=True)
def permission_request_approve_link(context, perm):
"""
Renders a html link to the approve view of the given permission request.
Returns no content if the request-user has no permission to delete foreign
permissions.
"""
user = context['request'].user
if user.is_authenticated():
if user.has_perm('authority.approve_permission_request'):
return _base_link(context, perm, 'authority-approve-request')
return {'url': None}

View file

@ -1,7 +1,11 @@
from django.conf.urls.defaults import *
from authority.views import add_permission, delete_permission
from authority.views import (add_permission, delete_permission,
approve_permission_request)
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"),
url(r'^add/(?P<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<pk>\d+)/$', add_permission, name="authority-add-permission", kwargs = {'approved': True }),
url(r'^add-request/(?P<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<pk>\d+)/$', add_permission, name="authority-add-request", kwargs = {'approved': False }),
url(r'^approve-request/(?P<permission_pk>\d+)/$', approve_permission_request, name="authority-approve-request"),
url(r'^delete-request/(?P<permission_pk>\d+)/$', delete_permission, name="authority-delete-request", kwargs = {'approved': False }),
url(r'^delete/(?P<permission_pk>\d+)/$', delete_permission, name="authority-delete-permission", kwargs = {'approved': True }),
)

View file

@ -2,56 +2,84 @@ 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.utils.translation import ugettext
from django.utils.translation import ugettext as _
from django.template.context import RequestContext
from django.template import loader
from django.contrib.auth.decorators import login_required
from authority.models import Permission
from authority.forms import UserPermissionForm
from authority.templatetags.permissions import add_url_for_obj
from authority.templatetags.permissions import url_for_obj
@require_POST
@login_required
def add_permission(request, app_label, module_name, pk, extra_context={},
template_name='authority/permission_form.html'):
def add_permission(request, app_label, module_name, pk, approved=False,
extra_context={}):
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 permission_denied(request)
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)
if approved:
template_name = 'authority/permission_form.html'
view_name = 'authority-add-permission'
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))
template_name = 'authority/permission_request_form.html'
view_name = 'authority-add-request'
if request.method == 'POST':
if codename is None:
return HttpResponseForbidden(next)
form = UserPermissionForm(data=request.POST, obj=obj, approved=approved,
perm=codename, initial=dict(codename=codename))
if form.is_valid():
form.save(request)
request.user.message_set.create(
message=_('You added a permission request.'))
return HttpResponseRedirect(next)
else:
form = UserPermissionForm(obj=obj, perm=codename, approved=approved,
initial=dict(codename=codename))
context = {
'form': form,
'form_url': url_for_obj(view_name, 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 == permission.creator:
permission.delete()
def approve_permission_request(request, permission_pk):
perm_request = get_object_or_404(Permission, pk=permission_pk)
if request.user.has_perm('authority.approve_permission_request'):
perm_request.approve_perm_request(request.user)
request.user.message_set.create(
message=ugettext('You removed the permission.'))
message=_('You approved the permission request.'))
next = request.REQUEST.get('next') or '/'
return HttpResponseRedirect(next)
@login_required
def delete_permission(request, permission_pk, approved):
permission = get_object_or_404(Permission,
pk=permission_pk, approved=approved)
if request.user.has_perm('authority.delete_foreign_permissions') \
or request.user == permission.creator:
permission.delete()
if approved:
msg=_('You removed the permission.')
else:
msg=_('You removed the permission request.')
request.user.message_set.create(message=msg)
next = request.REQUEST.get('next') or '/'
return HttpResponseRedirect(next)
def permission_denied(request, template_name=None, extra_context={}):
"""
Default 403 handler.