request.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. # -*- coding: utf-8 -*-
  2. from pyramid.request import Request
  3. from sqlalchemy.orm.exc import NoResultFound
  4. from tracim.exceptions import NotAuthenticated, ContentNotFound
  5. from tracim.exceptions import ContentNotFoundInTracimRequest
  6. from tracim.exceptions import WorkspaceNotFoundInTracimRequest
  7. from tracim.exceptions import UserNotFoundInTracimRequest
  8. from tracim.exceptions import UserDoesNotExist
  9. from tracim.exceptions import WorkspaceNotFound
  10. from tracim.exceptions import ImmutableAttribute
  11. from tracim.models.contents import ContentTypeLegacy as ContentType
  12. from tracim.lib.core.content import ContentApi
  13. from tracim.lib.core.user import UserApi
  14. from tracim.lib.core.workspace import WorkspaceApi
  15. from tracim.lib.utils.authorization import JSONDecodeError
  16. from tracim.models import User
  17. from tracim.models.data import Workspace, Content
  18. class TracimRequest(Request):
  19. """
  20. Request with tracim specific params/methods
  21. """
  22. def __init__(
  23. self,
  24. environ,
  25. charset=None,
  26. unicode_errors=None,
  27. decode_param_names=None,
  28. **kw
  29. ):
  30. super().__init__(
  31. environ,
  32. charset,
  33. unicode_errors,
  34. decode_param_names,
  35. **kw
  36. )
  37. # Current content, found in request path
  38. self._current_content = None # type: Content
  39. # Current workspace, found in request path
  40. self._current_workspace = None # type: Workspace
  41. # Candidate workspace found in request body
  42. self._candidate_workspace = None # type: Workspace
  43. # Authenticated user
  44. self._current_user = None # type: User
  45. # User found from request headers, content, distinct from authenticated
  46. # user
  47. self._candidate_user = None # type: User
  48. # INFO - G.M - 18-05-2018 - Close db at the end of the request
  49. self.add_finished_callback(self._cleanup)
  50. @property
  51. def current_workspace(self) -> Workspace:
  52. """
  53. Get current workspace of the request according to authentification and
  54. request headers (to retrieve workspace). Setted by default value the
  55. first time if not configured.
  56. :return: Workspace of the request
  57. """
  58. if self._current_workspace is None:
  59. self._current_workspace = self._get_current_workspace(self.current_user, self)
  60. return self._current_workspace
  61. @current_workspace.setter
  62. def current_workspace(self, workspace: Workspace) -> None:
  63. """
  64. Setting current_workspace
  65. :param workspace:
  66. :return:
  67. """
  68. if self._current_workspace is not None:
  69. raise ImmutableAttribute(
  70. "Can't modify already setted current_workspace"
  71. )
  72. self._current_workspace = workspace
  73. @property
  74. def current_user(self) -> User:
  75. """
  76. Get user from authentication mecanism.
  77. """
  78. if self._current_user is None:
  79. self.current_user = self._get_auth_safe_user(self)
  80. return self._current_user
  81. @current_user.setter
  82. def current_user(self, user: User) -> None:
  83. if self._current_user is not None:
  84. raise ImmutableAttribute(
  85. "Can't modify already setted current_user"
  86. )
  87. self._current_user = user
  88. @property
  89. def current_content(self) -> User:
  90. """
  91. Get current content from path
  92. """
  93. if self._current_content is None:
  94. self._current_content = self._get_current_content(
  95. self.current_user,
  96. self.current_workspace,
  97. self
  98. )
  99. return self._current_content
  100. @current_content.setter
  101. def current_content(self, content: Content) -> None:
  102. if self._current_content is not None:
  103. raise ImmutableAttribute(
  104. "Can't modify already setted current_content"
  105. )
  106. self._current_content = content
  107. # TODO - G.M - 24-05-2018 - Find a better naming for this ?
  108. @property
  109. def candidate_user(self) -> User:
  110. """
  111. Get user from headers/body request. This user is not
  112. the one found by authentication mecanism. This user
  113. can help user to know about who one page is about in
  114. a similar way as current_workspace.
  115. """
  116. if self._candidate_user is None:
  117. self.candidate_user = self._get_candidate_user(self)
  118. return self._candidate_user
  119. @property
  120. def candidate_workspace(self) -> Workspace:
  121. """
  122. Get workspace from headers/body request. This workspace is not
  123. the one found from path. Its the one from json body.
  124. """
  125. if self._candidate_workspace is None:
  126. self._candidate_workspace = self._get_candidate_workspace(
  127. self.current_user,
  128. self
  129. )
  130. return self._candidate_workspace
  131. def _cleanup(self, request: 'TracimRequest') -> None:
  132. """
  133. Close dbsession at the end of the request in order to avoid exception
  134. about not properly closed session or "object created in another thread"
  135. issue
  136. see https://github.com/tracim/tracim_backend/issues/62
  137. :param request: same as self, request
  138. :return: nothing.
  139. """
  140. self._current_user = None
  141. self._current_workspace = None
  142. self.dbsession.close()
  143. @candidate_user.setter
  144. def candidate_user(self, user: User) -> None:
  145. if self._candidate_user is not None:
  146. raise ImmutableAttribute(
  147. "Can't modify already setted candidate_user"
  148. )
  149. self._candidate_user = user
  150. ###
  151. # Utils for TracimRequest
  152. ###
  153. def _get_current_content(
  154. self,
  155. user: User,
  156. workspace: Workspace,
  157. request: 'TracimRequest'
  158. ):
  159. """
  160. Get current content from request
  161. :param user: User who want to check the workspace
  162. :param request: pyramid request
  163. :return: current content
  164. """
  165. content_id = ''
  166. try:
  167. if 'content_id' in request.matchdict:
  168. content_id = int(request.matchdict['content_id'])
  169. if not content_id:
  170. raise ContentNotFoundInTracimRequest('No content_id property found in request') # nopep8
  171. api = ContentApi(
  172. current_user=user,
  173. session=request.dbsession,
  174. config=request.registry.settings['CFG']
  175. )
  176. content = api.get_one(content_id=content_id, workspace=workspace, content_type=ContentType.Any) # nopep8
  177. except JSONDecodeError:
  178. raise ContentNotFound('Bad json body')
  179. except NoResultFound:
  180. raise ContentNotFound(
  181. 'Content {} does not exist '
  182. 'or is not visible for this user'.format(content_id)
  183. )
  184. return content
  185. def _get_candidate_user(
  186. self,
  187. request: 'TracimRequest',
  188. ) -> User:
  189. """
  190. Get candidate user
  191. :param request: pyramid request
  192. :return: user found from header/body
  193. """
  194. app_config = request.registry.settings['CFG']
  195. uapi = UserApi(None, session=request.dbsession, config=app_config)
  196. try:
  197. login = None
  198. if 'user_id' in request.matchdict:
  199. login = request.matchdict['user_id']
  200. if not login:
  201. raise UserNotFoundInTracimRequest('You request a candidate user but the context not permit to found one') # nopep8
  202. user = uapi.get_one(login)
  203. except UserNotFoundInTracimRequest as exc:
  204. raise UserDoesNotExist('User {} not found'.format(login)) from exc
  205. return user
  206. def _get_auth_safe_user(
  207. self,
  208. request: 'TracimRequest',
  209. ) -> User:
  210. """
  211. Get current pyramid authenticated user from request
  212. :param request: pyramid request
  213. :return: current authenticated user
  214. """
  215. app_config = request.registry.settings['CFG']
  216. uapi = UserApi(None, session=request.dbsession, config=app_config)
  217. try:
  218. login = request.authenticated_userid
  219. if not login:
  220. raise UserNotFoundInTracimRequest('You request a current user but the context not permit to found one') # nopep8
  221. user = uapi.get_one_by_email(login)
  222. except (UserDoesNotExist, UserNotFoundInTracimRequest) as exc:
  223. raise NotAuthenticated('User {} not found'.format(login)) from exc
  224. return user
  225. def _get_current_workspace(
  226. self,
  227. user: User,
  228. request: 'TracimRequest'
  229. ) -> Workspace:
  230. """
  231. Get current workspace from request
  232. :param user: User who want to check the workspace
  233. :param request: pyramid request
  234. :return: current workspace
  235. """
  236. workspace_id = ''
  237. try:
  238. if 'workspace_id' in request.matchdict:
  239. workspace_id = request.matchdict['workspace_id']
  240. if not workspace_id:
  241. raise WorkspaceNotFoundInTracimRequest('No workspace_id property found in request')
  242. wapi = WorkspaceApi(
  243. current_user=user,
  244. session=request.dbsession,
  245. config=request.registry.settings['CFG']
  246. )
  247. workspace = wapi.get_one(workspace_id)
  248. except JSONDecodeError:
  249. raise WorkspaceNotFound('Bad json body')
  250. except NoResultFound:
  251. raise WorkspaceNotFound(
  252. 'Workspace {} does not exist '
  253. 'or is not visible for this user'.format(workspace_id)
  254. )
  255. return workspace
  256. def _get_candidate_workspace(
  257. self,
  258. user: User,
  259. request: 'TracimRequest'
  260. ) -> Workspace:
  261. """
  262. Get current workspace from request
  263. :param user: User who want to check the workspace
  264. :param request: pyramid request
  265. :return: current workspace
  266. """
  267. workspace_id = ''
  268. try:
  269. if 'new_workspace_id' in request.json_body:
  270. workspace_id = request.json_body['new_workspace_id']
  271. if not workspace_id:
  272. raise WorkspaceNotFoundInTracimRequest('No new_workspace_id property found in body')
  273. wapi = WorkspaceApi(
  274. current_user=user,
  275. session=request.dbsession,
  276. config=request.registry.settings['CFG']
  277. )
  278. workspace = wapi.get_one(workspace_id)
  279. except JSONDecodeError:
  280. raise WorkspaceNotFound('Bad json body')
  281. except NoResultFound:
  282. raise WorkspaceNotFound(
  283. 'Workspace {} does not exist '
  284. 'or is not visible for this user'.format(workspace_id)
  285. )
  286. return workspace