authorization.py 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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_workspace_role(minimal_required_role: int) -> typing.Callable:
  78. """
  79. Restricts access to endpoint to minimal role or raise an exception.
  80. Check role for current_workspace.
  81. :param minimal_required_role: value from UserInWorkspace Object like
  82. UserRoleInWorkspace.CONTRIBUTOR or UserRoleInWorkspace.READER
  83. :return: decorator
  84. """
  85. def decorator(func: typing.Callable) -> typing.Callable:
  86. @functools.wraps(func)
  87. def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
  88. user = request.current_user
  89. workspace = request.current_workspace
  90. if workspace.get_user_role(user) >= minimal_required_role:
  91. return func(self, context, request)
  92. raise InsufficientUserRoleInWorkspace()
  93. return wrapper
  94. return decorator
  95. def require_candidate_workspace_role(minimal_required_role: int) -> typing.Callable: # nopep8
  96. """
  97. Restricts access to endpoint to minimal role or raise an exception.
  98. Check role for candidate_workspace.
  99. :param minimal_required_role: value from UserInWorkspace Object like
  100. UserRoleInWorkspace.CONTRIBUTOR or UserRoleInWorkspace.READER
  101. :return: decorator
  102. """
  103. def decorator(func: typing.Callable) -> typing.Callable:
  104. def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
  105. user = request.current_user
  106. workspace = request.candidate_workspace
  107. if workspace.get_user_role(user) >= minimal_required_role:
  108. return func(self, context, request)
  109. raise InsufficientUserRoleInWorkspace()
  110. return wrapper
  111. return decorator
  112. def require_content_types(content_types_slug: typing.List[str]) -> typing.Callable: # nopep8
  113. """
  114. Restricts access to specific file type or raise an exception.
  115. Check role for candidate_workspace.
  116. :param content_types_slug: list of slug of content_types
  117. :return: decorator
  118. """
  119. def decorator(func: typing.Callable) -> typing.Callable:
  120. @functools.wraps(func)
  121. def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
  122. content = request.current_content
  123. current_content_type_slug = CONTENT_TYPES.get_one_by_slug(content.type).slug # nopep8
  124. if current_content_type_slug in content_types_slug:
  125. return func(self, context, request)
  126. raise ContentTypeNotAllowed()
  127. return wrapper
  128. return decorator
  129. def require_comment_ownership_or_role(
  130. minimal_required_role_for_owner: int,
  131. minimal_required_role_for_anyone: int,
  132. ) -> typing.Callable:
  133. """
  134. Decorator for view to restrict access of tracim request if role is
  135. not high enough and user is not owner of the current_content
  136. :param minimal_required_role_for_owner: minimal role for owner
  137. of current_content to access to this view
  138. :param minimal_required_role_for_anyone: minimal role for anyone to
  139. access to this view.
  140. :return:
  141. """
  142. def decorator(func: typing.Callable) -> typing.Callable:
  143. @functools.wraps(func)
  144. def wrapper(self, context, request: 'TracimRequest') -> typing.Callable:
  145. user = request.current_user
  146. workspace = request.current_workspace
  147. comment = request.current_comment
  148. # INFO - G.M - 2018-06-178 - find minimal role required
  149. if comment.owner.user_id == user.user_id:
  150. minimal_required_role = minimal_required_role_for_owner
  151. else:
  152. minimal_required_role = minimal_required_role_for_anyone
  153. # INFO - G.M - 2018-06-178 - normal role test
  154. if workspace.get_user_role(user) >= minimal_required_role:
  155. return func(self, context, request)
  156. raise InsufficientUserRoleInWorkspace()
  157. return wrapper
  158. return decorator