2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-10-15 16:27:50 +04:00
|
|
|
Provides a set of pluggable permission policies.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2013-09-08 08:18:52 +04:00
|
|
|
from django.http import Http404
|
2015-06-18 16:38:29 +03:00
|
|
|
|
2017-02-28 19:51:32 +03:00
|
|
|
from rest_framework import exceptions
|
2016-08-05 13:04:01 +03:00
|
|
|
|
2015-03-17 08:13:07 +03:00
|
|
|
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
2018-11-01 22:12:34 +03:00
|
|
|
class OperationHolderMixin:
|
|
|
|
def __and__(self, other):
|
|
|
|
return OperandHolder(AND, self, other)
|
|
|
|
|
|
|
|
def __or__(self, other):
|
|
|
|
return OperandHolder(OR, self, other)
|
|
|
|
|
|
|
|
def __rand__(self, other):
|
|
|
|
return OperandHolder(AND, other, self)
|
|
|
|
|
|
|
|
def __ror__(self, other):
|
|
|
|
return OperandHolder(OR, other, self)
|
|
|
|
|
2019-02-25 17:33:40 +03:00
|
|
|
def __invert__(self):
|
|
|
|
return SingleOperandHolder(NOT, self)
|
|
|
|
|
|
|
|
|
|
|
|
class SingleOperandHolder(OperationHolderMixin):
|
|
|
|
def __init__(self, operator_class, op1_class):
|
|
|
|
self.operator_class = operator_class
|
|
|
|
self.op1_class = op1_class
|
|
|
|
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
|
|
op1 = self.op1_class(*args, **kwargs)
|
|
|
|
return self.operator_class(op1)
|
|
|
|
|
2018-11-01 22:12:34 +03:00
|
|
|
|
|
|
|
class OperandHolder(OperationHolderMixin):
|
2018-10-03 17:36:24 +03:00
|
|
|
def __init__(self, operator_class, op1_class, op2_class):
|
|
|
|
self.operator_class = operator_class
|
|
|
|
self.op1_class = op1_class
|
|
|
|
self.op2_class = op2_class
|
|
|
|
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
|
|
op1 = self.op1_class(*args, **kwargs)
|
|
|
|
op2 = self.op2_class(*args, **kwargs)
|
|
|
|
return self.operator_class(op1, op2)
|
|
|
|
|
|
|
|
|
|
|
|
class AND:
|
|
|
|
def __init__(self, op1, op2):
|
|
|
|
self.op1 = op1
|
|
|
|
self.op2 = op2
|
|
|
|
|
|
|
|
def has_permission(self, request, view):
|
|
|
|
return (
|
2019-02-25 15:47:02 +03:00
|
|
|
self.op1.has_permission(request, view) and
|
2018-10-03 17:36:24 +03:00
|
|
|
self.op2.has_permission(request, view)
|
|
|
|
)
|
|
|
|
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
|
|
return (
|
2019-02-25 15:47:02 +03:00
|
|
|
self.op1.has_object_permission(request, view, obj) and
|
2018-10-03 17:36:24 +03:00
|
|
|
self.op2.has_object_permission(request, view, obj)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class OR:
|
|
|
|
def __init__(self, op1, op2):
|
|
|
|
self.op1 = op1
|
|
|
|
self.op2 = op2
|
|
|
|
|
|
|
|
def has_permission(self, request, view):
|
|
|
|
return (
|
2019-02-25 15:47:02 +03:00
|
|
|
self.op1.has_permission(request, view) or
|
2018-10-03 17:36:24 +03:00
|
|
|
self.op2.has_permission(request, view)
|
|
|
|
)
|
|
|
|
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
|
|
return (
|
2019-02-25 15:47:02 +03:00
|
|
|
self.op1.has_object_permission(request, view, obj) or
|
2018-10-03 17:36:24 +03:00
|
|
|
self.op2.has_object_permission(request, view, obj)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2019-02-25 17:33:40 +03:00
|
|
|
class NOT:
|
|
|
|
def __init__(self, op1):
|
|
|
|
self.op1 = op1
|
|
|
|
|
|
|
|
def has_permission(self, request, view):
|
|
|
|
return not self.op1.has_permission(request, view)
|
|
|
|
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
|
|
return not self.op1.has_object_permission(request, view, obj)
|
|
|
|
|
|
|
|
|
2018-11-01 22:12:34 +03:00
|
|
|
class BasePermissionMetaclass(OperationHolderMixin, type):
|
|
|
|
pass
|
2018-10-03 17:36:24 +03:00
|
|
|
|
|
|
|
|
2019-04-30 18:53:44 +03:00
|
|
|
class BasePermission(metaclass=BasePermissionMetaclass):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
A base class from which all permission classes should inherit.
|
|
|
|
"""
|
|
|
|
|
2013-02-11 16:47:56 +04:00
|
|
|
def has_permission(self, request, view):
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2012-10-15 16:27:50 +04:00
|
|
|
Return `True` if permission is granted, `False` otherwise.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
2013-02-11 16:47:56 +04:00
|
|
|
return True
|
|
|
|
|
|
|
|
def has_object_permission(self, request, view, obj):
|
|
|
|
"""
|
|
|
|
Return `True` if permission is granted, `False` otherwise.
|
|
|
|
"""
|
|
|
|
return True
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
2012-10-27 23:17:49 +04:00
|
|
|
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.
|
|
|
|
"""
|
2016-03-17 14:06:47 +03:00
|
|
|
|
2013-02-11 16:47:56 +04:00
|
|
|
def has_permission(self, request, view):
|
2012-10-27 23:17:49 +04:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
class IsAuthenticated(BasePermission):
|
|
|
|
"""
|
|
|
|
Allows access only to authenticated users.
|
|
|
|
"""
|
|
|
|
|
2013-02-11 16:47:56 +04:00
|
|
|
def has_permission(self, request, view):
|
2018-10-29 00:12:39 +03:00
|
|
|
return bool(request.user and request.user.is_authenticated)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
|
|
class IsAdminUser(BasePermission):
|
|
|
|
"""
|
|
|
|
Allows access only to admin users.
|
|
|
|
"""
|
|
|
|
|
2013-02-11 16:47:56 +04:00
|
|
|
def has_permission(self, request, view):
|
2018-10-29 00:12:39 +03:00
|
|
|
return bool(request.user and request.user.is_staff)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
|
|
class IsAuthenticatedOrReadOnly(BasePermission):
|
|
|
|
"""
|
|
|
|
The request is authenticated as a user, or is a read-only request.
|
|
|
|
"""
|
|
|
|
|
2013-02-11 16:47:56 +04:00
|
|
|
def has_permission(self, request, view):
|
2018-10-29 00:12:39 +03:00
|
|
|
return bool(
|
2014-08-19 16:28:07 +04:00
|
|
|
request.method in SAFE_METHODS or
|
|
|
|
request.user and
|
2017-10-05 21:41:38 +03:00
|
|
|
request.user.is_authenticated
|
2014-08-19 16:28:07 +04:00
|
|
|
)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
2013-04-30 17:34:28 +04:00
|
|
|
This permission can only be applied against view classes that
|
2015-04-12 18:12:01 +03:00
|
|
|
provide a `.queryset` attribute.
|
2012-09-20 16:06:27 +04:00
|
|
|
"""
|
|
|
|
|
|
|
|
# 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 = {
|
2021-02-17 14:30:28 +03:00
|
|
|
'GET': ['%(app_label)s.view_%(model_name)s'],
|
2012-09-20 16:06:27 +04:00
|
|
|
'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'],
|
|
|
|
}
|
|
|
|
|
2013-03-09 03:42:20 +04:00
|
|
|
authenticated_users_only = True
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
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,
|
2015-09-21 20:57:20 +03:00
|
|
|
'model_name': model_cls._meta.model_name
|
2012-09-20 16:06:27 +04:00
|
|
|
}
|
2017-02-28 19:51:32 +03:00
|
|
|
|
|
|
|
if method not in self.perms_map:
|
|
|
|
raise exceptions.MethodNotAllowed(method)
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
return [perm % kwargs for perm in self.perms_map[method]]
|
|
|
|
|
2017-09-01 20:37:58 +03:00
|
|
|
def _queryset(self, view):
|
|
|
|
assert hasattr(view, 'get_queryset') \
|
|
|
|
or getattr(view, 'queryset', None) is not None, (
|
|
|
|
'Cannot apply {} on a view that does not set '
|
|
|
|
'`.queryset` or have a `.get_queryset()` method.'
|
|
|
|
).format(self.__class__.__name__)
|
|
|
|
|
|
|
|
if hasattr(view, 'get_queryset'):
|
|
|
|
queryset = view.get_queryset()
|
|
|
|
assert queryset is not None, (
|
|
|
|
'{}.get_queryset() returned None'.format(view.__class__.__name__)
|
|
|
|
)
|
|
|
|
return queryset
|
|
|
|
return view.queryset
|
|
|
|
|
2013-02-11 16:47:56 +04:00
|
|
|
def has_permission(self, request, view):
|
2015-05-13 15:26:44 +03:00
|
|
|
# Workaround to ensure DjangoModelPermissions are not applied
|
|
|
|
# to the root view when using DefaultRouter.
|
|
|
|
if getattr(view, '_ignore_model_permissions', False):
|
|
|
|
return True
|
|
|
|
|
2017-08-25 23:14:33 +03:00
|
|
|
if not request.user or (
|
2017-10-05 21:41:38 +03:00
|
|
|
not request.user.is_authenticated and self.authenticated_users_only):
|
2017-08-25 23:14:33 +03:00
|
|
|
return False
|
|
|
|
|
2017-09-01 20:37:58 +03:00
|
|
|
queryset = self._queryset(view)
|
2015-04-12 18:12:01 +03:00
|
|
|
perms = self.get_required_permissions(request.method, queryset.model)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
2017-08-25 23:14:33 +03:00
|
|
|
return request.user.has_perms(perms)
|
2013-03-10 17:08:29 +04:00
|
|
|
|
|
|
|
|
2013-04-30 17:34:28 +04:00
|
|
|
class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions):
|
|
|
|
"""
|
|
|
|
Similar to DjangoModelPermissions, except that anonymous users are
|
|
|
|
allowed read-only access.
|
|
|
|
"""
|
|
|
|
authenticated_users_only = False
|
|
|
|
|
|
|
|
|
2013-09-11 00:00:13 +04:00
|
|
|
class DjangoObjectPermissions(DjangoModelPermissions):
|
2013-09-08 08:18:52 +04:00
|
|
|
"""
|
2013-09-11 00:00:13 +04:00
|
|
|
The request is authenticated using Django's object-level permissions.
|
|
|
|
It requires an object-permissions-enabled backend, such as Django Guardian.
|
2013-09-09 20:32:29 +04:00
|
|
|
|
|
|
|
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
|
2015-04-12 18:12:01 +03:00
|
|
|
provide a `.queryset` attribute.
|
2013-09-08 08:18:52 +04:00
|
|
|
"""
|
2013-09-11 00:00:13 +04:00
|
|
|
perms_map = {
|
2021-02-17 14:30:28 +03:00
|
|
|
'GET': ['%(app_label)s.view_%(model_name)s'],
|
2013-09-11 00:00:13 +04:00
|
|
|
'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'],
|
2013-09-08 08:18:52 +04:00
|
|
|
}
|
|
|
|
|
2013-09-09 19:39:09 +04:00
|
|
|
def get_required_object_permissions(self, method, model_cls):
|
|
|
|
kwargs = {
|
2013-09-11 00:00:13 +04:00
|
|
|
'app_label': model_cls._meta.app_label,
|
2015-09-21 20:57:20 +03:00
|
|
|
'model_name': model_cls._meta.model_name
|
2013-09-09 19:39:09 +04:00
|
|
|
}
|
2017-02-28 19:51:32 +03:00
|
|
|
|
|
|
|
if method not in self.perms_map:
|
|
|
|
raise exceptions.MethodNotAllowed(method)
|
|
|
|
|
2013-09-11 00:00:13 +04:00
|
|
|
return [perm % kwargs for perm in self.perms_map[method]]
|
2013-09-08 08:18:52 +04:00
|
|
|
|
|
|
|
def has_object_permission(self, request, view, obj):
|
2017-09-01 20:37:58 +03:00
|
|
|
# authentication checks have already executed via has_permission
|
|
|
|
queryset = self._queryset(view)
|
2015-05-13 15:26:44 +03:00
|
|
|
model_cls = queryset.model
|
2015-04-12 18:12:01 +03:00
|
|
|
user = request.user
|
2013-09-08 08:18:52 +04:00
|
|
|
|
2013-09-09 19:39:09 +04:00
|
|
|
perms = self.get_required_object_permissions(request.method, model_cls)
|
2013-09-08 08:48:03 +04:00
|
|
|
|
2013-09-11 00:00:13 +04:00
|
|
|
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
|
2014-12-05 02:29:28 +03:00
|
|
|
# a 404 response.
|
2013-09-11 00:00:13 +04:00
|
|
|
|
2015-03-17 08:13:07 +03:00
|
|
|
if request.method in SAFE_METHODS:
|
2013-09-11 00:00:13 +04:00
|
|
|
# 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
|