diff --git a/pinry/core/api.py b/pinry/core/api.py index 1105745..259951b 100644 --- a/pinry/core/api.py +++ b/pinry/core/api.py @@ -1,5 +1,6 @@ from tastypie import fields from tastypie.authorization import DjangoAuthorization +from tastypie.exceptions import Unauthorized from tastypie.resources import ModelResource from django_images.models import Thumbnail @@ -7,6 +8,37 @@ from pinry.core.models import User from pinry.pins.models import Image, Pin +class PinryAuthorization(DjangoAuthorization): + """ + Pinry-specific Authorization backend with object-level permission checking. + """ + def update_detail(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.") + + permission = '%s.change_%s' % (klass._meta.app_label, klass._meta.module_name) + + if not bundle.request.user.has_perm(permission, bundle.obj): + raise Unauthorized("You are not allowed to access that resource.") + + return True + + def delete_detail(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.") + + permission = '%s.delete_%s' % (klass._meta.app_label, klass._meta.module_name) + + if not bundle.request.user.has_perm(permission, bundle.obj): + raise Unauthorized("You are not allowed to access that resource.") + + return True + + class UserResource(ModelResource): gravatar = fields.CharField(readonly=True) @@ -87,4 +119,4 @@ class PinResource(ModelResource): resource_name = 'pin' include_resource_uri = False always_return_data = True - authorization = DjangoAuthorization() + authorization = PinryAuthorization() diff --git a/pinry/core/auth/backends.py b/pinry/core/auth/backends.py index a1b0c02..149defc 100644 --- a/pinry/core/auth/backends.py +++ b/pinry/core/auth/backends.py @@ -1,6 +1,8 @@ from django.core.validators import email_re from pinry.core.models import User +from pinry.pins.models import Pin + class CombinedAuthBackend(object): def authenticate(self, username=None, password=None): @@ -22,4 +24,12 @@ class CombinedAuthBackend(object): try: return User.objects.get(pk=user_id) except User.DoesNotExist: - return None \ No newline at end of file + 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/pinry/core/tests.py b/pinry/core/tests.py index 9a57cb2..e13b819 100644 --- a/pinry/core/tests.py +++ b/pinry/core/tests.py @@ -37,7 +37,6 @@ class ImageResourceTest(ResourceTestCase): self.client = Client() def test_list_detail(self): - self.maxDiff = None image = Image.objects.get(pk=1) thumbnail = filter_generator_for('thumbnail')(image) standard = filter_generator_for('standard')(image) @@ -107,28 +106,44 @@ class PinResourceTest(ResourceTestCase): self.assertEqual(Pin.objects.count(), 3) self.assertEquals(Tag.objects.count(), 4) - def test_put_details_unauthenticated(self): + def test_put_detail_unauthenticated(self): + self.api_client.client.logout() uri = '/api/v1/pin/{}/'.format(self.pin_1.pk) response = self.api_client.put(uri, format='json', data={}) self.assertHttpUnauthorized(response) - def test_put_details_unauthorized(self): + def test_put_detail_unauthorized(self): uri = '/api/v1/pin/{}/'.format(self.pin_1.pk) User.objects.create_user('test', 'test@example.com', 'test') self.api_client.client.login(username='test', password='test') response = self.api_client.put(uri, format='json', data={}) self.assertHttpUnauthorized(response) - # def test_put_details(self): - # uri = '/api/v1/pin/{}/'.format(self.pin_1.pk) - # original = self.deserialize(self.api_client.get(uri, format='json')) - # new = original.copy() - # new['description'] = 'Updated description' - # - # self.assertEqual(Pin.objects.count(), 2) - # response = self.api_client.put(uri, format='json', data=new) - # self.assertHttpAccepted(response) - # self.assertEqual(Pin.objects.count(), 2) + def test_put_detail(self): + uri = '/api/v1/pin/{}/'.format(self.pin_1.pk) + original = self.deserialize(self.api_client.get(uri, format='json')) + new = {'description': 'Updated description'} + + response = self.api_client.put(uri, format='json', data=new) + self.assertHttpAccepted(response) + self.assertEqual(Pin.objects.count(), 2) + self.assertEqual(Pin.objects.get(pk=self.pin_1.pk).description, new['description']) + + def test_delete_detail_unauthenticated(self): + uri = '/api/v1/pin/{}/'.format(self.pin_1.pk) + self.api_client.client.logout() + self.assertHttpUnauthorized(self.api_client.delete(uri)) + + def test_delete_detail_unauthorized(self): + uri = '/api/v1/pin/{}/'.format(self.pin_1.pk) + User.objects.create_user('test', 'test@example.com', 'test') + self.api_client.client.login(username='test', password='test') + self.assertHttpUnauthorized(self.api_client.delete(uri)) + + def test_delete_detail(self): + uri = '/api/v1/pin/{}/'.format(self.pin_1.pk) + self.assertHttpAccepted(self.api_client.delete(uri)) + self.assertEqual(Pin.objects.count(), 1) def test_get_list_json_ordered(self): pin = Pin.objects.latest('id')