request.py 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  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 comment, found in request path
  38. self._current_comment = None # type: Content
  39. # Current content, found in request path
  40. self._current_content = None # type: Content
  41. # Current workspace, found in request path
  42. self._current_workspace = None # type: Workspace
  43. # Candidate workspace found in request body
  44. self._candidate_workspace = None # type: Workspace
  45. # Authenticated user
  46. self._current_user = None # type: User
  47. # User found from request headers, content, distinct from authenticated
  48. # user
  49. self._candidate_user = None # type: User
  50. # INFO - G.M - 18-05-2018 - Close db at the end of the request
  51. self.add_finished_callback(self._cleanup)
  52. @property
  53. def current_workspace(self) -> Workspace:
  54. """
  55. Get current workspace of the request according to authentification and
  56. request headers (to retrieve workspace). Setted by default value the
  57. first time if not configured.
  58. :return: Workspace of the request
  59. """
  60. if self._current_workspace is None:
  61. self._current_workspace = self._get_current_workspace(self.current_user, self) # nopep8
  62. return self._current_workspace
  63. @current_workspace.setter
  64. def current_workspace(self, workspace: Workspace) -> None:
  65. """
  66. Setting current_workspace
  67. :param workspace:
  68. :return:
  69. """
  70. if self._current_workspace is not None:
  71. raise ImmutableAttribute(
  72. "Can't modify already setted current_workspace"
  73. )
  74. self._current_workspace = workspace
  75. @property
  76. def current_user(self) -> User:
  77. """
  78. Get user from authentication mecanism.
  79. """
  80. if self._current_user is None:
  81. self.current_user = self._get_auth_safe_user(self)
  82. return self._current_user
  83. @current_user.setter
  84. def current_user(self, user: User) -> None:
  85. if self._current_user is not None:
  86. raise ImmutableAttribute(
  87. "Can't modify already setted current_user"
  88. )
  89. self._current_user = user
  90. @property
  91. def current_content(self) -> Content:
  92. """
  93. Get current content from path
  94. """
  95. if self._current_content is None:
  96. self._current_content = self._get_current_content(
  97. self.current_user,
  98. self.current_workspace,
  99. self
  100. )
  101. return self._current_content
  102. @current_content.setter
  103. def current_content(self, content: Content) -> None:
  104. if self._current_content is not None:
  105. raise ImmutableAttribute(
  106. "Can't modify already setted current_content"
  107. )
  108. self._current_content = content
  109. @property
  110. def current_comment(self) -> Content:
  111. """
  112. Get current comment from path
  113. """
  114. if self._current_comment is None:
  115. self._current_comment = self._get_current_comment(
  116. self.current_user,
  117. self.current_workspace,
  118. self.current_content,
  119. self
  120. )
  121. return self._current_comment
  122. @current_comment.setter
  123. def current_comment(self, content: Content) -> None:
  124. if self._current_comment is not None:
  125. raise ImmutableAttribute(
  126. "Can't modify already setted current_content"
  127. )
  128. self._current_comment = content
  129. # TODO - G.M - 24-05-2018 - Find a better naming for this ?
  130. @property
  131. def candidate_user(self) -> User:
  132. """
  133. Get user from headers/body request. This user is not
  134. the one found by authentication mecanism. This user
  135. can help user to know about who one page is about in
  136. a similar way as current_workspace.
  137. """
  138. if self._candidate_user is None:
  139. self.candidate_user = self._get_candidate_user(self)
  140. return self._candidate_user
  141. @property
  142. def candidate_workspace(self) -> Workspace:
  143. """
  144. Get workspace from headers/body request. This workspace is not
  145. the one found from path. Its the one from json body.
  146. """
  147. if self._candidate_workspace is None:
  148. self._candidate_workspace = self._get_candidate_workspace(
  149. self.current_user,
  150. self
  151. )
  152. return self._candidate_workspace
  153. def _cleanup(self, request: 'TracimRequest') -> None:
  154. """
  155. Close dbsession at the end of the request in order to avoid exception
  156. about not properly closed session or "object created in another thread"
  157. issue
  158. see https://github.com/tracim/tracim_backend/issues/62
  159. :param request: same as self, request
  160. :return: nothing.
  161. """
  162. self._current_user = None
  163. self._current_workspace = None
  164. self.dbsession.close()
  165. @candidate_user.setter
  166. def candidate_user(self, user: User) -> None:
  167. if self._candidate_user is not None:
  168. raise ImmutableAttribute(
  169. "Can't modify already setted candidate_user"
  170. )
  171. self._candidate_user = user
  172. ###
  173. # Utils for TracimRequest
  174. ###
  175. def _get_current_comment(
  176. self,
  177. user: User,
  178. workspace: Workspace,
  179. content: Content,
  180. request: 'TracimRequest'
  181. ) -> Content:
  182. """
  183. Get current content from request
  184. :param user: User who want to check the workspace
  185. :param request: pyramid request
  186. :return: current content
  187. """
  188. comment_id = ''
  189. try:
  190. if 'comment_id' in request.matchdict:
  191. comment_id = int(request.matchdict['comment_id'])
  192. if not comment_id:
  193. raise ContentNotFoundInTracimRequest('No comment_id property found in request') # nopep8
  194. api = ContentApi(
  195. current_user=user,
  196. session=request.dbsession,
  197. config=request.registry.settings['CFG']
  198. )
  199. comment = api.get_one(
  200. comment_id,
  201. content_type=ContentType.Comment,
  202. workspace=workspace,
  203. parent=content,
  204. )
  205. except JSONDecodeError:
  206. raise ContentNotFound('Bad json body')
  207. except NoResultFound:
  208. raise ContentNotFound(
  209. 'Comment {} does not exist '
  210. 'or is not visible for this user'.format(comment_id)
  211. )
  212. return comment
  213. def _get_current_content(
  214. self,
  215. user: User,
  216. workspace: Workspace,
  217. request: 'TracimRequest'
  218. ) -> Content:
  219. """
  220. Get current content from request
  221. :param user: User who want to check the workspace
  222. :param request: pyramid request
  223. :return: current content
  224. """
  225. content_id = ''
  226. try:
  227. if 'content_id' in request.matchdict:
  228. content_id = int(request.matchdict['content_id'])
  229. if not content_id:
  230. raise ContentNotFoundInTracimRequest('No content_id property found in request') # nopep8
  231. api = ContentApi(
  232. current_user=user,
  233. session=request.dbsession,
  234. config=request.registry.settings['CFG']
  235. )
  236. content = api.get_one(content_id=content_id, workspace=workspace, content_type=ContentType.Any) # nopep8
  237. except JSONDecodeError:
  238. raise ContentNotFound('Bad json body')
  239. except NoResultFound:
  240. raise ContentNotFound(
  241. 'Content {} does not exist '
  242. 'or is not visible for this user'.format(content_id)
  243. )
  244. return content
  245. def _get_candidate_user(
  246. self,
  247. request: 'TracimRequest',
  248. ) -> User:
  249. """
  250. Get candidate user
  251. :param request: pyramid request
  252. :return: user found from header/body
  253. """
  254. app_config = request.registry.settings['CFG']
  255. uapi = UserApi(None, session=request.dbsession, config=app_config)
  256. try:
  257. login = None
  258. if 'user_id' in request.matchdict:
  259. login = request.matchdict['user_id']
  260. if not login:
  261. raise UserNotFoundInTracimRequest('You request a candidate user but the context not permit to found one') # nopep8
  262. user = uapi.get_one(login)
  263. except UserNotFoundInTracimRequest as exc:
  264. raise UserDoesNotExist('User {} not found'.format(login)) from exc
  265. return user
  266. def _get_auth_safe_user(
  267. self,
  268. request: 'TracimRequest',
  269. ) -> User:
  270. """
  271. Get current pyramid authenticated user from request
  272. :param request: pyramid request
  273. :return: current authenticated user
  274. """
  275. app_config = request.registry.settings['CFG']
  276. uapi = UserApi(None, session=request.dbsession, config=app_config)
  277. try:
  278. login = request.authenticated_userid
  279. if not login:
  280. raise UserNotFoundInTracimRequest('You request a current user but the context not permit to found one') # nopep8
  281. user = uapi.get_one_by_email(login)
  282. except (UserDoesNotExist, UserNotFoundInTracimRequest) as exc:
  283. raise NotAuthenticated('User {} not found'.format(login)) from exc
  284. return user
  285. def _get_current_workspace(
  286. self,
  287. user: User,
  288. request: 'TracimRequest'
  289. ) -> Workspace:
  290. """
  291. Get current workspace from request
  292. :param user: User who want to check the workspace
  293. :param request: pyramid request
  294. :return: current workspace
  295. """
  296. workspace_id = ''
  297. try:
  298. if 'workspace_id' in request.matchdict:
  299. workspace_id = request.matchdict['workspace_id']
  300. if not workspace_id:
  301. raise WorkspaceNotFoundInTracimRequest('No workspace_id property found in request') # nopep8
  302. wapi = WorkspaceApi(
  303. current_user=user,
  304. session=request.dbsession,
  305. config=request.registry.settings['CFG']
  306. )
  307. workspace = wapi.get_one(workspace_id)
  308. except JSONDecodeError:
  309. raise WorkspaceNotFound('Bad json body')
  310. except NoResultFound:
  311. raise WorkspaceNotFound(
  312. 'Workspace {} does not exist '
  313. 'or is not visible for this user'.format(workspace_id)
  314. )
  315. return workspace
  316. def _get_candidate_workspace(
  317. self,
  318. user: User,
  319. request: 'TracimRequest'
  320. ) -> Workspace:
  321. """
  322. Get current workspace from request
  323. :param user: User who want to check the workspace
  324. :param request: pyramid request
  325. :return: current workspace
  326. """
  327. workspace_id = ''
  328. try:
  329. if 'new_workspace_id' in request.json_body:
  330. workspace_id = request.json_body['new_workspace_id']
  331. if not workspace_id:
  332. raise WorkspaceNotFoundInTracimRequest('No new_workspace_id property found in body') # nopep8
  333. wapi = WorkspaceApi(
  334. current_user=user,
  335. session=request.dbsession,
  336. config=request.registry.settings['CFG']
  337. )
  338. workspace = wapi.get_one(workspace_id)
  339. except JSONDecodeError:
  340. raise WorkspaceNotFound('Bad json body')
  341. except NoResultFound:
  342. raise WorkspaceNotFound(
  343. 'Workspace {} does not exist '
  344. 'or is not visible for this user'.format(workspace_id)
  345. )
  346. return workspace