Initial models and managers to support ACLs for a generic object

This commit is contained in:
Roberto Rosario
2011-10-24 12:57:40 -04:00
parent 01a31949ae
commit 7d1c000352
7 changed files with 260 additions and 0 deletions

21
apps/acls/__init__.py Normal file
View File

@@ -0,0 +1,21 @@
from django.utils.translation import ugettext_lazy as _
#from documents.models import Document
from navigation.api import register_links, register_multi_item_links
from project_setup.api import register_setup
from permissions.api import register_permission, set_namespace_title
ACLS_EDIT_ACL = {'namespace': 'acls', 'name': 'acl_edit', 'label': _(u'Edit ACLs')}
ACLS_VIEW_ACL = {'namespace': 'acls', 'name': 'acl_view', 'label': _(u'View ACLs')}
set_namespace_title('acls', _(u'Access control lists'))
register_permission(ACLS_EDIT_ACL)
register_permission(ACLS_VIEW_ACL)
acl_list = {'text': _(u'ACLs'), 'view': 'acl_list', 'famfam': 'lock', 'permissions': [ACLS_VIEW_ACL]}
#register_links(Document, [acl_list], menu_name='form_header')

23
apps/acls/admin.py Normal file
View File

@@ -0,0 +1,23 @@
from django.contrib import admin
from acls.models import AccessEntry
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
#class PermissionHolderInline(admin.StackedInline):
# model = PermissionHolder
# extra = 1
# classes = ('collapse-open',)
# allow_add = True#
#
class AccessEntryAdmin(admin.ModelAdmin):
related_lookup_fields = {
'generic': [['holder_type', 'holder_id'], ['content_type', 'object_id']],
}
#inlines = [PermissionHolderInline]
list_display = ('pk', 'holder_object', 'permission', 'content_object')
list_display_links = ('pk',)
model = AccessEntry
admin.site.register(AccessEntry, AccessEntryAdmin)

62
apps/acls/api.py Normal file
View File

@@ -0,0 +1,62 @@
try:
from psycopg2 import OperationalError
except ImportError:
class OperationalError(Exception):
pass
from django.core.exceptions import ImproperlyConfigured
from django.db import transaction
from django.db.utils import DatabaseError
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext
from django.core.exceptions import PermissionDenied
from django.utils.translation import ugettext_lazy as _
from permissions import PERMISSION_ROLE_VIEW, PERMISSION_ROLE_EDIT, \
PERMISSION_ROLE_CREATE, PERMISSION_ROLE_DELETE, \
PERMISSION_PERMISSION_GRANT, PERMISSION_PERMISSION_REVOKE
from permissions.models import Permission
namespace_titles = {
'permissions': _(u'Permissions')
}
def set_namespace_title(namespace, title):
namespace_titles.setdefault(namespace, title)
@transaction.commit_manually
def register_permission(permission):
try:
permission_obj, created = Permission.objects.get_or_create(
namespace=permission['namespace'], name=permission['name'])
permission_obj.label = unicode(permission['label'])
permission_obj.save()
except DatabaseError:
transaction.rollback()
# Special case for ./manage.py syncdb
except (OperationalError, ImproperlyConfigured):
transaction.rollback()
# Special for DjangoZoom, which executes collectstatic media
# doing syncdb and creating the database tables
else:
transaction.commit()
def check_permissions(requester, permission_list):
for permission_item in permission_list:
permission = get_object_or_404(Permission,
namespace=permission_item['namespace'], name=permission_item['name'])
if permission.has_permission(requester):
return True
raise PermissionDenied(ugettext(u'Insufficient permissions.'))
register_permission(PERMISSION_ROLE_VIEW)
register_permission(PERMISSION_ROLE_EDIT)
register_permission(PERMISSION_ROLE_CREATE)
register_permission(PERMISSION_ROLE_DELETE)
register_permission(PERMISSION_PERMISSION_GRANT)
register_permission(PERMISSION_PERMISSION_REVOKE)

84
apps/acls/models.py Normal file
View File

