aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/permissions.py
blob: 2d8a30e91bc7f797939a2deee8ece31b8164a15f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
var array = [].constructor;

function toJson(obj, pretty){
  var buf = [];
  toJsonArray(buf, obj, pretty ? "\n  " : _null, []);
  return buf.join('');
}

function fromJson(json) {
  if (!json) return json;
  try {
    var p = parser(json, true);
    var expression =  p.primary();
    p.assertAllConsumed();
    return expression();
  } catch (e) {
    error("fromJson error: ", json, e);
    throw e;
  }
}

angular['toJson'] = toJson;
angular['fromJson'] = fromJson;

function toJsonArray(buf, obj, pretty, stack){
  if (typeof obj == "object") {
    if (includes(stack, obj)) {
      buf.push("RECURSION");
      return;
    }
    stack.push(obj);
  }
  var type = typeof obj;
  if (obj === _null) {
    buf.push($null);
  } else<
"""
Provides a set of pluggable permission policies.
"""
from __future__ import unicode_literals
import inspect
import warnings

SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']

from django.http import Http404
from rest_framework.compat import oauth2_provider_scope, oauth2_constants, guardian


class BasePermission(object):
    """
    A base class from which all permission classes should inherit.
    """

    def has_permission(self, request, view):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True

    def has_object_permission(self, request, view, obj):
        """
        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


class AllowAny(BasePermission):
    """
    Allow any access.
    This isn't strictly required, since you could use an empty
    permission_classes list, but it's useful because it makes the intention
    more explicit.
    """
    def has_permission(self, request, view):
        return True


class IsAuthenticated(BasePermission):
    """
    Allows access only to authenticated users.
    """

    def has_permission(self, request, view):
        if request.user and request.user.is_authenticated():
            return True
        return False


class IsAdminUser(BasePermission):
    """
    Allows access only to admin users.
    """

    def has_permission(self, request, view):
        if request.user and request.user.is_staff:
            return True
        return False


class IsAuthenticatedOrReadOnly(BasePermission):
    """
    The request is authenticated as a user, or is a read-only request.
    """

    def has_permission(self, request, view):
        if (request.method in SAFE_METHODS or
            request.user and
            request.user.is_authenticated()):
            return True
        return False


class DjangoModelPermissions(BasePermission):
    """
    The request is authenticated using `django.contrib.auth` permissions.
    See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions

    It ensures that the user is authenticated, and has the appropriate
    `add`/`change`/`delete` permissions on the model.

    This permission can only be applied against view classes that
    provide a `.model` or `.queryset` attribute.
    """

    # Map methods into required permission codes.
    # Override this if you need to also provide 'view' permissions,
    # or if you want to provide custom permission codes.
    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'],
    }

    authenticated_users_only = True

    def get_required_permissions(self, method, model_cls):
        """
        Given a model and an HTTP method, return the list of permission
        codes that the user is required to have.
        """
        kwargs = {
            'app_label': model_cls._meta.app_label,
            'model_name': model_cls._meta.module_name
        }
        return [perm % kwargs for perm in self.perms_map[method]]

    def has_permission(self, request, view):
        model_cls = getattr(view, 'model', None)
        queryset = getattr(view, 'queryset', None)

        if model_cls is None and queryset is not None:
            model_cls = queryset.model

        # Workaround to ensure DjangoModelPermissions are not applied
        # to the root view when using DefaultRouter.
        if model_cls is None and getattr(view, '_ignore_model_permissions', False):
            return True

        assert model_cls, ('Cannot apply DjangoModelPermissions on a view that'
                           ' does not have `.model` or `.queryset` property.')

        perms = self.get_required_permissions(request.method, model_cls)

        if (request.user and
            (request.user.is_authenticated() or not self.authenticated_users_only) and
            request.user.has_perms(perms)):
            return True
        return False


class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions):
    """
    Similar to DjangoModelPermissions, except that anonymous users are
    allowed read-only access.
    """
    authenticated_users_only = False


class DjangoObjectLevelModelPermissions(DjangoModelPermissions):
    """
    Basic object level permissions utilizing django-guardian.
    """

    def __init__(self):
        assert guardian, 'Using DjangoObjectLevelModelPermissions, but django-guardian is not installed'

    action_perm_map = {
        'list': 'read',
        'retrieve': 'read',
        'create': 'add',
        'partial_update': 'change',
        'update': 'change',
        'destroy': 'delete',
    }

    def _get_names(self, view):
        model_cls = getattr(view, 'model', None)
        queryset = getattr(view, 'queryset', None)

        if model_cls is None and queryset is not None:
            model_cls = queryset.model
        if not model_cls:  # no model, no model based permissions
            return None
        model_name = model_cls._meta.module_name
        return model_name

    def has_permission(self, request, view):
        if view.action == 'list':
            user = request.user
            queryset = view.get_queryset()
            model_name = self._get_names(view)
            view.queryset = guardian.shortcuts.get_objects_for_user(user, 'read_' + model_name, queryset)  #TODO: move to filter
        return super(DjangoObjectLevelModelPermissions, self).has_permission(request, view)

    def has_object_permission(self, request, view, obj):
        user = request.user
        model_name = self._get_names(view)
        action = self.action_perm_map.get(view.action)

        assert action, "Tried to determine object permissions but no action specified in view"

        perm = "{action}_{model_name}".format(action=action, model_name=model_name)
        check = user.has_perm(perm, obj)
        if not check:
            raise Http404
        return user.has_perm(perm, obj)


class TokenHasReadWriteScope(BasePermission):
    """
    The request is authenticated as a user and the token used has the right scope
    """

    def has_permission(self, request, view):
        token = request.auth
        read_only = request.method in SAFE_METHODS

        if not token:
            return False

        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)

        assert False, ('TokenHasReadWriteScope requires either the'
        '`OAuthAuthentication` or `OAuth2Authentication` authentication '
        'class to be used.')