django-restricted-resource-2015.11/0000755000175000017500000000000012625352560017026 5ustar neilneil00000000000000django-restricted-resource-2015.11/setup.cfg0000644000175000017500000000017712625352560020654 0ustar neilneil00000000000000[upload_docs] upload-dir = build/sphinx/html [upload] sign = True [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 django-restricted-resource-2015.11/PKG-INFO0000644000175000017500000000142012625352560020120 0ustar neilneil00000000000000Metadata-Version: 1.1 Name: django-restricted-resource Version: 2015.11 Summary: Base model for Django that adds simple and efficient ownership and access control. Home-page: http://git.linaro.org/git/lava/django-restricted-resource.git Author: Linaro Limited Author-email: lava-team@linaro.org License: LGPLv3 Description: UNKNOWN Keywords: django,ownership,models Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 django-restricted-resource-2015.11/setup.py0000755000175000017500000000353212625352345020547 0ustar neilneil00000000000000#!/usr/bin/env python # Copyright (C) 2010 Linaro Limited # # Author: Zygmunt Krynicki # # This file is part of django-restricted-resource. # # django-restricted-resource is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 # as published by the Free Software Foundation # # django-restricted-resource is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with django-restricted-resource. If not, see . from setuptools import setup setup( name='django-restricted-resource', version="2015.11", author="Linaro Limited", author_email="lava-team@linaro.org", description="Base model for Django that adds simple and efficient ownership and access control.", url='http://git.linaro.org/git/lava/django-restricted-resource.git', test_suite='django_restricted_resource.test_project.tests.run_tests', license='LGPLv3', keywords=['django', 'ownership', 'models'], classifiers=[ "Development Status :: 4 - Beta", 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Operating System :: OS Independent", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", ], zip_safe=True, packages=['django_restricted_resource'], # dependencies install_requires=['django >= 1.6'], tests_require=['django-testscenarios >= 0.7.1'], ) django-restricted-resource-2015.11/MANIFEST.in0000644000175000017500000000006212322744720020557 0ustar neilneil00000000000000include django_restricted_resource/test_project/* django-restricted-resource-2015.11/django_restricted_resource.egg-info/0000755000175000017500000000000012625352560026121 5ustar neilneil00000000000000django-restricted-resource-2015.11/django_restricted_resource.egg-info/PKG-INFO0000644000175000017500000000142012625352560027213 0ustar neilneil00000000000000Metadata-Version: 1.1 Name: django-restricted-resource Version: 2015.11 Summary: Base model for Django that adds simple and efficient ownership and access control. Home-page: http://git.linaro.org/git/lava/django-restricted-resource.git Author: Linaro Limited Author-email: lava-team@linaro.org License: LGPLv3 Description: UNKNOWN Keywords: django,ownership,models Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 django-restricted-resource-2015.11/django_restricted_resource.egg-info/SOURCES.txt0000644000175000017500000000167412625352560030015 0ustar neilneil00000000000000MANIFEST.in setup.cfg setup.py django_restricted_resource/__init__.py django_restricted_resource/managers.py django_restricted_resource/models.py django_restricted_resource/test_utils.py django_restricted_resource/tests.py django_restricted_resource/utils.py django_restricted_resource.egg-info/PKG-INFO django_restricted_resource.egg-info/SOURCES.txt django_restricted_resource.egg-info/dependency_links.txt django_restricted_resource.egg-info/pbr.json django_restricted_resource.egg-info/requires.txt django_restricted_resource.egg-info/top_level.txt django_restricted_resource.egg-info/zip-safe django_restricted_resource/test_project/__init__.py django_restricted_resource/test_project/__init__.pyc django_restricted_resource/test_project/manage.py django_restricted_resource/test_project/settings.py django_restricted_resource/test_project/settings.pyc django_restricted_resource/test_project/tests.py django_restricted_resource/test_project/tests.pycdjango-restricted-resource-2015.11/django_restricted_resource.egg-info/top_level.txt0000644000175000017500000000003312625352560030647 0ustar neilneil00000000000000django_restricted_resource django-restricted-resource-2015.11/django_restricted_resource.egg-info/dependency_links.txt0000644000175000017500000000000112625352560032167 0ustar neilneil00000000000000 django-restricted-resource-2015.11/django_restricted_resource.egg-info/zip-safe0000644000175000017500000000000112314612257027546 0ustar neilneil00000000000000 django-restricted-resource-2015.11/django_restricted_resource.egg-info/requires.txt0000644000175000017500000000001612625352560030516 0ustar neilneil00000000000000django >= 1.6 django-restricted-resource-2015.11/django_restricted_resource.egg-info/pbr.json0000644000175000017500000000005712607706002027573 0ustar neilneil00000000000000{"is_release": false, "git_version": "c76130b"}django-restricted-resource-2015.11/django_restricted_resource/0000755000175000017500000000000012625352560024427 5ustar neilneil00000000000000django-restricted-resource-2015.11/django_restricted_resource/test_utils.py0000644000175000017500000001335412332731621027200 0ustar neilneil00000000000000# Copyright (C) 2010 Linaro Limited # # Author: Zygmunt Krynicki # # This file is part of django-restricted-resource. # # django-restricted-resource is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 # as published by the Free Software Foundation # # django-restricted-resource is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with django-restricted-resource. If not, see . import inspect import itertools from django.contrib.auth.models import (AnonymousUser, User, Group) from django.db import models from django_testscenarios.ubertest import (TestCase, TestCaseWithScenarios) from django_restricted_resource.models import RestrictedResource class TestCaseWithInvariants(TestCaseWithScenarios): """ TestCase that generates test scenarios based on the possible combination of invariants. Invariants are declared by the 'invariants' variable defined in class scope. The variable must point to a dictionary. Each element of that dictionary becomes a test invariant. The value of that dictionary may be one of: * list: Each of the elements is checked. If all elements are of simple type (int, bool, string) then parameter description will contain them as-is, otherwise they will be described as "variant-N" * dict: Each of the values is checked. In addition the keys will be used to construct meaningful parameter descriptions. """ def _get_invariants(self): return getattr(self, 'invariants', {}) def _get_scenarios(self): if hasattr(self, "scenarios"): return self.scenarios self.scenarios = self._get_all_possible_scenarios( self._get_invariants()) return self.scenarios def _dict_to_keys_and_values(self, d): pairs = d.items() keys = [first for (first, second) in pairs] values = [second for (first, second) in pairs] return keys, values def _get_all_possible_scenarios(self, invariants): scenarios = [] invariant_keys, invariant_values = self._dict_to_keys_and_values(invariants) scenario_ids_list = [] scenario_params_list = [] for value in invariant_values: if isinstance(value, list): if all([isinstance(variant, (int, bool, str)) for variant in value]): scenario_ids_list.append([repr(variant) for variant in value]) else: scenario_ids_list.append(['variant-%d' % variant for variant in range(len(value))]) scenario_params_list.append(value) elif isinstance(value, dict): k, v = self._dict_to_keys_and_values(value) scenario_ids_list.append(k) scenario_params_list.append(v) for scenario_ids, scenario_params in itertools.izip( itertools.product(*scenario_ids_list), itertools.product(*scenario_params_list)): parameters = dict(zip(invariant_keys, scenario_params)) name = ", ".join([ "%s=%s" % (invariant_key, param_id) for (invariant_key, param_id) in zip(invariant_keys, scenario_ids)]) scenario = (name, parameters) scenarios.append(scenario) return scenarios def setUp(self): super(TestCaseWithScenarios, self).setUp() # Evaluate lazy invariants now that `self' is around for invariant in self._get_invariants().iterkeys(): value = getattr(self, invariant) if inspect.isfunction(value): value = value(self) setattr(self, invariant, value) class ExampleRestrictedResource(RestrictedResource): """ Dummy model to get non-abstract model that inherits from RestrictedResource """ name = models.CharField(max_length=100, null=True, unique=True) class Meta: ordering = ['name'] class FixtureHelper(object): def getUniqueString(self, prefix=None, max_length=None): value = super(FixtureHelper, self).getUniqueString(prefix) if max_length is not None: if len(value) >= max_length: value = super(FixtureHelper, self).getUniqueString("short") if len(value) >= max_length: raise ValueError("Unable to satisfy request for random string with max_length=%d" % max_length) return value def getUniqueStringForField(self, model, field_name): return self.getUniqueString(max_length=model._meta.get_field_by_name(field_name)[0].max_length) def getUniqueUser(self, is_active=True): user = User.objects.create( username=self.getUniqueStringForField(User, "username"), is_active=is_active) self.addCleanup(user.delete) return user def getUniqueGroup(self): group = Group.objects.create( name=self.getUniqueStringForField(Group, "name")) self.addCleanup(group.delete) return group def getUniqueResource(self, owner, is_public, name=None): resource = ExampleRestrictedResource.objects.create( owner=owner, is_public=is_public, name=name) self.addCleanup(resource.delete) return resource def add_resources(self, resources, owner, is_public): for name in resources: self.getUniqueResource( name=name, owner=owner, is_public=is_public) django-restricted-resource-2015.11/django_restricted_resource/test_project/0000755000175000017500000000000012625352560027134 5ustar neilneil00000000000000django-restricted-resource-2015.11/django_restricted_resource/test_project/manage.py0000755000175000017500000000247712321741146030746 0ustar neilneil00000000000000#!/usr/bin/env python # Copyright (C) 2010 Linaro Limited # # Author: Zygmunt Krynicki # # This file is part of django-restricted-resource. # # django-restricted-resource is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 # as published by the Free Software Foundation # # django-restricted-resource is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with django-restricted-resource. If not, see . import os import sys def find_sources(): base_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "../..") if os.path.exists(os.path.join(base_path, "django_restricted_resource")): sys.path.insert(0, base_path) if __name__ == "__main__": find_sources() settings_module = "django_restricted_resource.test_project.settings" os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings_module) from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) django-restricted-resource-2015.11/django_restricted_resource/test_project/settings.pyc0000644000175000017500000000102712323040147031477 0ustar neilneil00000000000000ó fÂGSc @s?ddlmZeƒjedddddddgƒƒd S( iÿÿÿÿ(t gen_settingst SECRET_KEYtneeded_but_not_securedtINSTALLED_APPSsdjango.contrib.authsdjango.contrib.contenttypessdjango.contrib.sessionstdjango_restricted_resourceN(tdjango_testproject.settingsRtlocalstupdate(((sq/home/neil/code/lava/packaging/git/django-restricted-resource/django_restricted_resource/test_project/settings.pyts django-restricted-resource-2015.11/django_restricted_resource/test_project/__init__.pyc0000644000175000017500000000031012323040147031370 0ustar neilneil00000000000000ó fÂGSc@sdS(N((((sq/home/neil/code/lava/packaging/git/django-restricted-resource/django_restricted_resource/test_project/__init__.pytsdjango-restricted-resource-2015.11/django_restricted_resource/test_project/__init__.py0000644000175000017500000000140712321741146031242 0ustar neilneil00000000000000# Copyright (C) 2010 Linaro Limited # # Author: Zygmunt Krynicki # # This file is part of django-restricted-resource. # # django-restricted-resource is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 # as published by the Free Software Foundation # # django-restricted-resource is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with django-restricted-resource. If not, see . django-restricted-resource-2015.11/django_restricted_resource/test_project/tests.pyc0000644000175000017500000000112712323040147031002 0ustar neilneil00000000000000ó fÂGSc@s3ddlmZd„Zedkr/eƒndS(iÿÿÿÿ(t run_tests_forcCs tdƒS(Ns0django_restricted_resource.test_project.settings(R(((sn/home/neil/code/lava/packaging/git/django-restricted-resource/django_restricted_resource/test_project/tests.pyt run_testsst__main__N(tdjango_testproject.testsRRt__name__(((sn/home/neil/code/lava/packaging/git/django-restricted-resource/django_restricted_resource/test_project/tests.pyts  django-restricted-resource-2015.11/django_restricted_resource/test_project/settings.py0000644000175000017500000000213112321741146031336 0ustar neilneil00000000000000# Copyright (C) 2010 Linaro Limited # # Author: Zygmunt Krynicki # # This file is part of django-restricted-resource. # # django-restricted-resource is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 # as published by the Free Software Foundation # # django-restricted-resource is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with django-restricted-resource. If not, see . from django_testproject.settings import gen_settings locals().update( gen_settings( SECRET_KEY='needed_but_not_secured', INSTALLED_APPS=[ 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django_restricted_resource', ] ) ) django-restricted-resource-2015.11/django_restricted_resource/test_project/tests.py0000644000175000017500000000171712321741146030651 0ustar neilneil00000000000000# Copyright (C) 2010, 2011 Linaro Limited # # Author: Zygmunt Krynicki # # This file is part of django-restricted-resource. # # django-restricted-resource is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 # as published by the Free Software Foundation # # django-restricted-resource is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with django-restricted-resource. If not, see . from django_testproject.tests import run_tests_for def run_tests(): return run_tests_for("django_restricted_resource.test_project.settings") if __name__ == '__main__': run_tests() django-restricted-resource-2015.11/django_restricted_resource/utils.py0000644000175000017500000000216512321741146026140 0ustar neilneil00000000000000# Copyright (C) 2010 Linaro Limited # # Author: Zygmunt Krynicki # # This file is part of django-restricted-resource. # # django-restricted-resource is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 # as published by the Free Software Foundation # # django-restricted-resource is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with django-restricted-resource. If not, see . def filter_bogus_users(user): """ Check for bogus instances of user (anonymous, disabled, etc) and change them to None. This simplifies user comparison and handling. If the user is `None' then he's not trusted, period, no need to check deeper. """ if user is not None and user.is_authenticated() and user.is_active: return user django-restricted-resource-2015.11/django_restricted_resource/__init__.py0000644000175000017500000000145212321741146026535 0ustar neilneil00000000000000# Copyright (C) 2010 Linaro Limited # # Author: Zygmunt Krynicki # # This file is part of django-restricted-resource. # # django-restricted-resource is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 # as published by the Free Software Foundation # # django-restricted-resource is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with django-restricted-resource. If not, see . __version__ = (0, 2, 8, "dev", 0) django-restricted-resource-2015.11/django_restricted_resource/models.py0000644000175000017500000001630212321741217026260 0ustar neilneil00000000000000# Copyright (C) 2010 Linaro Limited # # Author: Zygmunt Krynicki # # This file is part of django-restricted-resource. # # django-restricted-resource is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 # as published by the Free Software Foundation # # django-restricted-resource is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with django-restricted-resource. If not, see . """ Unit tests for django-restricted-resources """ from django.core.exceptions import ValidationError from django.contrib.auth.models import (AnonymousUser, User, Group) from django.db import models from django_restricted_resource.utils import filter_bogus_users from django_restricted_resource.managers import RestrictedResourceManager class RestrictedResource(models.Model): """ Model for handling resource ownership and access rights. This is not a generic permission framework. Instead it's a rigid and simple but arguably useful model with the following properties: * resources are owned by exactly one user or exactly one group * resources are accessible to anyone if marked as public * resources are accessible to owner if owned by user * resources are accessible to members if owned by group * What the access actually permits is not defined This similar to the UNIX permission system with the following differences: * There are no separate READ, WRITE, EXECUTE nor STICKY bits per any of the possible user classes (OWNER, GROUP MEMBER, OTHER). Instead the application is free to use any desired policy to define actions a particular user may invoke on a resource that he can already access. * There is a dedicated "public" flag indicating that anyone can access the specified resource. * (important) Enumeration of resources that user can access is very efficient when implemented in a relational database. """ NO_ACCESS, PUBLIC_ACCESS, PRIVATE_ACCESS, SHARED_ACCESS = range(4) user = models.ForeignKey(User, null=True, blank=True) group = models.ForeignKey(Group, null=True, blank=True) is_public = models.BooleanField(default=False) objects = RestrictedResourceManager() class Meta: abstract = True def save(self, *args, **kwargs): """ Validate and save the resource """ self.clean() return super(RestrictedResource, self).save(*args, **kwargs) def clean(self): """ Make sure that ownership constraint is met. RestrictedResource must be owned by exactly one user or exactly one group. """ if self.user is not None and self.group is not None: raise ValidationError( 'Cannot be owned by user and group at the same time') if self.user is None and self.group is None: raise ValidationError( 'Must be owned by someone') if hasattr(models.Model, "clean"): return super(RestrictedResource, self).clean() def _set_owner(self, owner): if not isinstance(owner, (User, Group)): raise TypeError("Expected User or Group instance as argument") if isinstance(owner, User): self.user = owner self.group = None else: self.group = owner self.user = None def _get_owner(self): return self.user or self.group owner = property( _get_owner, _set_owner, None, "Principal owner (either User or Group) of this restricted resource") def is_accessible_by(self, principal): """ Check if the resource can be accessed by the specified principal. """ return self.get_access_type(principal) != self.NO_ACCESS def is_owned_by(self, principal): """ Check if the resource is owned by the specified principal. The principal may be any user or group """ if principal is None: return False # If the principal is an User then this object is owned by that user or # the group the user belongs to. if isinstance(principal, (User, AnonymousUser, type(None))): user = filter_bogus_users(principal) if user is None: return False if self.user is not None: return self.user == user else: return self.group in user.groups.all() # If the principal is a Group then this object is owned by that group elif isinstance(principal, Group): group = principal return self.group == group else: raise TypeError("Expected User or Group instance as argument") def get_access_type(self, principal): """ Determine the mode of access the principal has to this object: Possible access types: NO_ACCESS No access to restricted resource PUBLIC_ACCESS Granted to: * User (including owner) accessing publicly available resource * Groups accessing publicly available resource * None (including Anonymous users and blocked users) accessing publicly available resources PRIVATE_ACCESS Granted to: * Owner accessing otherwise private resource SHARED_ACCESS Granted to: * User accessing restricted data via membership * Groups accessing restricted data they own """ if isinstance(principal, (User, AnonymousUser, type(None))): user = filter_bogus_users(principal) return self._get_access_type_for_user(user) elif isinstance(principal, Group): group = principal return self._get_access_type_for_group(group) else: raise TypeError("Expected User or Group instance as argument") def _get_access_type_for_user(self, user): # Allow anyone to access public data if self.is_public: return self.PUBLIC_ACCESS # Allow access for owners if self.user is not None and self.user == user: return self.PRIVATE_ACCESS # Allow access for team members if self.group is not None and user is not None: if self.group in user.groups.all(): return self.SHARED_ACCESS # Finally, disallow return self.NO_ACCESS def _get_access_type_for_group(self, group): # Allow anyone to access public data if self.is_public: return self.PUBLIC_ACCESS # Allow access to group if group is the owner if self.group is not None and self.group == group: return self.SHARED_ACCESS # Finally, disallow return self.NO_ACCESS django-restricted-resource-2015.11/django_restricted_resource/managers.py0000644000175000017500000001251212625014407026572 0ustar neilneil00000000000000# Copyright (C) 2010 Linaro Limited # # Author: Zygmunt Krynicki # # This file is part of django-restricted-resource. # # django-restricted-resource is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 # as published by the Free Software Foundation # # django-restricted-resource is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with django-restricted-resource. If not, see . """ Module with model manager for RestrictedResource """ import django from django.contrib.auth.models import (User, AnonymousUser, Group) from django.db.models import Q from django.db import models from django_restricted_resource.utils import filter_bogus_users if django.VERSION < (1, 7): QuerySet = models.query.QuerySet else: QuerySet = models.QuerySet class RestrictedResourceQuerySet(QuerySet): """ Model manager for RestrictedResource and subclasses that has additional methods. The extra methods are: * owned_by_principal(principal) * accessible_by_principal(principal) Both methods allow for efficient enumeration of owned and accessible resources (respectively). There is one extra convenience method: * accessible_by_anyone() That is equivalent to accessible_by_principal(None) but is more expressive in intent. """ def owned_by_principal(self, principal): """ Return a QuestySet of RestrictedResource instances that are owned by the specified principal, which can be a User or Group instance. """ if isinstance(principal, (User, AnonymousUser, type(None))): user = filter_bogus_users(principal) return self._owned_by_user(user) elif isinstance(principal, Group): group = principal return self._owned_by_group(group) else: raise TypeError("Expected User or Group instance as argument") def accessible_by_principal(self, principal): """ Return a QuerySet of RestrictedResource instances that can be accessed by specified principal. The principal may be None, AnonymousUser, valid User or valid Group. Note: All objects that can be accessed are returned, not just objects with exclusive access. To determine why a particular principal can access a particular resource use RestrictedResource.get_access_type(principal). """ if isinstance(principal, (User, AnonymousUser, type(None))): user = filter_bogus_users(principal) return self._accessible_by_user(user) elif isinstance(principal, Group): group = principal return self._accessible_by_group(group) else: raise TypeError("Expected User or Group instance as argument") def accessible_by_anyone(self): """ Return a QuerySet of BundleStream instances that can be accessed by anyone. """ return self._accessible_by_user(None) def _accessible_by_user(self, user): if user is None: return self.filter(is_public=True) else: return self.filter( Q(is_public=True) | Q(user=user) | Q(group__in=user.groups.all())) def _accessible_by_group(self, group): # None gets mapped to users, there is no chance to get None here assert group is not None return self.filter( Q(is_public=True) | Q(group=group)) def _owned_by_user(self, user): if user is None: return self.none() else: return self.filter( Q(user=user) | Q(group__in=user.groups.all())) def _owned_by_group(self, group): # None gets mapped to users, there is no chance to get None here assert group is not None return self.filter(group=group) if django.VERSION >= (1, 7): RestrictedResourceManager = models.Manager.from_queryset( RestrictedResourceQuerySet) else: # TODO: Remove once the support for django 1.6 has been dropped. class RestrictedResourceManager(models.Manager): def get_query_set(self): return RestrictedResourceQuerySet(self.model, using=self._db) def owned_by_principal(self, principal): return self.get_query_set().owned_by_principal(principal) def accessible_by_principal(self, principal): return self.get_query_set().accessible_by_principal(principal) def accessible_by_anyone(self): return self.get_query_set().accessible_by_anyone() def _accessible_by_user(self, user): return self.get_query_set()._accessible_by_user(user) def _accessible_by_group(self, group): return self.get_query_set()._accessible_by_group(group) def _owned_by_user(self, user): return self.get_query_set()._owned_by_user(user) def _owned_by_group(self, group): return self.get_query_set()._owned_by_group(group) django-restricted-resource-2015.11/django_restricted_resource/tests.py0000644000175000017500000004121212405654570026145 0ustar neilneil00000000000000# Copyright (C) 2010 Linaro Limited # # Author: Zygmunt Krynicki # # This file is part of django-restricted-resource. # # django-restricted-resource is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 # as published by the Free Software Foundation # # django-restricted-resource is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with django-restricted-resource. If not, see . """ Unit tests for django_restricted_resource application """ from django.contrib.auth.models import (AnonymousUser, User, Group) from django.core.exceptions import ValidationError from django_testscenarios.ubertest import TestCaseWithScenarios from django_restricted_resource.test_utils import ( ExampleRestrictedResource, FixtureHelper, TestCase, TestCaseWithInvariants, TestCaseWithScenarios, ) from django_restricted_resource.models import RestrictedResource import django if hasattr(django, 'setup'): django.setup() class ResourceCleanTests(FixtureHelper, TestCase): def test_clean_raises_exception_when_owner_is_not_set(self): resource = RestrictedResource() self.assertRaises(ValidationError, resource.clean) def test_clean_raises_exception_when_both_user_and_group_is_set(self): user = self.getUniqueUser() group = self.getUniqueGroup() resource = RestrictedResource(user=user, group=group) self.assertRaises(ValidationError, resource.clean) def test_clean_is_okay_when_just_user_set(self): user = self.getUniqueUser() resource = RestrictedResource(user=user) self.assertEqual(resource.clean(), None) def test_clean_is_okay_when_just_group_set(self): group = self.getUniqueGroup() resource = RestrictedResource(group=group) self.assertEqual(resource.clean(), None) class ResourceOwnerTest(FixtureHelper, TestCase): """ Tests for the owner property """ def test_user_is_owner(self): user = self.getUniqueUser() resource = ExampleRestrictedResource(user=user) self.assertEqual(resource.owner, user) def test_group_is_owner(self): group = self.getUniqueGroup() resource = ExampleRestrictedResource(group=group) self.assertEqual(resource.owner, group) def test_owner_can_be_changed_to_group(self): group = self.getUniqueGroup() resource = ExampleRestrictedResource() resource.owner = group resource.save() self.assertEqual(resource.group, group) self.assertEqual(resource.user, None) def test_owner_can_be_changed_to_user(self): user = self.getUniqueUser() resource = ExampleRestrictedResource() resource.owner = user resource.save() self.assertEqual(resource.group, None) self.assertEqual(resource.user, user) def test_owner_cannot_be_none(self): resource = ExampleRestrictedResource() self.assertRaises(TypeError, setattr, resource, "owner", None) class PrincipalTypeIsChecked( FixtureHelper, TestCaseWithInvariants): invariants = dict( principal=[1, object(), {}, [], "string"], ) def test_owner_assignment(self): resource = ExampleRestrictedResource() self.assertRaises(TypeError, setattr, resource, "owner", self.principal) def test_is_accessible_by(self): resource = ExampleRestrictedResource() self.assertRaises(TypeError, resource.is_accessible_by, self.principal) def test_is_owned_by(self): resource = ExampleRestrictedResource() self.assertRaises(TypeError, resource.is_owned_by, self.principal) def test_manger_owned_by_set(self): self.assertRaises(TypeError, ExampleRestrictedResource.objects.owned_by_principal, self.principal) def test_manger_accessible_by_set(self): self.assertRaises(TypeError, ExampleRestrictedResource.objects.accessible_by_principal, self.principal) class OthersDoNotOwnResource( FixtureHelper, TestCaseWithInvariants): """ RestrictedResource.is_owned_by() returns False for everyone but the owner """ invariants = dict( owner=dict( user=lambda self: self.getUniqueUser(), group=lambda self: self.getUniqueGroup(), ), accessing_principal=dict( nothing=None, anonymous_user=AnonymousUser(), inactive_user=lambda self: self.getUniqueUser(is_active=False), unrelated_user=lambda self: self.getUniqueUser(), unrelated_group=lambda self: self.getUniqueGroup(), ), is_public=[True, False], ) def test(self): resource = self.getUniqueResource( owner=self.owner, is_public=self.is_public) self.assertFalse( resource.is_owned_by(self.accessing_principal)) class OwnerOwnsResource( FixtureHelper, TestCaseWithInvariants): """ RestrictedResource.is_owned_by() returns True for the owner """ invariants = dict( owner=dict( user=lambda self: self.getUniqueUser(), group=lambda self: self.getUniqueGroup(), ), is_public=[True, False], ) def test(self): resource = self.getUniqueResource( owner=self.owner, is_public=self.is_public) self.assertTrue(resource.is_owned_by(self.owner)) class GroupMemberOwnsResource(FixtureHelper): def test(self): """ RestrictedResource.is_owned_by() returns True for owning group members """ group = self.getUniqueGroup() user = self.getUniqueUser() user.groups.add(group) resource = self.getUniqueResource(owner=group) self.assertTrue(resource.is_owned_by(user)) class ResourceManagerOwnerSetFindsNoMatchesForOthers( FixtureHelper, TestCaseWithInvariants): """ RestrictedResourceManager.owned_by_principal() does not return anything for non-owners """ invariants = dict( owner=dict( user=lambda self: self.getUniqueUser(), group=lambda self: self.getUniqueGroup(), ), accessing_principal=dict( nothing=None, anonymous_user=AnonymousUser(), inactive_user=lambda self: self.getUniqueUser(is_active=False), unrelated_user=lambda self: self.getUniqueUser(), unrelated_group=lambda self: self.getUniqueGroup(), ), is_public=[True, False], ) def test(self): manager = ExampleRestrictedResource.objects resource = self.getUniqueResource( owner=self.owner, is_public=self.is_public) result = manager.owned_by_principal(self.accessing_principal) self.assertEqual(result.count(), 0) class ResourceManagerOwnerSetFindsMatchesForOwner( FixtureHelper, TestCaseWithInvariants): invariants = dict( num_objects=[0, 10, 500], owner=dict( user=lambda self: self.getUniqueUser(), group=lambda self: self.getUniqueGroup(), ), is_public=[True, False], ) def test(self): for i in range(self.num_objects): self.getUniqueResource( owner=self.owner, name=str(i), is_public=self.is_public) manager = ExampleRestrictedResource.objects result = manager.owned_by_principal(self.owner) self.assertEqual(result.count(), self.num_objects) class ResourceManagerOwnerSetFindsMatchesForOwnerGroupMember( FixtureHelper, TestCaseWithInvariants): invariants = dict( num_objects=[0, 10, 500], is_public=[True, False], ) def test(self): group = self.getUniqueGroup() user = self.getUniqueUser() user.groups.add(group) for i in range(self.num_objects): self.getUniqueResource( owner=group, name=str(i), is_public=self.is_public) manager = ExampleRestrictedResource.objects result = manager.owned_by_principal(user) self.assertEqual(result.count(), self.num_objects) class EveryoneHasPublicAccessToPublicResources( FixtureHelper, TestCaseWithInvariants): """ Tests for the get_access_type() method """ invariants = dict( owner=dict( user=lambda self: self.getUniqueUser(), group=lambda self: self.getUniqueGroup(), ), accessing_principal=dict( nothing=None, anonymous_user=AnonymousUser(), inactive_user=lambda self: self.getUniqueUser(is_active=False), unrelated_user=lambda self: self.getUniqueUser(), unrelated_group=lambda self: self.getUniqueGroup(), owner=lambda self: self.owner, ), is_public=[True, False], ) def setUp(self): super(EveryoneHasPublicAccessToPublicResources, self).setUp() self.resource = self.getUniqueResource( owner=self.owner, is_public=True) def test_get_access_type(self): self.assertEqual( self.resource.get_access_type(self.accessing_principal), self.resource.PUBLIC_ACCESS) def test_is_accessible_by(self): self.assertTrue( self.resource.is_accessible_by(self.accessing_principal)) class NobodyButTheOwnerHasAccessToNonPublicUserResources( FixtureHelper, TestCaseWithInvariants): """ Tests for the get_access_type() method """ invariants = dict( accessing_principal=dict( nothing=None, anonymous_user=AnonymousUser(), inactive_user=lambda self: self.getUniqueUser(is_active=False), unrelated_user=lambda self: self.getUniqueUser(), unrelated_group=lambda self: self.getUniqueGroup(), ) ) def test_everyone_else(self): owner = self.getUniqueUser() resource = self.getUniqueResource(is_public=False, owner=owner) self.assertEqual( resource.get_access_type(self.accessing_principal), resource.NO_ACCESS) class OwnerHasPrivateAccessToNonPublicUserResources( FixtureHelper, TestCase): """ Tests for the get_access_type() method """ def test_owner(self): owner = self.getUniqueUser() resource = self.getUniqueResource(is_public=False, owner=owner) self.assertEqual( resource.get_access_type(owner), resource.PRIVATE_ACCESS) class GroupMembersGetSharedAccessToNonPublicGroupResources( FixtureHelper, TestCase): def test_get_access_type_for_owning_group(self): owner = self.getUniqueGroup() resource = self.getUniqueResource(owner=owner, is_public=False) self.assertEqual( resource.get_access_type(owner), resource.SHARED_ACCESS) def test_get_access_type_for_related_user(self): owner = self.getUniqueGroup() resource = self.getUniqueResource(owner=owner, is_public=False) related_user = self.getUniqueUser() related_user.groups.add(owner) self.assertEqual( resource.get_access_type(related_user), resource.SHARED_ACCESS) class ResourceManagerAccessibleSetFindsOnlyPublicElementsForNonOwners( FixtureHelper, TestCaseWithInvariants): invariants = dict( owner=dict( user=lambda self: self.getUniqueUser(), group=lambda self: self.getUniqueGroup(), ), accessing_principal=dict( nothing=None, anonymous_user=AnonymousUser(), inactive_user=lambda self: self.getUniqueUser(is_active=False), unrelated_user=lambda self: self.getUniqueUser(), unrelated_group=lambda self: self.getUniqueGroup(), ) ) def test(self): manager = ExampleRestrictedResource.objects self.add_resources(["a", "b", "c"], owner=self.owner, is_public=True) self.add_resources(["x", "y", "z"], owner=self.owner, is_public=False) resources = manager.accessible_by_principal(self.accessing_principal) self.assertEqual( [res.name for res in resources], ["a", "b", "c"]) class ResourceManagerAccessibleByAnyoneSetFindsOnlyPublicElements( FixtureHelper, TestCaseWithInvariants): invariants = dict( owner=dict( user=lambda self: self.getUniqueUser(), group=lambda self: self.getUniqueGroup(), ), ) def test(self): self.add_resources(["a", "b", "c"], owner=self.owner, is_public=True) self.add_resources(["x", "y", "z"], owner=self.owner, is_public=False) manager = ExampleRestrictedResource.objects resources = manager.accessible_by_anyone() self.assertEqual( [res.name for res in resources], ["a", "b", "c"]) class ResourceManagerAccessibleByPrincipalSetFindsAllOwnedlements( FixtureHelper, TestCaseWithInvariants): invariants = dict( owner=dict( user=lambda self: self.getUniqueUser(), group=lambda self: self.getUniqueGroup(), ), ) def test(self): self.add_resources(["a", "b", "c"], owner=self.owner, is_public=True) self.add_resources(["x", "y", "z"], owner=self.owner, is_public=False) manager = ExampleRestrictedResource.objects resources = manager.accessible_by_principal(self.owner) self.assertEqual( [res.name for res in resources], ["a", "b", "c", "x", "y", "z"]) class ResourceManagerAccessibleSetTests( FixtureHelper, TestCase): def setUp(self): super(ResourceManagerAccessibleSetTests, self).setUp() self.user = self.getUniqueUser() self.unrealted_group = self.getUniqueGroup() self.group = self.getUniqueGroup() self.unrelated_user = self.getUniqueUser() self.manager = ExampleRestrictedResource.objects def add_resources(self, resources, owner, public): for name in resources: ExampleRestrictedResource.objects.create( owner=owner, name=name, is_public=public) def test_accessible_by_anyone_returns_only_public_objects(self): self.add_resources(["a", "b", "c"], owner=self.user, public=True) self.add_resources(["x", "y", "z"], owner=self.user, public=False) resources = ExampleRestrictedResource.objects.accessible_by_anyone() self.assertEqual( [res.name for res in resources], ["a", "b", "c"]) def test_accessible_by_prinipal_for_owner(self): self.add_resources(["a", "b", "c"], owner=self.user, public=True) self.add_resources(["x", "y", "z"], owner=self.user, public=False) resources = ExampleRestrictedResource.objects.accessible_by_principal( self.user) self.assertEqual( [res.name for res in resources], ["a", "b", "c", "x", "y", "z"]) def test_accessible_by_prinipal_for_group(self): self.add_resources(["a", "b", "c"], owner=self.group, public=True) self.add_resources(["x", "y", "z"], owner=self.group, public=False) resources = ExampleRestrictedResource.objects.accessible_by_principal( self.group) self.assertEqual( [res.name for res in resources], ["a", "b", "c", "x", "y", "z"]) def test_accessible_by_prinicpal_for_unrelated_user(self): self.add_resources(["a", "b", "c"], owner=self.user, public=True) self.add_resources(["x", "y", "z"], owner=self.user, public=False) resources = ExampleRestrictedResource.objects.accessible_by_principal( self.unrelated_user) self.assertEqual( [res.name for res in resources], ["a", "b", "c"]) def test_accessible_by_prinicpal_for_unrelated_user_without_any_public_objects(self): self.add_resources(["x", "y", "z"], owner=self.user, public=False) resources = self.manager.accessible_by_principal(self.unrelated_user) self.assertEqual([res.name for res in resources], []) def test_accessible_by_principal_for_group_and_member(self): self.add_resources(["a", "b", "c"], owner=self.group, public=True) self.add_resources(["x", "y", "z"], owner=self.group, public=False) self.user.groups.add(self.group) resources = self.manager.accessible_by_principal(self.user) self.assertEqual( [res.name for res in resources], ["a", "b", "c", "x", "y", "z"])