diff options
Diffstat (limited to 'rest_framework/permissions.py')
| -rw-r--r-- | rest_framework/permissions.py | 108 |
1 files changed, 67 insertions, 41 deletions
diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 1036663e..9069d315 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -2,13 +2,11 @@ Provides a set of pluggable permission policies. """ from __future__ import unicode_literals -import inspect -import warnings +from django.http import Http404 +from rest_framework.compat import get_model_name SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS'] -from rest_framework.compat import oauth2_provider_scope, oauth2_constants - class BasePermission(object): """ @@ -25,13 +23,6 @@ class BasePermission(object): """ Return `True` if permission is granted, `False` otherwise. """ - if len(inspect.getargspec(self.has_permission).args) == 4: - warnings.warn( - 'The `obj` argument in `has_permission` is deprecated. ' - 'Use `has_object_permission()` instead for object permissions.', - DeprecationWarning, stacklevel=2 - ) - return self.has_permission(request, view, obj) return True @@ -52,9 +43,7 @@ class IsAuthenticated(BasePermission): """ def has_permission(self, request, view): - if request.user and request.user.is_authenticated(): - return True - return False + return request.user and request.user.is_authenticated() class IsAdminUser(BasePermission): @@ -63,9 +52,7 @@ class IsAdminUser(BasePermission): """ def has_permission(self, request, view): - if request.user and request.user.is_staff: - return True - return False + return request.user and request.user.is_staff class IsAuthenticatedOrReadOnly(BasePermission): @@ -74,11 +61,11 @@ class IsAuthenticatedOrReadOnly(BasePermission): """ def has_permission(self, request, view): - if (request.method in SAFE_METHODS or + return ( + request.method in SAFE_METHODS or request.user and - request.user.is_authenticated()): - return True - return False + request.user.is_authenticated() + ) class DjangoModelPermissions(BasePermission): @@ -115,11 +102,14 @@ class DjangoModelPermissions(BasePermission): """ kwargs = { 'app_label': model_cls._meta.app_label, - 'model_name': model_cls._meta.module_name + 'model_name': get_model_name(model_cls) } return [perm % kwargs for perm in self.perms_map[method]] def has_permission(self, request, view): + # Note that `.model` attribute on views is deprecated, although we + # enforce the deprecation on the view `get_serializer_class()` and + # `get_queryset()` methods, rather than here. model_cls = getattr(view, 'model', None) queryset = getattr(view, 'queryset', None) @@ -136,11 +126,11 @@ class DjangoModelPermissions(BasePermission): perms = self.get_required_permissions(request.method, model_cls) - if (request.user and + return ( + request.user and (request.user.is_authenticated() or not self.authenticated_users_only) and - request.user.has_perms(perms)): - return True - return False + request.user.has_perms(perms) + ) class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions): @@ -151,24 +141,60 @@ class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions): authenticated_users_only = False -class TokenHasReadWriteScope(BasePermission): +class DjangoObjectPermissions(DjangoModelPermissions): """ - The request is authenticated as a user and the token used has the right scope + The request is authenticated using Django's object-level permissions. + It requires an object-permissions-enabled backend, such as Django Guardian. + + It ensures that the user is authenticated, and has the appropriate + `add`/`change`/`delete` permissions on the object using .has_perms. + + This permission can only be applied against view classes that + provide a `.model` or `.queryset` attribute. """ - def has_permission(self, request, view): - token = request.auth - read_only = request.method in SAFE_METHODS + perms_map = { + 'GET': [], + 'OPTIONS': [], + 'HEAD': [], + 'POST': ['%(app_label)s.add_%(model_name)s'], + 'PUT': ['%(app_label)s.change_%(model_name)s'], + 'PATCH': ['%(app_label)s.change_%(model_name)s'], + 'DELETE': ['%(app_label)s.delete_%(model_name)s'], + } - if not token: - return False + def get_required_object_permissions(self, method, model_cls): + kwargs = { + 'app_label': model_cls._meta.app_label, + 'model_name': get_model_name(model_cls) + } + return [perm % kwargs for perm in self.perms_map[method]] - if hasattr(token, 'resource'): # OAuth 1 - return read_only or not request.auth.resource.is_readonly - elif hasattr(token, 'scope'): # OAuth 2 - required = oauth2_constants.READ if read_only else oauth2_constants.WRITE - return oauth2_provider_scope.check(required, request.auth.scope) + def has_object_permission(self, request, view, obj): + model_cls = getattr(view, 'model', None) + queryset = getattr(view, 'queryset', None) - assert False, ('TokenHasReadWriteScope requires either the' - '`OAuthAuthentication` or `OAuth2Authentication` authentication ' - 'class to be used.') + if model_cls is None and queryset is not None: + model_cls = queryset.model + + perms = self.get_required_object_permissions(request.method, model_cls) + user = request.user + + if not user.has_perms(perms, obj): + # If the user does not have permissions we need to determine if + # they have read permissions to see 403, or not, and simply see + # a 404 response. + + if request.method in ('GET', 'OPTIONS', 'HEAD'): + # Read permissions already checked and failed, no need + # to make another lookup. + raise Http404 + + read_perms = self.get_required_object_permissions('GET', model_cls) + if not user.has_perms(read_perms, obj): + raise Http404 + + # Has read permissions. + return False + + return True |
