123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- """
- Provides a set of pluggable permission policies.
- """
- from django.http import Http404
- from rest_framework import exceptions
- SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
- 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)
- 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)
- class OperandHolder(OperationHolderMixin):
- 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 (
- self.op1.has_permission(request, view) and
- self.op2.has_permission(request, view)
- )
- def has_object_permission(self, request, view, obj):
- return (
- self.op1.has_object_permission(request, view, obj) and
- 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 (
- self.op1.has_permission(request, view) or
- self.op2.has_permission(request, view)
- )
- def has_object_permission(self, request, view, obj):
- return (
- self.op1.has_object_permission(request, view, obj) or
- self.op2.has_object_permission(request, view, obj)
- )
- 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)
- class BasePermissionMetaclass(OperationHolderMixin, type):
- pass
- class BasePermission(metaclass=BasePermissionMetaclass):
- """
- 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.
- """
- 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):
- return bool(request.user and request.user.is_authenticated)
- class IsAdminUser(BasePermission):
- """
- Allows access only to admin users.
- """
- def has_permission(self, request, view):
- return bool(request.user and request.user.is_staff)
- class IsAuthenticatedOrReadOnly(BasePermission):
- """
- The request is authenticated as a user, or is a read-only request.
- """
- def has_permission(self, request, view):
- return bool(
- request.method in SAFE_METHODS or
- request.user and
- request.user.is_authenticated
- )
- 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 `.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.model_name
- }
- if method not in self.perms_map:
- raise exceptions.MethodNotAllowed(method)
- return [perm % kwargs for perm in self.perms_map[method]]
- 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
- def has_permission(self, request, view):
- # Workaround to ensure DjangoModelPermissions are not applied
- # to the root view when using DefaultRouter.
- if getattr(view, '_ignore_model_permissions', False):
- return True
- if not request.user or (
- not request.user.is_authenticated and self.authenticated_users_only):
- return False
- queryset = self._queryset(view)
- perms = self.get_required_permissions(request.method, queryset.model)
- return request.user.has_perms(perms)
- class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions):
- """
- Similar to DjangoModelPermissions, except that anonymous users are
- allowed read-only access.
- """
- authenticated_users_only = False
- class DjangoObjectPermissions(DjangoModelPermissions):
- """
- 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 `.queryset` attribute.
- """
- 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'],
- }
- def get_required_object_permissions(self, method, model_cls):
- kwargs = {
- 'app_label': model_cls._meta.app_label,
- 'model_name': model_cls._meta.model_name
- }
- if method not in self.perms_map:
- raise exceptions.MethodNotAllowed(method)
- return [perm % kwargs for perm in self.perms_map[method]]
- def has_object_permission(self, request, view, obj):
- # authentication checks have already executed via has_permission
- queryset = self._queryset(view)
- model_cls = queryset.model
- user = request.user
- perms = self.get_required_object_permissions(request.method, model_cls)
- 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 SAFE_METHODS:
- # 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
|