Skip to content

Commit

Permalink
PARTIAL: More progress but still not totally satisfied.
Browse files Browse the repository at this point in the history
  • Loading branch information
toastdriven committed Mar 19, 2012
1 parent 0991975 commit 77c8c7b
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 71 deletions.
158 changes: 131 additions & 27 deletions tastypie/authorization.py
@@ -1,3 +1,6 @@
from tastypie.exceptions import TastypieError, Unauthorized


class Authorization(object):
"""
A base class that provides no permissions checking.
Expand All @@ -13,29 +16,81 @@ def __get__(self, instance, owner):

def apply_limits(self, request, object_list):
"""
A means of narrowing a list of objects on a per-user/request basis.
Deprecated.
"""
raise TastypieError("Authorization classes no longer support `apply_limits`. Please update to using `read_list`.")

Default simply returns the unaltered list.
def read_list(self, object_list, bundle):
"""
Returns a list of all the objects a user is allowed to read.
Should return an empty list if none are allowed.
Returns the entire list by default.
"""
return object_list

def to_read(self, bundle):
def read_single(self, object_list, bundle):
"""
Checks if the user is authorized to perform the request. If ``object``
is provided, it can do additional row-level checks.
Returns either ``True`` if the user is allowed to read the object in
question or throw ``Unauthorized`` if they are not.
Should return either ``True`` if allowed, ``False`` if not or an
``HttpResponse`` if you need something custom.
Returns ``True`` by default.
"""
return True

def to_add(self, bundle):
def create_list(self, object_list, bundle):
"""
Unimplemented, as Tastypie never creates entire new lists, but
present for consistency & possible extension.
"""
raise NotImplementedError("Tastypie has not way to determine if all objects should be allowed to be created.")

def create_single(self, object_list, bundle):
"""
Returns either ``True`` if the user is allowed to create the object in
question or throw ``Unauthorized`` if they are not.
Returns ``True`` by default.
"""
return True

def to_change(self, bundle):
def update_list(self, object_list, bundle):
"""
Returns a list of all the objects a user is allowed to update.
Should return an empty list if none are allowed.
Returns the entire list by default.
"""
return object_list

def update_single(self, object_list, bundle):
"""
Returns either ``True`` if the user is allowed to update the object in
question or throw ``Unauthorized`` if they are not.
Returns ``True`` by default.
"""
return True

def to_delete(self, bundle):
def delete_list(self, object_list, bundle):
"""
Returns a list of all the objects a user is allowed to delete.
Should return an empty list if none are allowed.
Returns the entire list by default.
"""
return object_list

def delete_single(self, object_list, bundle):
"""
Returns either ``True`` if the user is allowed to delete the object in
question or throw ``Unauthorized`` if they are not.
Returns ``True`` by default.
"""
return True


Expand All @@ -45,16 +100,28 @@ class ReadOnlyAuthorization(Authorization):
Only allows ``GET`` requests.
"""
def to_read(self, bundle):
def read_list(self, object_list, bundle):
return object_list

def read_single(self, object_list, bundle):
return True

def to_add(self, bundle):
def create_list(self, object_list, bundle):
return []

def create_single(self, object_list, bundle):
return False

def to_change(self, bundle):
def update_list(self, object_list, bundle):
return []

def update_single(self, object_list, bundle):
return False

def to_delete(self, bundle):
def delete_list(self, object_list, bundle):
return []

def delete_single(self, object_list, bundle):
return False


Expand All @@ -63,49 +130,86 @@ class DjangoAuthorization(Authorization):
Uses permission checking from ``django.contrib.auth`` to map
``POST / PUT / DELETE / PATCH`` to their equivalent Django auth
permissions.
"""
def base_checks(self, bundle):
klass = self.resource_meta.object_class
Both the list & detail variants simply check the model they're based
on, as that's all the more granular Django's permission setup gets.
"""
def base_checks(self, model_klass):
# If it doesn't look like a model, we can't check permissions.
if not klass or not getattr(klass, '_meta', None):
if not model_klass or not getattr(model_klass, '_meta', None):
return False

# User must be logged in to check permissions.
if not hasattr(bundle.request, 'user'):
return False

return klass
return model_klass

def read_list(self, object_list, bundle):
klass = self.base_checks(object_list.model)

if klass is False:
return False

# GET-style methods are always allowed.
return True

def to_read(self, bundle):
klass = self.base_checks(bundle)
def read_single(self, object_list, bundle):
klass = self.base_checks(bundle.obj.__class__)

if klass is False:
return False

# GET-style methods are always allowed.
return True

def to_add(self, bundle):
klass = self.base_checks(bundle)
def create_list(self, object_list, bundle):
klass = self.base_checks(object_list.model)

if klass is False:
return False

permission = '%s.add_%s' % (klass._meta.app_label, klass._meta.module_name)
return bundle.request.user.has_perm(permission)

def create_single(self, object_list, bundle):
klass = self.base_checks(bundle.obj.__class__)

if klass is False:
return False

permission = '%s.add_%s' % (klass._meta.app_label, klass._meta.module_name)
return bundle.request.user.has_perm(permission)

def to_change(self, bundle):
klass = self.base_checks(bundle)
def update_list(self, object_list, bundle):
klass = self.base_checks(object_list.model)

if klass is False:
return False

permission = '%s.change_%s' % (klass._meta.app_label, klass._meta.module_name)
return bundle.request.user.has_perm(permission)

def update_single(self, object_list, bundle):
klass = self.base_checks(bundle.obj.__class__)

if klass is False:
return False

permission = '%s.change_%s' % (klass._meta.app_label, klass._meta.module_name)
return bundle.request.user.has_perm(permission)

def to_delete(self, bundle):
klass = self.base_checks(bundle)
def delete_list(self, object_list, bundle):
klass = self.base_checks(object_list.model)

if klass is False:
return False

permission = '%s.delete_%s' % (klass._meta.app_label, klass._meta.module_name)
return bundle.request.user.has_perm(permission)

def delete_single(self, object_list, bundle):
klass = self.base_checks(bundle.obj.__class__)

if klass is False:
return False
Expand Down
20 changes: 15 additions & 5 deletions tastypie/exceptions.py
Expand Up @@ -25,6 +25,16 @@ class NotFound(TastypieError):
pass


class Unauthorized(TastypieError):
"""
Raised when the request object is not accessible to the user.
This is different than the ``tastypie.http.HttpUnauthorized`` & is handled
differently internally.
"""
pass


class ApiFieldError(TastypieError):
"""
Raised when there is a configuration error with a ``ApiField``.
Expand All @@ -42,7 +52,7 @@ class UnsupportedFormat(TastypieError):
class BadRequest(TastypieError):
"""
A generalized exception for indicating incorrect request parameters.
Handled specially in that the message tossed by this exception will be
presented to the end user.
"""
Expand Down Expand Up @@ -73,14 +83,14 @@ class ImmediateHttpResponse(TastypieError):
"""
This exception is used to interrupt the flow of processing to immediately
return a custom HttpResponse.
Common uses include::
* for authentication (like digest/OAuth)
* for throttling
"""
response = HttpResponse("Nothing provided.")

def __init__(self, response):
self.response = response

0 comments on commit 77c8c7b

Please sign in to comment.