permissions.py 9.0 KB

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