permissions.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. """
  2. Provides a set of pluggable permission policies.
  3. """
  4. from django.http import Http404
  5. from rest_framework import exceptions
  6. SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
  7. class OperationHolderMixin:
  8. def __and__(self, other):
  9. return OperandHolder(AND, self, other)
  10. def __or__(self, other):
  11. return OperandHolder(OR, self, other)
  12. def __rand__(self, other):
  13. return OperandHolder(AND, other, self)
  14. def __ror__(self, other):
  15. return OperandHolder(OR, other, self)
  16. def __invert__(self):
  17. return SingleOperandHolder(NOT, self)
  18. class SingleOperandHolder(OperationHolderMixin):
  19. def __init__(self, operator_class, op1_class):
  20. self.operator_class = operator_class
  21. self.op1_class = op1_class
  22. def __call__(self, *args, **kwargs):
  23. op1 = self.op1_class(*args, **kwargs)
  24. return self.operator_class(op1)
  25. class OperandHolder(OperationHolderMixin):
  26. def __init__(self, operator_class, op1_class, op2_class):
  27. self.operator_class = operator_class
  28. self.op1_class = op1_class
  29. self.op2_class = op2_class
  30. def __call__(self, *args, **kwargs):
  31. op1 = self.op1_class(*args, **kwargs)
  32. op2 = self.op2_class(*args, **kwargs)
  33. return self.operator_class(op1, op2)
  34. class AND:
  35. def __init__(self, op1, op2):
  36. self.op1 = op1
  37. self.op2 = op2
  38. def has_permission(self, request, view):
  39. return (
  40. self.op1.has_permission(request, view) and
  41. self.op2.has_permission(request, view)
  42. )
  43. def has_object_permission(self, request, view, obj):
  44. return (
  45. self.op1.has_object_permission(request, view, obj) and
  46. self.op2.has_object_permission(request, view, obj)
  47. )
  48. class OR:
  49. def __init__(self, op1, op2):
  50. self.op1 = op1
  51. self.op2 = op2
  52. def has_permission(self, request, view):
  53. return (
  54. self.op1.has_permission(request, view) or
  55. self.op2.has_permission(request, view)
  56. )
  57. def has_object_permission(self, request, view, obj):
  58. return (
  59. self.op1.has_object_permission(request, view, obj) or
  60. self.op2.has_object_permission(request, view, obj)
  61. )
  62. class NOT:
  63. def __init__(self, op1):
  64. self.op1 = op1
  65. def has_permission(self, request, view):
  66. return not self.op1.has_permission(request, view)
  67. def has_object_permission(self, request, view, obj):
  68. return not self.op1.has_object_permission(request, view, obj)
  69. class BasePermissionMetaclass(OperationHolderMixin, type):
  70. pass
  71. class BasePermission(metaclass=BasePermissionMetaclass):
  72. """
  73. A base class from which all permission classes should inherit.
  74. """
  75. def has_permission(self, request, view):
  76. """
  77. Return `True` if permission is granted, `False` otherwise.
  78. """
  79. return True
  80. def has_object_permission(self, request, view, obj):
  81. """
  82. Return `True` if permission is granted, `False` otherwise.
  83. """
  84. return True
  85. class AllowAny(BasePermission):
  86. """
  87. Allow any access.
  88. This isn't strictly required, since you could use an empty
  89. permission_classes list, but it's useful because it makes the intention
  90. more explicit.
  91. """
  92. def has_permission(self, request, view):
  93. return True
  94. class IsAuthenticated(BasePermission):
  95. """
  96. Allows access only to authenticated users.
  97. """
  98. def has_permission(self, request, view):
  99. return bool(request.user and request.user.is_authenticated)
  100. class IsAdminUser(BasePermission):
  101. """
  102. Allows access only to admin users.
  103. """
  104. def has_permission(self, request, view):
  105. return bool(request.user and request.user.is_staff)
  106. class IsAuthenticatedOrReadOnly(BasePermission):
  107. """
  108. The request is authenticated as a user, or is a read-only request.
  109. """
  110. def has_permission(self, request, view):
  111. return bool(
  112. request.method in SAFE_METHODS or
  113. request.user and
  114. request.user.is_authenticated
  115. )
  116. class DjangoModelPermissions(BasePermission):
  117. """
  118. The request is authenticated using `django.contrib.auth` permissions.
  119. See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions
  120. It ensures that the user is authenticated, and has the appropriate
  121. `add`/`change`/`delete` permissions on the model.
  122. This permission can only be applied against view classes that
  123. provide a `.queryset` attribute.
  124. """
  125. # Map methods into required permission codes.
  126. # Override this if you need to also provide 'view' permissions,
  127. # or if you want to provide custom permission codes.
  128. perms_map = {
  129. 'GET': [],
  130. 'OPTIONS': [],
  131. 'HEAD': [],
  132. 'POST': ['%(app_label)s.add_%(model_name)s'],
  133. 'PUT': ['%(app_label)s.change_%(model_name)s'],
  134. 'PATCH': ['%(app_label)s.change_%(model_name)s'],
  135. 'DELETE': ['%(app_label)s.delete_%(model_name)s'],
  136. }
  137. authenticated_users_only = True
  138. def get_required_permissions(self, method, model_cls):
  139. """
  140. Given a model and an HTTP method, return the list of permission
  141. codes that the user is required to have.
  142. """
  143. kwargs = {
  144. 'app_label': model_cls._meta.app_label,
  145. 'model_name': model_cls._meta.model_name
  146. }
  147. if method not in self.perms_map:
  148. raise exceptions.MethodNotAllowed(method)
  149. return [perm % kwargs for perm in self.perms_map[method]]
  150. def _queryset(self, view):
  151. assert hasattr(view, 'get_queryset') \
  152. or getattr(view, 'queryset', None) is not None, (
  153. 'Cannot apply {} on a view that does not set '
  154. '`.queryset` or have a `.get_queryset()` method.'
  155. ).format(self.__class__.__name__)
  156. if hasattr(view, 'get_queryset'):
  157. queryset = view.get_queryset()
  158. assert queryset is not None, (
  159. '{}.get_queryset() returned None'.format(view.__class__.__name__)
  160. )
  161. return queryset
  162. return view.queryset
  163. def has_permission(self, request, view):
  164. # Workaround to ensure DjangoModelPermissions are not applied
  165. # to the root view when using DefaultRouter.
  166. if getattr(view, '_ignore_model_permissions', False):
  167. return True
  168. if not request.user or (
  169. not request.user.is_authenticated and self.authenticated_users_only):
  170. return False
  171. queryset = self._queryset(view)
  172. perms = self.get_required_permissions(request.method, queryset.model)
  173. return request.user.has_perms(perms)
  174. class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions):
  175. """
  176. Similar to DjangoModelPermissions, except that anonymous users are
  177. allowed read-only access.
  178. """
  179. authenticated_users_only = False
  180. class DjangoObjectPermissions(DjangoModelPermissions):
  181. """
  182. The request is authenticated using Django's object-level permissions.
  183. It requires an object-permissions-enabled backend, such as Django Guardian.
  184. It ensures that the user is authenticated, and has the appropriate
  185. `add`/`change`/`delete` permissions on the object using .has_perms.
  186. This permission can only be applied against view classes that
  187. provide a `.queryset` attribute.
  188. """
  189. perms_map = {
  190. 'GET': [],
  191. 'OPTIONS': [],
  192. 'HEAD': [],
  193. 'POST': ['%(app_label)s.add_%(model_name)s'],
  194. 'PUT': ['%(app_label)s.change_%(model_name)s'],
  195. 'PATCH': ['%(app_label)s.change_%(model_name)s'],
  196. 'DELETE': ['%(app_label)s.delete_%(model_name)s'],
  197. }
  198. def get_required_object_permissions(self, method, model_cls):
  199. kwargs = {
  200. 'app_label': model_cls._meta.app_label,
  201. 'model_name': model_cls._meta.model_name
  202. }
  203. if method not in self.perms_map:
  204. raise exceptions.MethodNotAllowed(method)
  205. return [perm % kwargs for perm in self.perms_map[method]]
  206. def has_object_permission(self, request, view, obj):
  207. # authentication checks have already executed via has_permission
  208. queryset = self._queryset(view)
  209. model_cls = queryset.model
  210. user = request.user
  211. perms = self.get_required_object_permissions(request.method, model_cls)
  212. if not user.has_perms(perms, obj):
  213. # If the user does not have permissions we need to determine if
  214. # they have read permissions to see 403, or not, and simply see
  215. # a 404 response.
  216. if request.method in SAFE_METHODS:
  217. # Read permissions already checked and failed, no need
  218. # to make another lookup.
  219. raise Http404
  220. read_perms = self.get_required_object_permissions('GET', model_cls)
  221. if not user.has_perms(read_perms, obj):
  222. raise Http404
  223. # Has read permissions.
  224. return False
  225. return True