From 6117fde64983d432bdb4e6bdd05bafbbd9cf680b Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Thu, 19 Jan 2012 17:44:34 -0400 Subject: [PATCH 01/10] Initial default to current acl support --- apps/acls/managers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/acls/managers.py b/apps/acls/managers.py index ffe3d08d7a..5b772a4c86 100644 --- a/apps/acls/managers.py +++ b/apps/acls/managers.py @@ -211,8 +211,9 @@ class DefaultAccessEntryManager(models.Manager): content type is created. """ def get_holders_for(self, cls): - if isinstance(cls, EncapsulatedObject): - cls = cls.source_object + cls = AccessEntryManager.source_object(cls) + #if isinstance(cls, EncapsulatedObject): + # cls = cls.source_object content_type = ContentType.objects.get_for_model(cls) holder_list = [] From 796dcc908aee7ad5acbb34063fd236e12e6f090c Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Fri, 20 Jan 2012 22:52:56 -0400 Subject: [PATCH 02/10] Move source_object class method to the classes module --- apps/acls/classes.py | 7 +++++++ apps/acls/managers.py | 36 ++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/apps/acls/classes.py b/apps/acls/classes.py index f2b7c36ef2..2b4719dc95 100644 --- a/apps/acls/classes.py +++ b/apps/acls/classes.py @@ -16,6 +16,13 @@ logger = logging.getLogger(__name__) _cache = {} +def get_source_object(obj): + if isinstance(obj, EncapsulatedObject): + return obj.source_object + else: + return obj + + class EncapsulatedObject(object): source_object_name = u'source_object' diff --git a/apps/acls/managers.py b/apps/acls/managers.py index 5b772a4c86..e46082e2e9 100644 --- a/apps/acls/managers.py +++ b/apps/acls/managers.py @@ -13,7 +13,8 @@ from django.core.urlresolvers import reverse from common.models import AnonymousUserSingleton from permissions.models import Permission -from .classes import EncapsulatedObject, AccessHolder, ClassAccessHolder +from .classes import (EncapsulatedObject, AccessHolder, ClassAccessHolder, + get_source_object) logger = logging.getLogger(__name__) @@ -23,18 +24,12 @@ class AccessEntryManager(models.Manager): Implement a 3 tier permission system, involving a permissions, an actor and an object """ - def source_object(self, obj): - if isinstance(obj, EncapsulatedObject): - return obj.source_object - else: - return obj - def grant(self, permission, actor, obj): """ Grant a permission (what), (to) an actor, (on) a specific object """ - obj = self.source_object(obj) - actor = self.source_object(actor) + obj = get_source_object(obj) + actor = get_source_object(actor) access_entry, created = self.model.objects.get_or_create( permission=permission, @@ -49,8 +44,8 @@ class AccessEntryManager(models.Manager): """ Revoke a permission (what), (from) an actor, (on) a specific object """ - obj = self.source_object(obj) - actor = self.source_object(actor) + obj = get_source_object(obj) + actor = get_source_object(actor) try: access_entry = self.model.objects.get( @@ -60,17 +55,18 @@ class AccessEntryManager(models.Manager): content_type=ContentType.objects.get_for_model(obj), object_id=obj.pk ) - access_entry.delete() - return True except self.model.DoesNotExist: return False + else: + access_entry.delete() + return True def has_access(self, permission, actor, obj): """ Returns whether an actor has a specific permission for an object """ - obj = self.source_object(obj) - actor = self.source_object(actor) + obj = get_source_object(obj) + actor = get_source_object(actor) if isinstance(actor, User): if actor.is_superuser or actor.is_staff: @@ -93,8 +89,8 @@ class AccessEntryManager(models.Manager): def check_access(self, permission, actor, obj): # TODO: Merge with has_access - obj = self.source_object(obj) - actor = self.source_object(actor) + obj = get_source_object(obj) + actor = get_source_object(actor) if self.has_access(permission, actor, obj): return True @@ -105,8 +101,8 @@ class AccessEntryManager(models.Manager): """ Returns whether an actor has at least one of a list of permissions for an object """ - obj = self.source_object(obj) - actor = self.source_object(actor) + obj = get_source_object(obj) + actor = get_source_object(actor) for permission in permission_list: if self.has_access(permission, actor, obj): return True @@ -211,7 +207,7 @@ class DefaultAccessEntryManager(models.Manager): content type is created. """ def get_holders_for(self, cls): - cls = AccessEntryManager.source_object(cls) + cls = get_source_object(cls) #if isinstance(cls, EncapsulatedObject): # cls = cls.source_object From 39f47702c0506d87617e78847f18b31b6488f340 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Fri, 20 Jan 2012 22:53:48 -0400 Subject: [PATCH 03/10] Finish the apply_default_acls function and enable it for document creation --- apps/acls/utils.py | 27 +++++++++++++++++++++++++++ apps/sources/models.py | 3 +++ 2 files changed, 30 insertions(+) create mode 100644 apps/acls/utils.py diff --git a/apps/acls/utils.py b/apps/acls/utils.py new file mode 100644 index 0000000000..4da462d7c7 --- /dev/null +++ b/apps/acls/utils.py @@ -0,0 +1,27 @@ +from __future__ import absolute_import + +from django.contrib.contenttypes.models import ContentType + +from .models import AccessEntry, DefaultAccessEntry, CreatorSingleton +from .classes import EncapsulatedObject, AccessHolder, ClassAccessHolder + + +def apply_default_acls(self, obj, actor=None): + if isinstance(obj, EncapsulatedObject): + obj = obj.source_object + + if actor: + actor = AnonymousUserSingleton.objects.passthru_check(actor) + #actor_type = ContentType.objects.get_for_model(actor) + + content_type = ContentType.objects.get_for_model(obj) + + for default_acl in DefaultAccessEntry.objects.filter(content_type=content_type): + holder = CreatorSingleton.objects.passthru_check(default_acl.holder_object, actor) + + access_entry = AccessEntry( + permission=default_acl.permission, + holder_object=holder, + content_object=obj, + ) + access_entry.save() diff --git a/apps/sources/models.py b/apps/sources/models.py index 52e828a4fe..d6a997daf9 100644 --- a/apps/sources/models.py +++ b/apps/sources/models.py @@ -17,6 +17,7 @@ from history.api import create_history from metadata.models import MetadataType from metadata.api import save_metadata_list from scheduler.api import register_interval_job, remove_job +from acls.utils import apply_default_acls from .managers import SourceTransformationManager from .literals import (SOURCE_CHOICES, SOURCE_CHOICES_PLURAL, @@ -76,6 +77,8 @@ class BaseModel(models.Model): document.document_type = document_type document.save() + apply_default_acls(document, document, user) + if metadata_dict_list: save_metadata_list(metadata_dict_list, document, create=True) warnings = update_indexes(document) From d1eb387c16e18d880109ffebc8465f89a485b685 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Fri, 20 Jan 2012 22:54:26 -0400 Subject: [PATCH 04/10] Start work on the creator pseudo class acl holder --- apps/acls/forms.py | 4 ++++ apps/acls/models.py | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/apps/acls/forms.py b/apps/acls/forms.py index e5a55a006c..f4b7b0b4b7 100644 --- a/apps/acls/forms.py +++ b/apps/acls/forms.py @@ -45,3 +45,7 @@ class HolderSelectionForm(forms.Form): super(HolderSelectionForm, self).__init__(*args, **kwargs) self.fields['holder_gid'].choices = non_holder_list + + +class ClassHolderSelectionForm(HolderSelectionForm): + pass diff --git a/apps/acls/models.py b/apps/acls/models.py index a255348760..b5e7c2829b 100644 --- a/apps/acls/models.py +++ b/apps/acls/models.py @@ -11,6 +11,7 @@ from django.core.exceptions import PermissionDenied from django.core.exceptions import ObjectDoesNotExist from permissions.models import StoredPermission +from common.models import Singleton, SingletonManager from .managers import AccessEntryManager, DefaultAccessEntryManager from .classes import AccessObjectClass @@ -91,3 +92,22 @@ class DefaultAccessEntry(models.Model): def __unicode__(self): return u'%s: %s' % (self.content_type, self.content_object) + + +class CreatorSingletonManager(SingletonManager): + def passthru_check(self, holder, creator=None): + if isinstance(holder, self.model): + # TODO: raise explicit error if is instance and creator=None + return creator + else: + return holder + +class CreatorSingleton(Singleton): + objects = CreatorSingletonManager() + + def __unicode__(self): + return ugettext('Creator') + + class Meta: + verbose_name = _(u'creator') + verbose_name_plural = _(u'creator') From 70953882a7c7c73bbea360bf078ffd92393d63ee Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Sat, 21 Jan 2012 03:57:26 -0400 Subject: [PATCH 05/10] Implement ClassHolderSelectionForm and HolderSelectionForm forms Subclassed from BaseHolderSelectionForm --- apps/acls/forms.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/acls/forms.py b/apps/acls/forms.py index f4b7b0b4b7..3f2c048320 100644 --- a/apps/acls/forms.py +++ b/apps/acls/forms.py @@ -9,13 +9,14 @@ from common.utils import get_object_name from common.models import AnonymousUserSingleton from .classes import AccessHolder +from .models import CreatorSingleton def _as_choice_list(holders): return sorted([(AccessHolder.encapsulate(holder).gid, get_object_name(holder, display_object_type=False)) for holder in holders], key=lambda x: x[1]) -class HolderSelectionForm(forms.Form): +class BaseHolderSelectionForm(forms.Form): holder_gid = forms.ChoiceField( label=_(u'New holder') ) @@ -30,6 +31,7 @@ class HolderSelectionForm(forms.Form): users = set(User.objects.filter(is_active=True)) - set(staff_users) - set(super_users) - set(current_holders) roles = set(Role.objects.all()) - set(current_holders) groups = set(Group.objects.all()) - set(current_holders) + special = set(self.special_holders) - set(current_holders) non_holder_list = [] if users: @@ -41,11 +43,16 @@ class HolderSelectionForm(forms.Form): if roles: non_holder_list.append((_(u'Roles'), _as_choice_list(list(roles)))) - non_holder_list.append((_(u'Special'), _as_choice_list([AnonymousUserSingleton.objects.get()]))) + if special: + non_holder_list.append((_(u'Special'), _as_choice_list(list(special)))) - super(HolderSelectionForm, self).__init__(*args, **kwargs) + super(BaseHolderSelectionForm, self).__init__(*args, **kwargs) self.fields['holder_gid'].choices = non_holder_list + +class HolderSelectionForm(BaseHolderSelectionForm): + special_holders = [AnonymousUserSingleton.objects.get()] + -class ClassHolderSelectionForm(HolderSelectionForm): - pass +class ClassHolderSelectionForm(BaseHolderSelectionForm): + special_holders = [AnonymousUserSingleton.objects.get(), CreatorSingleton.objects.get()] From 95f56cbd5a66ff5dedaae6d6d697c4ad34cdcdb5 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Sat, 21 Jan 2012 04:03:58 -0400 Subject: [PATCH 06/10] Enable default acls setup upon creation for documents, folders, tags and smart_links --- apps/acls/utils.py | 12 +++++++++--- apps/folders/views.py | 2 ++ apps/linking/views.py | 2 ++ apps/sources/models.py | 2 +- apps/tags/views.py | 2 ++ 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/acls/utils.py b/apps/acls/utils.py index 4da462d7c7..2d9c9a92e7 100644 --- a/apps/acls/utils.py +++ b/apps/acls/utils.py @@ -1,24 +1,30 @@ from __future__ import absolute_import +import logging + from django.contrib.contenttypes.models import ContentType +from common.models import AnonymousUserSingleton + from .models import AccessEntry, DefaultAccessEntry, CreatorSingleton from .classes import EncapsulatedObject, AccessHolder, ClassAccessHolder +logger = logging.getLogger(__name__) -def apply_default_acls(self, obj, actor=None): + +def apply_default_acls(obj, actor=None): + logger.debug('actor, init: %s' % actor) if isinstance(obj, EncapsulatedObject): obj = obj.source_object if actor: actor = AnonymousUserSingleton.objects.passthru_check(actor) - #actor_type = ContentType.objects.get_for_model(actor) content_type = ContentType.objects.get_for_model(obj) for default_acl in DefaultAccessEntry.objects.filter(content_type=content_type): holder = CreatorSingleton.objects.passthru_check(default_acl.holder_object, actor) - + access_entry = AccessEntry( permission=default_acl.permission, holder_object=holder, diff --git a/apps/folders/views.py b/apps/folders/views.py index 0bd85d2790..3c4a494aa6 100644 --- a/apps/folders/views.py +++ b/apps/folders/views.py @@ -17,6 +17,7 @@ from permissions import Permission from common.utils import encapsulate from acls.models import AccessEntry from acls.views import acl_list_for +from acls.utils import apply_default_acls from .models import Folder from .forms import FolderForm, FolderListForm @@ -64,6 +65,7 @@ def folder_create(request): if form.is_valid(): folder, created = Folder.objects.get_or_create(user=request.user, title=form.cleaned_data['title']) if created: + apply_default_acls(folder, request.user) messages.success(request, _(u'Folder created successfully')) return HttpResponseRedirect(reverse('folder_list')) else: diff --git a/apps/linking/views.py b/apps/linking/views.py index 07e1d42351..2153e3c3fc 100644 --- a/apps/linking/views.py +++ b/apps/linking/views.py @@ -17,6 +17,7 @@ from documents.permissions import PERMISSION_DOCUMENT_VIEW from permissions.models import Permission from acls.views import acl_list_for from acls.models import AccessEntry, PermissionDenied +from acls.utils import apply_default_acls from .models import SmartLink, SmartLinkCondition from .conf.settings import SHOW_EMPTY_SMART_LINKS @@ -147,6 +148,7 @@ def smart_link_create(request): form = SmartLinkForm(request.POST) if form.is_valid(): document_group = form.save() + apply_default_acls(document_group, request.user) messages.success(request, _(u'Smart link: %s created successfully.') % document_group) return HttpResponseRedirect(reverse('document_group_list')) else: diff --git a/apps/sources/models.py b/apps/sources/models.py index d6a997daf9..d7fb8f7161 100644 --- a/apps/sources/models.py +++ b/apps/sources/models.py @@ -77,7 +77,7 @@ class BaseModel(models.Model): document.document_type = document_type document.save() - apply_default_acls(document, document, user) + apply_default_acls(document, user) if metadata_dict_list: save_metadata_list(metadata_dict_list, document, create=True) diff --git a/apps/tags/views.py b/apps/tags/views.py index 615c248829..712ecd86c9 100644 --- a/apps/tags/views.py +++ b/apps/tags/views.py @@ -17,6 +17,7 @@ from documents.permissions import PERMISSION_DOCUMENT_VIEW from common.utils import encapsulate from acls.models import AccessEntry, PermissionDenied from acls.views import acl_list_for, acl_new_holder_for +from acls.utils import apply_default_acls from .forms import TagListForm, TagForm from .models import TagProperties @@ -44,6 +45,7 @@ def tag_create(request): tag = Tag(name=tag_name) tag.save() TagProperties(tag=tag, color=form.cleaned_data['color']).save() + apply_default_acls(tag, request.user) messages.success(request, _(u'Tag created succesfully.')) return HttpResponseRedirect(redirect_url) From bf9073fdce79af5bb41cb50b6c5db037ec0fe2e6 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Sat, 21 Jan 2012 04:04:13 -0400 Subject: [PATCH 07/10] Add creator content type icon --- apps/acls/literals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/acls/literals.py b/apps/acls/literals.py index 5d86e0a013..0b867021a9 100644 --- a/apps/acls/literals.py +++ b/apps/acls/literals.py @@ -9,4 +9,5 @@ CONTENT_TYPE_ICON_MAP = { 'taggit.tag': 'tag_blue', 'linking.smartlink': 'page_link', 'common.anonymoususersingleton': 'user', + 'acls.creatorsingleton': 'user', } From 7714e62e9720219015d99a347dbd89d5f87c2dbb Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Sat, 21 Jan 2012 04:04:42 -0400 Subject: [PATCH 08/10] Display proper unicode result for anonymous user class and creator class --- apps/acls/classes.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/acls/classes.py b/apps/acls/classes.py index 2b4719dc95..89802566ea 100644 --- a/apps/acls/classes.py +++ b/apps/acls/classes.py @@ -102,7 +102,7 @@ class EncapsulatedObject(object): def __init__(self, source_object): self.content_type = ContentType.objects.get_for_model(source_object) - self.ct_fullname = '%s.%s' % (self.content_type.app_label, self.content_type.name) + self.ct_fullname = '%s.%s' % (self.content_type.app_label, self.content_type.model) if isinstance(source_object, ModelBase): # Class @@ -116,9 +116,12 @@ class EncapsulatedObject(object): def __unicode__(self): if isinstance(self.source_object, ModelBase): return capfirst(unicode(self.source_object._meta.verbose_name_plural)) - elif self.ct_fullname == 'auth.user': return u'%s %s' % (self.source_object._meta.verbose_name, self.source_object.get_full_name()) + elif self.ct_fullname == 'common.anonymoususersingleton': + return unicode(self.source_object) + elif self.ct_fullname == 'acls.creatorsingleton': + return unicode(self.source_object) else: return u'%s %s' % (self.source_object._meta.verbose_name, self.source_object) From f37500b0b4dcd58d1eaae3b8bc54daeeedef3cdb Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Sat, 21 Jan 2012 04:06:48 -0400 Subject: [PATCH 09/10] Allow returning on the db stored permissions --- apps/acls/managers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/acls/managers.py b/apps/acls/managers.py index e46082e2e9..1fe21cf231 100644 --- a/apps/acls/managers.py +++ b/apps/acls/managers.py @@ -61,14 +61,14 @@ class AccessEntryManager(models.Manager): access_entry.delete() return True - def has_access(self, permission, actor, obj): + def has_access(self, permission, actor, obj, db_only=False): """ Returns whether an actor has a specific permission for an object """ obj = get_source_object(obj) actor = get_source_object(actor) - if isinstance(actor, User): + if isinstance(actor, User) and db_only == False: if actor.is_superuser or actor.is_staff: return True @@ -144,7 +144,7 @@ class AccessEntryManager(models.Manager): return holder_list - def get_holder_permissions_for(self, obj, actor): + def get_holder_permissions_for(self, obj, actor, db_only=False): """ Returns a list of actors that hold at least one permission for a specific object @@ -152,7 +152,7 @@ class AccessEntryManager(models.Manager): logger.debug('obj: %s' % obj) logger.debug('actor: %s' % actor) - if isinstance(actor, User): + if isinstance(actor, User) and db_only == False: if actor.is_superuser or actor.is_staff: return Permission.objects.all() From ec461c4f9b70fe970592e46d1d21a4feb5ae94ee Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Sat, 21 Jan 2012 04:07:23 -0400 Subject: [PATCH 10/10] Update acl views to use encapsulated objects --- apps/acls/views.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/apps/acls/views.py b/apps/acls/views.py index 45af37d086..cfd4fc046c 100644 --- a/apps/acls/views.py +++ b/apps/acls/views.py @@ -24,7 +24,7 @@ from .models import AccessEntry, DefaultAccessEntry from .classes import (AccessHolder, AccessObject, AccessObjectClass, ClassAccessHolder) from .widgets import object_w_content_type_icon -from .forms import HolderSelectionForm +from .forms import HolderSelectionForm, ClassHolderSelectionForm from .api import get_class_permissions_for logger = logging.getLogger(__name__) @@ -47,7 +47,7 @@ def acl_list_for(request, obj, extra_context=None): 'title': _(u'access control lists for: %s' % obj), 'extra_columns': [ {'name': _(u'holder'), 'attribute': encapsulate(lambda x: object_w_content_type_icon(x.source_object))}, - {'name': _(u'permissions'), 'attribute': encapsulate(lambda x: _permission_titles(AccessEntry.objects.get_holder_permissions_for(obj, x.source_object)))}, + {'name': _(u'permissions'), 'attribute': encapsulate(lambda x: _permission_titles(AccessEntry.objects.get_holder_permissions_for(obj, x.source_object, db_only=True)))}, ], 'hide_object': True, 'access_object': AccessObject.encapsulate(obj), @@ -78,7 +78,8 @@ def acl_detail(request, access_object_gid, holder_object_gid): except ObjectDoesNotExist: raise Http404 - return acl_detail_for(request, holder.source_object, access_object.source_object) + #return acl_detail_for(request, holder.source_object, access_object.source_object) + return acl_detail_for(request, holder, access_object) def acl_detail_for(request, actor, obj): @@ -87,8 +88,7 @@ def acl_detail_for(request, actor, obj): except PermissionDenied: AccessEntry.objects.check_accesses([ACLS_VIEW_ACL], actor, obj) - permission_list = get_class_permissions_for(obj) - + permission_list = get_class_permissions_for(obj.source_object) #TODO : get all globally assigned permission, new function get_permissions_for_holder (roles aware) subtemplates_list = [ { @@ -105,7 +105,7 @@ def acl_detail_for(request, actor, obj): {'name': _(u'label'), 'attribute': 'label'}, { 'name':_(u'has permission'), - 'attribute': encapsulate(lambda permission: two_state_template(AccessEntry.objects.has_access(permission, actor, obj))) + 'attribute': encapsulate(lambda permission: two_state_template(AccessEntry.objects.has_access(permission, actor, obj, db_only=True))) }, ], 'hide_object': True, @@ -114,15 +114,15 @@ def acl_detail_for(request, actor, obj): ] context = { - 'object': obj, + 'object': obj.source_object, 'subtemplates_list': subtemplates_list, 'multi_select_as_buttons': True, 'multi_select_item_properties': { 'permission_pk': lambda x: x.pk, - 'holder_gid': lambda x: AccessHolder(actor).gid, - 'object_gid': lambda x: AccessObject(obj).gid, + 'holder_gid': lambda x: actor.gid, + 'object_gid': lambda x: obj.gid, }, - 'access_object': AccessObject.encapsulate(obj), + 'access_object': obj, 'navigation_object_list': [ {'object': 'object'}, {'object': 'access_object'} @@ -425,7 +425,6 @@ def acl_class_acl_detail(request, access_object_class_gid, holder_object_gid): except ObjectDoesNotExist: raise Http404 - #permission_list = list(access_object_class.get_class_permissions()) permission_list = get_class_permissions_for(access_object_class.content_type.model_class()) #TODO : get all globally assigned permission, new function get_permissions_for_holder (roles aware) subtemplates_list = [ @@ -468,7 +467,7 @@ def acl_class_new_holder_for(request, access_object_class_gid): access_object_class = AccessObjectClass.get(gid=access_object_class_gid) if request.method == 'POST': - form = HolderSelectionForm(request.POST) + form = ClassHolderSelectionForm(request.POST) if form.is_valid(): try: access_holder = ClassAccessHolder.get(form.cleaned_data['holder_gid']) @@ -477,7 +476,7 @@ def acl_class_new_holder_for(request, access_object_class_gid): except ObjectDoesNotExist: raise Http404 else: - form = HolderSelectionForm(current_holders=DefaultAccessEntry.objects.get_holders_for(access_object_class)) + form = ClassHolderSelectionForm(current_holders=DefaultAccessEntry.objects.get_holders_for(access_object_class)) context = { 'form': form,