authorization.py 8.2KB

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