diff --git a/Pipfile b/Pipfile index 23e86c0..77bb400 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,7 @@ requests = "*" django-taggit = "*" django-braces = "*" django-compressor = "*" -django-tastypie = ">=0.13.0,<0.14" +django-tastypie = "*" mock = "*" factory-boy = "<2.0,>=1.3" gunicorn = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 8522b86..4a60cef 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c632e45ac592ec9c159c042db8c52e221ae133ade94cd11fcfb124a5fd5b9dd0" + "sha256": "68c12441be13a252f7fdd1b5532c7befdfa56fc8307ab75a2b197a1946a54bf2" }, "pipfile-spec": 6, "requires": {}, @@ -69,11 +69,10 @@ }, "django-tastypie": { "hashes": [ - "sha256:bd4eb8a9da6c2dd70c946edc460776a64f40fae01132520a26133361bd43bf3f", - "sha256:e404b9ac24ab400015047f5503fae1732d9e944a7152693fe902c0a905f250cc" + "sha256:1fbf61ec7467eec70bd1abcb14e3b1dc67e47cc3642ad16ed8a3709f4140678b" ], "index": "pypi", - "version": "==0.13.3" + "version": "==0.14.1" }, "factory-boy": { "hashes": [ diff --git a/core/api.py b/core/api.py index 217849b..108ce50 100644 --- a/core/api.py +++ b/core/api.py @@ -10,36 +10,101 @@ from .models import Pin, Image from users.models import User +def _is_pin_owner(obj_or_list, user): + assert obj_or_list is not None + if not isinstance(obj_or_list, (tuple, list)): + obj_or_list = (obj_or_list,) + results = tuple( + obj.submitter == user + for obj in obj_or_list + if isinstance(obj, Pin) + ) + if len(results) <= 0: + raise ValueError( + "You should never check permission on %s with this function." + % obj_or_list + ) + return all(results) + + +def _is_authenticated_and_owner(object_list, bundle): + if bundle.request.user.is_anonymous(): + return object_list.none() + return object_list.filter(submitter=bundle.request.user) + + class PinryAuthorization(DjangoAuthorization): """ Pinry-specific Authorization backend with object-level permission checking. """ - def update_detail(self, object_list, bundle): + def _is_obj_owner(self, object_list, bundle): klass = self.base_checks(bundle.request, bundle.obj.__class__) if klass is False: raise Unauthorized("You are not allowed to access that resource.") + return _is_pin_owner(bundle.obj, bundle.request.user) - permission = '%s.change_%s' % (klass._meta.app_label, klass._meta.model_name) - - if not bundle.request.user.has_perm(permission, bundle.obj): - raise Unauthorized("You are not allowed to access that resource.") + def read_list(self, object_list, bundle): + # This assumes a ``QuerySet`` from ``ModelResource``. + return object_list + def read_detail(self, object_list, bundle): + """ + User can always read detail of any Pin object. + """ return True + def create_detail(self, object_list, bundle): + return self._is_obj_owner(object_list, bundle) + + def update_detail(self, object_list, bundle): + return self._is_obj_owner(object_list, bundle) + def delete_detail(self, object_list, bundle): - klass = self.base_checks(bundle.request, bundle.obj.__class__) + return self._is_obj_owner(object_list, bundle) - if klass is False: - raise Unauthorized("You are not allowed to access that resource.") + def update_list(self, object_list, bundle): + return _is_authenticated_and_owner(object_list, bundle) - permission = '%s.delete_%s' % (klass._meta.app_label, klass._meta.model_name) + def delete_list(self, object_list, bundle): + return _is_authenticated_and_owner(object_list, bundle) - if not bundle.request.user.has_perm(permission, bundle.obj): - raise Unauthorized("You are not allowed to access that resource.") +class ImageAuthorization(DjangoAuthorization): + """ + Pinry-specific Authorization backend with object-level permission checking. + """ + def __init__(self): + DjangoAuthorization.__init__(self) + + def read_list(self, object_list, bundle): + return object_list + + def read_detail(self, object_list, bundle): + """ + User can always read detail of any Pin object. + """ return True + def create_detail(self, object_list, bundle): + return bundle.request.user.is_authenticated() + + def update_detail(self, object_list, bundle): + return bundle.request.user.is_authenticated() + + def delete_detail(self, object_list, bundle): + return bundle.request.user.is_authenticated() + + def update_list(self, object_list, bundle): + if not bundle.request.user.is_authenticated(): + return object_list.none() + return object_list + + def delete_list(self, object_list, bundle): + if not bundle.request.user.is_authenticated(): + return object_list.none() + return object_list + class UserResource(ModelResource): gravatar = fields.CharField(readonly=True) @@ -101,7 +166,7 @@ class ImageResource(ModelResource): include_resource_uri = False resource_name = 'image' queryset = Image.objects.all() - authorization = DjangoAuthorization() + authorization = ImageAuthorization() class PinResource(ModelResource): diff --git a/users/auth/backends.py b/users/auth/backends.py index 8efd152..e871f9e 100644 --- a/users/auth/backends.py +++ b/users/auth/backends.py @@ -33,11 +33,3 @@ class CombinedAuthBackend(object): return User.objects.get(pk=user_id) except User.DoesNotExist: return None - - def has_perm(self, user, perm, obj=None): - """ - A very simplistic authorization mechanism for now. Basically a pin owner can do anything with the pin. - """ - if obj and isinstance(obj, Pin): - return obj.submitter == user - return False diff --git a/users/tests.py b/users/tests.py index 892a6cc..8f3f7b0 100644 --- a/users/tests.py +++ b/users/tests.py @@ -34,21 +34,6 @@ class CombinedAuthBackendTest(TestCase): def test_authenticate_unknown_user(self): self.assertIsNone(self.backend.authenticate(username='wrong-username', password='wrong-password')) - @mock.patch('requests.get', mock_requests_get) - def test_has_perm_on_pin(self): - image = Image.objects.create_for_url('http://testserver/mocked/screenshot.png') - user = User.objects.get(username=self.username) - pin = Pin.objects.create(submitter=user, image=image) - self.assertTrue(self.backend.has_perm(user, 'add_pin', pin)) - - @mock.patch('requests.get', mock_requests_get) - def test_has_perm_on_pin_unauthorized(self): - image = Image.objects.create_for_url('http://testserver/mocked/screenshot.png') - user = User.objects.get(username=self.username) - other_user = User.objects.create_user('test', 'test@example.com', 'test') - pin = Pin.objects.create(submitter=user, image=image) - self.assertFalse(self.backend.has_perm(other_user, 'add_pin', pin)) - class CreateUserTest(TestCase): def test_create_post(self):