diff --git a/apps/acls/classes.py b/apps/acls/classes.py index f2b7c36ef2..89802566ea 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' @@ -95,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 @@ -109,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) diff --git a/apps/acls/forms.py b/apps/acls/forms.py index e5a55a006c..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,7 +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(BaseHolderSelectionForm): + special_holders = [AnonymousUserSingleton.objects.get(), CreatorSingleton.objects.get()] 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', } diff --git a/apps/acls/managers.py b/apps/acls/managers.py index ffe3d08d7a..1fe21cf231 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,19 +55,20 @@ 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): + def has_access(self, permission, actor, obj, db_only=False): """ 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 isinstance(actor, User) and db_only == False: if actor.is_superuser or actor.is_staff: return True @@ -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 @@ -148,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 @@ -156,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() @@ -211,8 +207,9 @@ class DefaultAccessEntryManager(models.Manager): content type is created. """ def get_holders_for(self, cls): - if isinstance(cls, EncapsulatedObject): - cls = cls.source_object + cls = get_source_object(cls) + #if isinstance(cls, EncapsulatedObject): + # cls = cls.source_object content_type = ContentType.objects.get_for_model(cls) holder_list = [] 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') diff --git a/apps/acls/utils.py b/apps/acls/utils.py new file mode 100644 index 0000000000..2d9c9a92e7 --- /dev/null +++ b/apps/acls/utils.py @@ -0,0 +1,33 @@ +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(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) + + 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/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, 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 52e828a4fe..d7fb8f7161 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, user) + if metadata_dict_list: save_metadata_list(metadata_dict_list, document, create=True) warnings = update_indexes(document) 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)