@@ -0,0 +1,84 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.contrib.auth.models import User
#from django.shortcuts import get_object_or_404
#from django.utils.translation import ugettext
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from permissions.models import Permission
class AccessEntryManager(models.Manager):
def grant(self, permission, requester, obj):
"""
Grant a permission (what), (to) a requester, (on) a specific object
"""
access_entry, created = AccessEntry.objects.get_or_create(
permission=permission,
holder_type=ContentType.objects.get_for_model(requester),
holder_id=requester.pk,
content_type=ContentType.objects.get_for_model(obj),
object_id=obj.pk
)
return created
def revoke(self, permission, holder, obj):
try:
access_entry = AccessEntry.objects.get(
permission=permission,
holder_type=ContentType.objects.get_for_model(holder),
holder_id=holder.pk,
content_type=ContentType.objects.get_for_model(obj),
object_id=obj.pk
)
access_entry.delete()
return True
except AccessEntry.DoesNotExist:
return False
def check_accesses(self, permission_list, requester, obj):
for permission_item in permission_list:
permission = get_object_or_404(Permission,
namespace=permission_item['namespace'], name=permission_item['name'])
try:
access_entry = AccessEntry.objects.get(
permission=permission,
holder_type=ContentType.objects.get_for_model(requester),
holder_id=requester.pk,
content_type=ContentType.objects.get_for_model(obj),
object_id=obj.pk
)
return True
except AccessEntry.DoesNotExist:
raise PermissionDenied(ugettext(u'Insufficient permissions.'))
def get_acl_url(self, obj):
content_type = ContentType.objects.get_for_model(obj.__class__)
return reverse('acl_list', args=[content_type.app_label, content_type.model, obj.pk])
class AccessEntry(models.Model):
permission = models.ForeignKey(Permission, verbose_name=_(u'permission'))
holder_type = models.ForeignKey(ContentType,
related_name='access_holder',
limit_choices_to={'model__in': ('user', 'group', 'role')})
holder_id = models.PositiveIntegerField()
holder_object = generic.GenericForeignKey(ct_field='holder_type', fk_field='holder_id')
content_type = models.ForeignKey(ContentType,
related_name='object_content_type')
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey(ct_field='content_type', fk_field='object_id')
objects = AccessEntryManager()
class Meta:
verbose_name = _(u'access entry')
verbose_name_plural = _(u'access entries')
def __unicode__(self):
return u'%s: %s' % (self.content_type, self.content_object)

16
apps/acls/tests.py Normal file
View File

@@ -0,0 +1,16 @@
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)

15
apps/acls/urls.py Normal file
View File

@@ -0,0 +1,15 @@
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('acls.views',
url(r'^list_for/(?P<app_label>[-\w]+)/(?P<model_name>[-\w]+)/(?P<object_id>\d+)/$', 'acl_list', (), 'acl_list'),
# url(r'^role/list/$', 'role_list', (), 'role_list'),
# url(r'^role/create/$', 'role_create', (), 'role_create'),
# url(r'^role/(?P<role_id>\d+)/permissions/$', 'role_permissions', (), 'role_permissions'),
# url(r'^role/(?P<role_id>\d+)/edit/$', 'role_edit', (), 'role_edit'),
# url(r'^role/(?P<role_id>\d+)/delete/$', 'role_delete', (), 'role_delete'),
# url(r'^role/(?P<role_id>\d+)/members/$', 'role_members', (), 'role_members'),
#
# url(r'^permissions/multiple/grant/$', 'permission_grant', (), 'permission_multiple_grant'),
# url(r'^permissions/multiple/revoke/$', 'permission_revoke', (), 'permission_multiple_revoke'),
)

39
apps/acls/views.py Normal file
View File

@@ -0,0 +1,39 @@
from django.utils.translation import ugettext_lazy as _
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from django.contrib import messages
from django.views.generic.list_detail import object_list
from django.core.urlresolvers import reverse
from django.views.generic.create_update import create_object, delete_object, update_object
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User, Group
from permissions.api import check_permissions, namespace_titles
from acls import ACLS_EDIT_ACL, ACLS_VIEW_ACL
def acl_list_for(request, obj, extra_context=None):
check_permissions(request.user, [ACLS_VIEW_ACL])
context = {
'object_list': [],
'title': _(u'access control lists for: %s' % obj),
#'multi_select_as_buttons': True,
#'hide_links': True,
}
if extra_context:
context.update(extra_context)
return render_to_response('generic_list.html', context,
context_instance=RequestContext(request))
def acl_list(request, app_label, model_name, object_id):
ct = get_object_or_404(ContentType, app_label=app_label, model=model_name)
obj = get_object_or_404(ContentType.user_type.get_object_for_this_type, pk=object_id)
return acl_list_for(request, obj)