authorization.py 7.0KB

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