authorization.py 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. # -*- coding: utf-8 -*-
  2. import typing
  3. from typing import TYPE_CHECKING
  4. import functools
  5. from pyramid.interfaces import IAuthorizationPolicy
  6. from zope.interface import implementer
  7. from tracim_backend.models.contents import ContentType
  8. from tracim_backend.models.contents import CONTENT_TYPES
  9. try:
  10. from json.decoder import JSONDecodeError
  11. except ImportError: # python3.4
  12. JSONDecodeError = ValueError
  13. from tracim_backend.models.contents import ContentType
  14. from tracim_backend.exceptions import InsufficientUserRoleInWorkspace
  15. from tracim_backend.exceptions import ContentTypeNotAllowed
  16. from tracim_backend.exceptions import InsufficientUserProfile
  17. if TYPE_CHECKING:
  18. from tracim_backend import TracimRequest
  19. ###
  20. # Pyramid default permission/authorization mecanism
  21. # INFO - G.M - 12-04-2018 - Setiing a Default permission on view is
  22. # needed to activate AuthentificationPolicy and
  23. # AuthorizationPolicy on pyramid request
  24. TRACIM_DEFAULT_PERM = 'tracim'
  25. @implementer(IAuthorizationPolicy)
  26. class AcceptAllAuthorizationPolicy(object):
  27. """
  28. Empty AuthorizationPolicy : Allow all request. As Pyramid need
  29. a Authorization policy when we use AuthentificationPolicy, this
  30. class permit use to disable pyramid authorization mecanism with
  31. working a AuthentificationPolicy.
  32. """
  33. def permits(self, context, principals, permision):
  34. return True
  35. def principals_allowed_by_permission(self, context, permission):
  36. raise NotImplementedError()
  37. ###
  38. # Authorization decorators for views
  39. # INFO - G.M - 12-04-2018
  40. # Instead of relying on pyramid authorization mecanism
  41. # We prefer to use decorators
  42. def require_same_user_or_profile(group: int) -> typing.Callable:
  43. """
  44. Decorator for view to restrict access of tracim request if candidate user
  45. is distinct from authenticated user and not with high enough profile.
  46. :param group: value from Group Object
  47. like Group.TIM_USER or Group.TIM_MANAGER
  48. :return:
  49. """
  50. def decorator(func: typing.Callable) -> typing.Callable:
  51. @functools.wraps(func)
  52. def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
  53. auth_user = request.current_user
  54. candidate_user = request.candidate_user
  55. if auth_user.user_id == candidate_user.user_id or \
  56. auth_user.profile.id >= group:
  57. return func(self, context, request)
  58. raise InsufficientUserProfile()
  59. return wrapper
  60. return decorator
  61. def require_profile(group: int) -> typing.Callable:
  62. """
  63. Decorator for view to restrict access of tracim request if profile is
  64. not high enough
  65. :param group: value from Group Object
  66. like Group.TIM_USER or Group.TIM_MANAGER
  67. :return:
  68. """
  69. def decorator(func: typing.Callable) -> typing.Callable:
  70. @functools.wraps(func)
  71. def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
  72. user = request.current_user
  73. if user.profile.id >= group:
  74. return func(self, context, request)
  75. raise InsufficientUserProfile()
  76. return wrapper
  77. return decorator
  78. def require_profile_or_other_profile_with_workspace_role(
  79. allow_all_group: int,
  80. allow_if_role_group: int,
  81. minimal_required_role: int,
  82. ) -> typing.Callable:
  83. """
  84. Allow access for allow_all_group profile
  85. or allow access for allow_if_role_group
  86. profile if mininal_required_role is correct.
  87. :param allow_all_group: value from Group Object
  88. like Group.TIM_USER or Group.TIM_MANAGER
  89. :param allow_if_role_group: value from Group Object
  90. like Group.TIM_USER or Group.TIM_MANAGER
  91. :param minimal_required_role: value from UserInWorkspace Object like
  92. UserRoleInWorkspace.CONTRIBUTOR or UserRoleInWorkspace.READER
  93. :return: decorator
  94. """
  95. def decorator(func: typing.Callable) -> typing.Callable:
  96. @functools.wraps(func)
  97. def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
  98. user = request.current_user
  99. workspace = request.current_workspace
  100. if user.profile.id >= allow_all_group:
  101. return func(self, context, request)
  102. elif user.profile.id >= allow_if_role_group:
  103. if workspace.get_user_role(user) >= minimal_required_role:
  104. return func(self, context, request)
  105. raise InsufficientUserRoleInWorkspace()
  106. else:
  107. raise InsufficientUserProfile()
  108. return wrapper
  109. return decorator
  110. def require_workspace_role(minimal_required_role: int) -> typing.Callable:
  111. """
  112. Restricts access to endpoint to minimal role or raise an exception.
  113. Check role for current_workspace.
  114. :param minimal_required_role: value from UserInWorkspace Object like
  115. UserRoleInWorkspace.CONTRIBUTOR or UserRoleInWorkspace.READER
  116. :return: decorator
  117. """
  118. def decorator(func: typing.Callable) -> typing.Callable:
  119. @functools.wraps(func)
  120. def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
  121. user = request.current_user
  122. workspace = request.current_workspace
  123. if workspace.get_user_role(user) >= minimal_required_role:
  124. return func(self, context, request)
  125. raise InsufficientUserRoleInWorkspace()
  126. return wrapper
  127. return decorator
  128. def require_candidate_workspace_role(minimal_required_role: int) -> typing.Callable: # nopep8
  129. """
  130. Restricts access to endpoint to minimal role or raise an exception.
  131. Check role for candidate_workspace.
  132. :param minimal_required_role: value from UserInWorkspace Object like
  133. UserRoleInWorkspace.CONTRIBUTOR or UserRoleInWorkspace.READER
  134. :return: decorator
  135. """
  136. def decorator(func: typing.Callable) -> typing.Callable:
  137. def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
  138. user = request.current_user
  139. workspace = request.candidate_workspace
  140. if workspace.get_user_role(user) >= minimal_required_role:
  141. return func(self, context, request)
  142. raise InsufficientUserRoleInWorkspace()
  143. return wrapper
  144. return decorator
  145. def require_content_types(content_types: typing.List['ContentType']) -> typing.Callable: # nopep8
  146. """
  147. Restricts access to specific file type or raise an exception.
  148. Check role for candidate_workspace.
  149. :param content_types: list of ContentType object
  150. :return: decorator
  151. """
  152. def decorator(func: typing.Callable) -> typing.Callable:
  153. @functools.wraps(func)
  154. def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
  155. content = request.current_content
  156. current_content_type_slug = CONTENT_TYPES.get_one_by_slug(content.type).slug
  157. content_types_slug = [content_type.slug for content_type in content_types] # nopep8
  158. if current_content_type_slug in content_types_slug:
  159. return func(self, context, request)
  160. raise ContentTypeNotAllowed()
  161. return wrapper
  162. return decorator
  163. def require_comment_ownership_or_role(
  164. minimal_required_role_for_owner: int,
  165. minimal_required_role_for_anyone: int,
  166. ) -> typing.Callable:
  167. """
  168. Decorator for view to restrict access of tracim request if role is
  169. not high enough and user is not owner of the current_content
  170. :param minimal_required_role_for_owner: minimal role for owner
  171. of current_content to access to this view
  172. :param minimal_required_role_for_anyone: minimal role for anyone to
  173. access to this view.
  174. :return:
  175. """
  176. def decorator(func: typing.Callable) -> typing.Callable:
  177. @functools.wraps(func)
  178. def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
  179. user = request.current_user
  180. workspace = request.current_workspace
  181. comment = request.current_comment
  182. # INFO - G.M - 2018-06-178 - find minimal role required
  183. if comment.owner.user_id == user.user_id:
  184. minimal_required_role = minimal_required_role_for_owner
  185. else:
  186. minimal_required_role = minimal_required_role_for_anyone
  187. # INFO - G.M - 2018-06-178 - normal role test
  188. if workspace.get_user_role(user) >= minimal_required_role:
  189. return func(self, context, request)
  190. raise InsufficientUserRoleInWorkspace()
  191. return wrapper
  192. return decorator