user_controller.py 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. from pyramid.config import Configurator
  2. from tracim_backend.lib.utils.utils import password_generator
  3. try: # Python 3.5+
  4. from http import HTTPStatus
  5. except ImportError:
  6. from http import client as HTTPStatus
  7. from tracim_backend import hapic
  8. from tracim_backend.lib.utils.request import TracimRequest
  9. from tracim_backend.models import Group
  10. from tracim_backend.lib.core.group import GroupApi
  11. from tracim_backend.lib.core.user import UserApi
  12. from tracim_backend.lib.core.workspace import WorkspaceApi
  13. from tracim_backend.lib.core.content import ContentApi
  14. from tracim_backend.views.controllers import Controller
  15. from tracim_backend.lib.utils.authorization import require_same_user_or_profile
  16. from tracim_backend.lib.utils.authorization import require_profile
  17. from tracim_backend.exceptions import WrongUserPassword
  18. from tracim_backend.exceptions import EmailAlreadyExistInDb
  19. from tracim_backend.exceptions import PasswordDoNotMatch
  20. from tracim_backend.views.core_api.schemas import UserSchema
  21. from tracim_backend.views.core_api.schemas import AutocompleteQuerySchema
  22. from tracim_backend.views.core_api.schemas import UserDigestSchema
  23. from tracim_backend.views.core_api.schemas import SetEmailSchema
  24. from tracim_backend.views.core_api.schemas import SetPasswordSchema
  25. from tracim_backend.views.core_api.schemas import UserInfosSchema
  26. from tracim_backend.views.core_api.schemas import UserCreationSchema
  27. from tracim_backend.views.core_api.schemas import UserProfileSchema
  28. from tracim_backend.views.core_api.schemas import UserIdPathSchema
  29. from tracim_backend.views.core_api.schemas import ReadStatusSchema
  30. from tracim_backend.views.core_api.schemas import ContentIdsQuerySchema
  31. from tracim_backend.views.core_api.schemas import NoContentSchema
  32. from tracim_backend.views.core_api.schemas import UserWorkspaceIdPathSchema
  33. from tracim_backend.views.core_api.schemas import UserWorkspaceAndContentIdPathSchema
  34. from tracim_backend.views.core_api.schemas import ContentDigestSchema
  35. from tracim_backend.views.core_api.schemas import ActiveContentFilterQuerySchema
  36. from tracim_backend.views.core_api.schemas import WorkspaceDigestSchema
  37. from tracim_backend.app_models.contents import CONTENT_TYPES
  38. SWAGGER_TAG__USER_ENDPOINTS = 'Users'
  39. class UserController(Controller):
  40. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  41. @require_same_user_or_profile(Group.TIM_ADMIN)
  42. @hapic.input_path(UserIdPathSchema())
  43. @hapic.output_body(WorkspaceDigestSchema(many=True),)
  44. def user_workspace(self, context, request: TracimRequest, hapic_data=None):
  45. """
  46. Get list of user workspaces
  47. """
  48. app_config = request.registry.settings['CFG']
  49. wapi = WorkspaceApi(
  50. current_user=request.candidate_user, # User
  51. session=request.dbsession,
  52. config=app_config,
  53. )
  54. workspaces = wapi.get_all_for_user(request.candidate_user)
  55. return [
  56. wapi.get_workspace_with_context(workspace)
  57. for workspace in workspaces
  58. ]
  59. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  60. @require_same_user_or_profile(Group.TIM_ADMIN)
  61. @hapic.input_path(UserIdPathSchema())
  62. @hapic.output_body(UserSchema())
  63. def user(self, context, request: TracimRequest, hapic_data=None):
  64. """
  65. Get user infos.
  66. """
  67. app_config = request.registry.settings['CFG']
  68. uapi = UserApi(
  69. current_user=request.current_user, # User
  70. session=request.dbsession,
  71. config=app_config,
  72. )
  73. return uapi.get_user_with_context(request.candidate_user)
  74. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  75. @require_profile(Group.TIM_ADMIN)
  76. @hapic.output_body(UserDigestSchema(many=True))
  77. def users(self, context, request: TracimRequest, hapic_data=None):
  78. """
  79. Get all users
  80. """
  81. app_config = request.registry.settings['CFG']
  82. uapi = UserApi(
  83. current_user=request.current_user, # User
  84. session=request.dbsession,
  85. config=app_config,
  86. )
  87. users = uapi.get_all()
  88. context_users = [
  89. uapi.get_user_with_context(user) for user in users
  90. ]
  91. return context_users
  92. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  93. @require_same_user_or_profile(Group.TIM_MANAGER)
  94. @hapic.input_path(UserIdPathSchema())
  95. @hapic.input_query(AutocompleteQuerySchema())
  96. @hapic.output_body(UserDigestSchema(many=True))
  97. def known_members(self, context, request: TracimRequest, hapic_data=None):
  98. """
  99. Get known users list
  100. """
  101. app_config = request.registry.settings['CFG']
  102. uapi = UserApi(
  103. current_user=request.candidate_user, # User
  104. session=request.dbsession,
  105. config=app_config,
  106. )
  107. users = uapi.get_known_user(acp=hapic_data.query.acp)
  108. context_users = [
  109. uapi.get_user_with_context(user) for user in users
  110. ]
  111. return context_users
  112. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  113. @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
  114. @hapic.handle_exception(EmailAlreadyExistInDb, HTTPStatus.BAD_REQUEST)
  115. @require_same_user_or_profile(Group.TIM_ADMIN)
  116. @hapic.input_body(SetEmailSchema())
  117. @hapic.input_path(UserIdPathSchema())
  118. @hapic.output_body(UserSchema())
  119. def set_user_email(self, context, request: TracimRequest, hapic_data=None):
  120. """
  121. Set user Email
  122. """
  123. app_config = request.registry.settings['CFG']
  124. uapi = UserApi(
  125. current_user=request.current_user, # User
  126. session=request.dbsession,
  127. config=app_config,
  128. )
  129. user = uapi.set_email(
  130. request.candidate_user,
  131. hapic_data.body.loggedin_user_password,
  132. hapic_data.body.email,
  133. do_save=True
  134. )
  135. return uapi.get_user_with_context(user)
  136. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  137. @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
  138. @hapic.handle_exception(PasswordDoNotMatch, HTTPStatus.BAD_REQUEST)
  139. @require_same_user_or_profile(Group.TIM_ADMIN)
  140. @hapic.input_body(SetPasswordSchema())
  141. @hapic.input_path(UserIdPathSchema())
  142. @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8
  143. def set_user_password(self, context, request: TracimRequest, hapic_data=None): # nopep8
  144. """
  145. Set user password
  146. """
  147. app_config = request.registry.settings['CFG']
  148. uapi = UserApi(
  149. current_user=request.current_user, # User
  150. session=request.dbsession,
  151. config=app_config,
  152. )
  153. uapi.set_password(
  154. request.candidate_user,
  155. hapic_data.body.loggedin_user_password,
  156. hapic_data.body.new_password,
  157. hapic_data.body.new_password2,
  158. do_save=True
  159. )
  160. return
  161. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  162. @require_same_user_or_profile(Group.TIM_ADMIN)
  163. @hapic.input_body(UserInfosSchema())
  164. @hapic.input_path(UserIdPathSchema())
  165. @hapic.output_body(UserSchema())
  166. def set_user_infos(self, context, request: TracimRequest, hapic_data=None):
  167. """
  168. Set user info data
  169. """
  170. app_config = request.registry.settings['CFG']
  171. uapi = UserApi(
  172. current_user=request.current_user, # User
  173. session=request.dbsession,
  174. config=app_config,
  175. )
  176. user = uapi.update(
  177. request.candidate_user,
  178. name=hapic_data.body.public_name,
  179. timezone=hapic_data.body.timezone,
  180. do_save=True
  181. )
  182. return uapi.get_user_with_context(user)
  183. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  184. @hapic.handle_exception(EmailAlreadyExistInDb, HTTPStatus.BAD_REQUEST)
  185. @require_profile(Group.TIM_ADMIN)
  186. @hapic.input_body(UserCreationSchema())
  187. @hapic.output_body(UserSchema())
  188. def create_user(self, context, request: TracimRequest, hapic_data=None):
  189. """
  190. Create new user
  191. """
  192. app_config = request.registry.settings['CFG']
  193. uapi = UserApi(
  194. current_user=request.current_user, # User
  195. session=request.dbsession,
  196. config=app_config,
  197. )
  198. gapi = GroupApi(
  199. current_user=request.current_user, # User
  200. session=request.dbsession,
  201. config=app_config,
  202. )
  203. groups = [gapi.get_one_with_name(hapic_data.body.profile)]
  204. user = uapi.create_user(
  205. email=hapic_data.body.email,
  206. password=hapic_data.body.password,
  207. timezone=hapic_data.body.timezone,
  208. name=hapic_data.body.public_name,
  209. do_notify=hapic_data.body.email_notification,
  210. groups=groups,
  211. do_save=True
  212. )
  213. return uapi.get_user_with_context(user)
  214. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  215. @require_profile(Group.TIM_ADMIN)
  216. @hapic.input_path(UserIdPathSchema())
  217. @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8
  218. def enable_user(self, context, request: TracimRequest, hapic_data=None):
  219. """
  220. enable user
  221. """
  222. app_config = request.registry.settings['CFG']
  223. uapi = UserApi(
  224. current_user=request.current_user, # User
  225. session=request.dbsession,
  226. config=app_config,
  227. )
  228. uapi.enable(user=request.candidate_user, do_save=True)
  229. return
  230. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  231. @require_profile(Group.TIM_ADMIN)
  232. @hapic.input_path(UserIdPathSchema())
  233. @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8
  234. def delete_user(self, context, request: TracimRequest, hapic_data=None):
  235. """
  236. delete user
  237. """
  238. app_config = request.registry.settings['CFG']
  239. uapi = UserApi(
  240. current_user=request.current_user, # User
  241. session=request.dbsession,
  242. config=app_config,
  243. )
  244. uapi.delete(user=request.candidate_user, do_save=True)
  245. return
  246. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  247. @require_profile(Group.TIM_ADMIN)
  248. @hapic.input_path(UserIdPathSchema())
  249. @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8
  250. def undelete_user(self, context, request: TracimRequest, hapic_data=None):
  251. """
  252. undelete user
  253. """
  254. app_config = request.registry.settings['CFG']
  255. uapi = UserApi(
  256. current_user=request.current_user, # User
  257. session=request.dbsession,
  258. config=app_config,
  259. show_deleted=True,
  260. )
  261. uapi.undelete(user=request.candidate_user, do_save=True)
  262. return
  263. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  264. @require_profile(Group.TIM_ADMIN)
  265. @hapic.input_path(UserIdPathSchema())
  266. @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8
  267. def disable_user(self, context, request: TracimRequest, hapic_data=None):
  268. """
  269. disable user
  270. """
  271. app_config = request.registry.settings['CFG']
  272. uapi = UserApi(
  273. current_user=request.current_user, # User
  274. session=request.dbsession,
  275. config=app_config,
  276. )
  277. uapi.disable(user=request.candidate_user, do_save=True)
  278. return
  279. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  280. @require_profile(Group.TIM_ADMIN)
  281. @hapic.input_path(UserIdPathSchema())
  282. @hapic.input_body(UserProfileSchema())
  283. @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8
  284. def set_profile(self, context, request: TracimRequest, hapic_data=None):
  285. """
  286. set user profile
  287. """
  288. app_config = request.registry.settings['CFG']
  289. uapi = UserApi(
  290. current_user=request.current_user, # User
  291. session=request.dbsession,
  292. config=app_config,
  293. )
  294. gapi = GroupApi(
  295. current_user=request.current_user, # User
  296. session=request.dbsession,
  297. config=app_config,
  298. )
  299. groups = [gapi.get_one_with_name(hapic_data.body.profile)]
  300. uapi.update(
  301. user=request.candidate_user,
  302. groups=groups,
  303. do_save=True,
  304. )
  305. return
  306. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  307. @require_same_user_or_profile(Group.TIM_ADMIN)
  308. @hapic.input_path(UserWorkspaceIdPathSchema())
  309. @hapic.input_query(ActiveContentFilterQuerySchema())
  310. @hapic.output_body(ContentDigestSchema(many=True))
  311. def last_active_content(self, context, request: TracimRequest, hapic_data=None): # nopep8
  312. """
  313. Get last_active_content for user
  314. """
  315. app_config = request.registry.settings['CFG']
  316. content_filter = hapic_data.query
  317. api = ContentApi(
  318. current_user=request.candidate_user, # User
  319. session=request.dbsession,
  320. config=app_config,
  321. )
  322. wapi = WorkspaceApi(
  323. current_user=request.candidate_user, # User
  324. session=request.dbsession,
  325. config=app_config,
  326. )
  327. workspace = None
  328. if hapic_data.path.workspace_id:
  329. workspace = wapi.get_one(hapic_data.path.workspace_id)
  330. before_content = None
  331. if content_filter.before_content_id:
  332. before_content = api.get_one(
  333. content_id=content_filter.before_content_id,
  334. workspace=workspace,
  335. content_type=CONTENT_TYPES.Any_SLUG
  336. )
  337. last_actives = api.get_last_active(
  338. workspace=workspace,
  339. limit=content_filter.limit or None,
  340. before_content=before_content,
  341. )
  342. return [
  343. api.get_content_in_context(content)
  344. for content in last_actives
  345. ]
  346. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  347. @require_same_user_or_profile(Group.TIM_ADMIN)
  348. @hapic.input_path(UserWorkspaceIdPathSchema())
  349. @hapic.input_query(ContentIdsQuerySchema(), as_list=['contents_ids'])
  350. @hapic.output_body(ReadStatusSchema(many=True)) # nopep8
  351. def contents_read_status(self, context, request: TracimRequest, hapic_data=None): # nopep8
  352. """
  353. get user_read status of contents
  354. """
  355. app_config = request.registry.settings['CFG']
  356. content_filter = hapic_data.query
  357. api = ContentApi(
  358. current_user=request.candidate_user, # User
  359. session=request.dbsession,
  360. config=app_config,
  361. )
  362. wapi = WorkspaceApi(
  363. current_user=request.candidate_user, # User
  364. session=request.dbsession,
  365. config=app_config,
  366. )
  367. workspace = None
  368. if hapic_data.path.workspace_id:
  369. workspace = wapi.get_one(hapic_data.path.workspace_id)
  370. last_actives = api.get_last_active(
  371. workspace=workspace,
  372. limit=None,
  373. before_content=None,
  374. content_ids=hapic_data.query.contents_ids or None
  375. )
  376. return [
  377. api.get_content_in_context(content)
  378. for content in last_actives
  379. ]
  380. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  381. @require_same_user_or_profile(Group.TIM_ADMIN)
  382. @hapic.input_path(UserWorkspaceAndContentIdPathSchema())
  383. @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8
  384. def set_content_as_read(self, context, request: TracimRequest, hapic_data=None): # nopep8
  385. """
  386. set user_read status of content to read
  387. """
  388. app_config = request.registry.settings['CFG']
  389. api = ContentApi(
  390. show_archived=True,
  391. show_deleted=True,
  392. current_user=request.candidate_user,
  393. session=request.dbsession,
  394. config=app_config,
  395. )
  396. api.mark_read(request.current_content, do_flush=True)
  397. return
  398. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  399. @require_same_user_or_profile(Group.TIM_ADMIN)
  400. @hapic.input_path(UserWorkspaceAndContentIdPathSchema())
  401. @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8
  402. def set_content_as_unread(self, context, request: TracimRequest, hapic_data=None): # nopep8
  403. """
  404. set user_read status of content to unread
  405. """
  406. app_config = request.registry.settings['CFG']
  407. api = ContentApi(
  408. show_archived=True,
  409. show_deleted=True,
  410. current_user=request.candidate_user,
  411. session=request.dbsession,
  412. config=app_config,
  413. )
  414. api.mark_unread(request.current_content, do_flush=True)
  415. return
  416. @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
  417. @require_same_user_or_profile(Group.TIM_ADMIN)
  418. @hapic.input_path(UserWorkspaceIdPathSchema())
  419. @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8
  420. def set_workspace_as_read(self, context, request: TracimRequest, hapic_data=None): # nopep8
  421. """
  422. set user_read status of all content of workspace to read
  423. """
  424. app_config = request.registry.settings['CFG']
  425. api = ContentApi(
  426. show_archived=True,
  427. show_deleted=True,
  428. current_user=request.candidate_user,
  429. session=request.dbsession,
  430. config=app_config,
  431. )
  432. api.mark_read__workspace(request.current_workspace)
  433. return
  434. def bind(self, configurator: Configurator) -> None:
  435. """
  436. Create all routes and views using pyramid configurator
  437. for this controller
  438. """
  439. # user workspace
  440. configurator.add_route('user_workspace', '/users/{user_id}/workspaces', request_method='GET') # nopep8
  441. configurator.add_view(self.user_workspace, route_name='user_workspace')
  442. # user info
  443. configurator.add_route('user', '/users/{user_id}', request_method='GET') # nopep8
  444. configurator.add_view(self.user, route_name='user')
  445. # users lists
  446. configurator.add_route('users', '/users', request_method='GET') # nopep8
  447. configurator.add_view(self.users, route_name='users')
  448. # known members lists
  449. configurator.add_route('known_members', '/users/{user_id}/known_members', request_method='GET') # nopep8
  450. configurator.add_view(self.known_members, route_name='known_members')
  451. # set user email
  452. configurator.add_route('set_user_email', '/users/{user_id}/email', request_method='PUT') # nopep8
  453. configurator.add_view(self.set_user_email, route_name='set_user_email')
  454. # set user password
  455. configurator.add_route('set_user_password', '/users/{user_id}/password', request_method='PUT') # nopep8
  456. configurator.add_view(self.set_user_password, route_name='set_user_password') # nopep8
  457. # set user_info
  458. configurator.add_route('set_user_info', '/users/{user_id}', request_method='PUT') # nopep8
  459. configurator.add_view(self.set_user_infos, route_name='set_user_info')
  460. # create user
  461. configurator.add_route('create_user', '/users', request_method='POST')
  462. configurator.add_view(self.create_user, route_name='create_user')
  463. # enable user
  464. configurator.add_route('enable_user', '/users/{user_id}/enable', request_method='PUT') # nopep8
  465. configurator.add_view(self.enable_user, route_name='enable_user')
  466. # disable user
  467. configurator.add_route('disable_user', '/users/{user_id}/disable', request_method='PUT') # nopep8
  468. configurator.add_view(self.disable_user, route_name='disable_user')
  469. # delete user
  470. configurator.add_route('delete_user', '/users/{user_id}/delete', request_method='PUT') # nopep8
  471. configurator.add_view(self.delete_user, route_name='delete_user')
  472. # undelete user
  473. configurator.add_route('undelete_user', '/users/{user_id}/undelete', request_method='PUT') # nopep8
  474. configurator.add_view(self.undelete_user, route_name='undelete_user')
  475. # set user profile
  476. configurator.add_route('set_user_profile', '/users/{user_id}/profile', request_method='PUT') # nopep8
  477. configurator.add_view(self.set_profile, route_name='set_user_profile')
  478. # user content
  479. configurator.add_route('contents_read_status', '/users/{user_id}/workspaces/{workspace_id}/contents/read_status', request_method='GET') # nopep8
  480. configurator.add_view(self.contents_read_status, route_name='contents_read_status') # nopep8
  481. # last active content for user
  482. configurator.add_route('last_active_content', '/users/{user_id}/workspaces/{workspace_id}/contents/recently_active', request_method='GET') # nopep8
  483. configurator.add_view(self.last_active_content, route_name='last_active_content') # nopep8
  484. # set content as read/unread
  485. configurator.add_route('read_content', '/users/{user_id}/workspaces/{workspace_id}/contents/{content_id}/read', request_method='PUT') # nopep8
  486. configurator.add_view(self.set_content_as_read, route_name='read_content') # nopep8
  487. configurator.add_route('unread_content', '/users/{user_id}/workspaces/{workspace_id}/contents/{content_id}/unread', request_method='PUT') # nopep8
  488. configurator.add_view(self.set_content_as_unread, route_name='unread_content') # nopep8
  489. # set workspace as read
  490. configurator.add_route('read_workspace', '/users/{user_id}/workspaces/{workspace_id}/read', request_method='PUT') # nopep8
  491. configurator.add_view(self.set_workspace_as_read, route_name='read_workspace') # nopep8