user.py 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. # -*- coding: utf-8 -*-
  2. import random
  3. import pytz
  4. from tracim import model as pm
  5. import tg
  6. from tg import predicates
  7. from tg import tmpl_context
  8. from tg.i18n import ugettext as _
  9. from tracim.controllers import TIMRestController
  10. from tracim.controllers.user import UserWorkspaceRestController
  11. from tracim.lib import CST
  12. from tracim.lib import helpers as h
  13. from tracim.lib.base import logger
  14. from tracim.lib.email import get_email_manager
  15. from tracim.lib.group import GroupApi
  16. from tracim.lib.user import UserApi
  17. from tracim.lib.userworkspace import RoleApi
  18. from tracim.lib.workspace import WorkspaceApi
  19. from tracim.model import DBSession
  20. from tracim.model.auth import Group
  21. from tracim.model.serializers import CTX
  22. from tracim.model.serializers import Context
  23. from tracim.model.serializers import DictLikeClass
  24. class UserProfileAdminRestController(TIMRestController):
  25. """CRUD Controller allowing to manage groups of a user."""
  26. allow_only = predicates.in_group(Group.TIM_ADMIN_GROUPNAME)
  27. _ALLOWED_PROFILE_USER = 'tracim-profile-user'
  28. _ALLOWED_PROFILE_MANAGER = 'tracim-profile-manager'
  29. _ALLOWED_PROFILE_ADMIN = 'tracim-profile-admin'
  30. @property
  31. def allowed_profiles(self):
  32. return [
  33. UserProfileAdminRestController._ALLOWED_PROFILE_USER,
  34. UserProfileAdminRestController._ALLOWED_PROFILE_MANAGER,
  35. UserProfileAdminRestController._ALLOWED_PROFILE_ADMIN,
  36. ]
  37. def _before(self, *args, **kw):
  38. """
  39. Instantiate the current workspace in tg.tmpl_context.
  40. :param args:
  41. :param kw:
  42. :return:
  43. """
  44. super(self.__class__, self)._before(args, kw)
  45. api = UserApi(tg.tmpl_context.current_user)
  46. user_id = tg.request.controller_state.routing_args.get('user_id')
  47. user = api.get_one(user_id)
  48. tg.tmpl_context.user_id = user_id
  49. tg.tmpl_context.user = user
  50. @tg.expose()
  51. def switch(self, new_role) -> None:
  52. """
  53. Switch to the given new role.
  54. :param new_role: value should be:
  55. 'tracim-user',
  56. 'tracim-manager' (allowed to create workspaces) or
  57. 'tracim-admin' (admin the whole system)
  58. """
  59. return self.put(new_role)
  60. @tg.expose()
  61. def put(self, new_profile):
  62. # FIXME - Allow only self password or operation for managers
  63. current_user = tmpl_context.current_user
  64. user = tmpl_context.user
  65. group_api = GroupApi(current_user)
  66. if current_user.user_id == user.user_id:
  67. tg.flash(_('You can\'t change your own profile'), CST.STATUS_ERROR)
  68. tg.redirect(self.parent_controller.url())
  69. redirect_url = self.parent_controller.url(skip_id=True)
  70. if new_profile not in self.allowed_profiles:
  71. tg.flash(_('Unknown profile'), CST.STATUS_ERROR)
  72. tg.redirect(redirect_url)
  73. pod_user_group = group_api.get_one(Group.TIM_USER)
  74. pod_manager_group = group_api.get_one(Group.TIM_MANAGER)
  75. pod_admin_group = group_api.get_one(Group.TIM_ADMIN)
  76. # this is the default value ; should never appear
  77. flash_message = _('User updated.')
  78. if new_profile == UserProfileAdminRestController._ALLOWED_PROFILE_USER:
  79. if pod_user_group not in user.groups:
  80. user.groups.append(pod_user_group)
  81. try:
  82. user.groups.remove(pod_manager_group)
  83. except:
  84. pass
  85. try:
  86. user.groups.remove(pod_admin_group)
  87. except:
  88. pass
  89. flash_message = _('User {} is now a basic user').format(user.get_display_name())
  90. elif new_profile == UserProfileAdminRestController._ALLOWED_PROFILE_MANAGER:
  91. if pod_user_group not in user.groups:
  92. user.groups.append(pod_user_group)
  93. if pod_manager_group not in user.groups:
  94. user.groups.append(pod_manager_group)
  95. try:
  96. user.groups.remove(pod_admin_group)
  97. except:
  98. pass
  99. flash_message = _('User {} can now workspaces').format(user.get_display_name())
  100. elif new_profile == UserProfileAdminRestController._ALLOWED_PROFILE_ADMIN:
  101. if pod_user_group not in user.groups:
  102. user.groups.append(pod_user_group)
  103. if pod_manager_group not in user.groups:
  104. user.groups.append(pod_manager_group)
  105. if pod_admin_group not in user.groups:
  106. user.groups.append(pod_admin_group)
  107. flash_message = _('User {} is now an administrator').format(user.get_display_name())
  108. else:
  109. error_msg = \
  110. 'Trying to change user {} profile with unexpected profile {}'
  111. logger.error(self, error_msg.format(user.user_id, new_profile))
  112. tg.flash(_('Unknown profile'), CST.STATUS_ERROR)
  113. tg.redirect(redirect_url)
  114. DBSession.flush()
  115. tg.flash(flash_message, CST.STATUS_OK)
  116. tg.redirect(redirect_url)
  117. def get_edit(self):
  118. pass
  119. def get_all(self):
  120. pass
  121. def post(self):
  122. pass
  123. class UserPasswordAdminRestController(TIMRestController):
  124. """CRUD Controller allowing to manage password of a given user."""
  125. allow_only = predicates.in_any_group(
  126. Group.TIM_MANAGER_GROUPNAME,
  127. Group.TIM_ADMIN_GROUPNAME,
  128. )
  129. def _before(self, *args, **kw):
  130. """
  131. Instantiate the current workspace in tg.tmpl_context.
  132. :param args:
  133. :param kw:
  134. :return:
  135. """
  136. super(self.__class__, self)._before(args, kw)
  137. api = UserApi(tg.tmpl_context.current_user)
  138. user_id = tg.request.controller_state.routing_args.get('user_id')
  139. user = api.get_one(user_id)
  140. tg.tmpl_context.user_id = user_id
  141. tg.tmpl_context.user = user
  142. @tg.expose('tracim.templates.admin.user_password_edit')
  143. def edit(self):
  144. current_user = tmpl_context.current_user
  145. api = UserApi(current_user)
  146. dictified_user = Context(CTX.USER).toDict(tmpl_context.user, 'user')
  147. return DictLikeClass(result=dictified_user)
  148. @tg.expose()
  149. def put(self, new_password1, new_password2, next_url=''):
  150. # FIXME - Manage
  151. current_user = tmpl_context.current_user
  152. user = tmpl_context.user
  153. if not next_url:
  154. next_url = tg.lurl('/admin/users/{}'.format(user.user_id))
  155. if not new_password1 or not new_password2:
  156. tg.flash(_('Empty password is not allowed.'), CST.STATUS_ERROR)
  157. tg.redirect(next_url)
  158. if new_password1 != new_password2:
  159. tg.flash(_('New passwords do not match.'), CST.STATUS_ERROR)
  160. tg.redirect(next_url)
  161. user.password = new_password1
  162. pm.DBSession.flush()
  163. tg.flash(_('The password has been changed'), CST.STATUS_OK)
  164. tg.redirect(next_url)
  165. class UserWorkspaceRestController(TIMRestController):
  166. def _before(self, *args, **kw):
  167. """
  168. Instantiate the current workspace in tg.tmpl_context.
  169. :param args:
  170. :param kw:
  171. :return:
  172. """
  173. super(self.__class__, self)._before(args, kw)
  174. api = UserApi(tg.tmpl_context.current_user)
  175. user_id = tg.request.controller_state.routing_args.get('user_id')
  176. user = api.get_one(user_id)
  177. tg.tmpl_context.user_id = user_id
  178. tg.tmpl_context.user = user
  179. @tg.expose()
  180. def enable_notifications(self, workspace_id, next_url=None):
  181. workspace_id = int(workspace_id)
  182. api = WorkspaceApi(tg.tmpl_context.current_user)
  183. workspace = api.get_one(workspace_id)
  184. api.enable_notifications(tg.tmpl_context.user, workspace)
  185. tg.flash(_('User {}: notification enabled for workspace {}').format(
  186. tg.tmpl_context.user.get_display_name(), workspace.label))
  187. if next_url:
  188. tg.redirect(tg.url(next_url))
  189. tg.redirect(self.parent_controller.url(None, 'me'))
  190. @tg.expose()
  191. def disable_notifications(self, workspace_id, next_url=None):
  192. workspace_id = int(workspace_id)
  193. api = WorkspaceApi(tg.tmpl_context.current_user)
  194. workspace = api.get_one(workspace_id)
  195. api.disable_notifications(tg.tmpl_context.user, workspace)
  196. tg.flash(_('User {}: notification disabled for workspace {}').format(
  197. tg.tmpl_context.user.get_display_name(), workspace.label))
  198. if next_url:
  199. tg.redirect(tg.url(next_url))
  200. tg.redirect(self.parent_controller.url(None, 'me'))
  201. class UserRestController(TIMRestController):
  202. """CRUD Controller allowing to manage Users."""
  203. allow_only = predicates.in_any_group(
  204. Group.TIM_MANAGER_GROUPNAME,
  205. Group.TIM_ADMIN_GROUPNAME,
  206. )
  207. password = UserPasswordAdminRestController()
  208. profile = UserProfileAdminRestController()
  209. workspaces = UserWorkspaceRestController()
  210. PASSWORD_LENGTH = 12
  211. PASSWORD_CHARACTERS = '0123456789' \
  212. 'abcdefghijklmonpqrstuvwxyz' \
  213. 'ABCDEFGHIJKLMONPQRSTUVWXYZ'
  214. @classmethod
  215. def current_item_id_key_in_context(cls):
  216. return 'user_id'
  217. @tg.require(predicates.in_group(Group.TIM_MANAGER_GROUPNAME))
  218. @tg.expose('tracim.templates.admin.user_getall')
  219. def get_all(self, *args, **kw):
  220. current_user = tmpl_context.current_user
  221. api = UserApi(current_user)
  222. users = api.get_all()
  223. current_user_content = Context(CTX.CURRENT_USER).toDict(current_user)
  224. fake_api = Context(CTX.USERS).toDict({'current_user': current_user_content})
  225. dictified_users = Context(CTX.USERS).toDict(users, 'users', 'user_nb')
  226. return DictLikeClass(result=dictified_users, fake_api=fake_api)
  227. @tg.require(predicates.in_group(Group.TIM_MANAGER_GROUPNAME))
  228. @tg.expose()
  229. def post(
  230. self,
  231. name: str,
  232. email: str,
  233. password: str,
  234. is_tracim_manager: str='off',
  235. is_tracim_admin: str='off',
  236. send_email: str='off',
  237. ):
  238. is_tracim_manager = h.on_off_to_boolean(is_tracim_manager)
  239. is_tracim_admin = h.on_off_to_boolean(is_tracim_admin)
  240. send_email = h.on_off_to_boolean(send_email)
  241. current_user = tmpl_context.current_user
  242. if current_user.profile.id < Group.TIM_ADMIN:
  243. # A manager can't give large rights
  244. is_tracim_manager = False
  245. is_tracim_admin = False
  246. api = UserApi(current_user)
  247. if api.user_with_email_exists(email):
  248. tg.flash(_('A user with email address "{}" already exists.').format(email), CST.STATUS_ERROR)
  249. tg.redirect(self.url())
  250. user = api.create_user()
  251. user.email = email
  252. user.display_name = name
  253. if password:
  254. user.password = password
  255. elif send_email:
  256. # Setup a random password to send email at user
  257. password = self.generate_password()
  258. user.password = password
  259. api.save(user)
  260. # Now add the user to related groups
  261. group_api = GroupApi(current_user)
  262. user.groups.append(group_api.get_one(Group.TIM_USER))
  263. if is_tracim_manager:
  264. user.groups.append(group_api.get_one(Group.TIM_MANAGER))
  265. if is_tracim_admin:
  266. user.groups.append(group_api.get_one(Group.TIM_ADMIN))
  267. api.save(user)
  268. email_sent = True
  269. if send_email:
  270. email_manager = get_email_manager()
  271. try:
  272. email_manager.notify_created_account(user, password=password)
  273. except Exception:
  274. email_sent = False
  275. api.execute_created_user_actions(user)
  276. if not email_sent:
  277. tg.flash(_('User {0} created but email was not sent to {1}').format(user.get_display_name(), user.email),
  278. CST.STATUS_WARNING)
  279. else:
  280. tg.flash(_('User {} created.').format(user.get_display_name()), CST.STATUS_OK)
  281. tg.redirect(self.url())
  282. @classmethod
  283. def generate_password(
  284. cls,
  285. password_length=PASSWORD_LENGTH,
  286. password_chars=PASSWORD_CHARACTERS,
  287. ):
  288. # character list that will be contained into the password
  289. char_list = []
  290. for _unused in range(password_length):
  291. # This puts a random char from the list above inside
  292. # the list of chars and then merges them into a String
  293. char_list.append(random.choice(password_chars))
  294. password = ''.join(char_list)
  295. return password
  296. @tg.expose('tracim.templates.admin.user_getone')
  297. def get_one(self, user_id):
  298. current_user = tmpl_context.current_user
  299. api = UserApi(current_user)
  300. # role_api = RoleApi(tg.tmpl_context.current_user)
  301. # user_api = UserApi(tg.tmpl_context.current_user)
  302. user = api.get_one(user_id) # FIXME
  303. role_api = RoleApi(tg.tmpl_context.current_user)
  304. role_list = role_api.get_roles_for_select_field()
  305. dictified_user = Context(CTX.ADMIN_USER).toDict(user, 'user')
  306. current_user_content = Context(CTX.CURRENT_USER).toDict(tmpl_context.current_user)
  307. fake_api_content = DictLikeClass(current_user=current_user_content,
  308. role_types=role_list)
  309. fake_api = Context(CTX.ADMIN_USER).toDict(fake_api_content)
  310. return DictLikeClass(result=dictified_user, fake_api=fake_api)
  311. @tg.expose('tracim.templates.admin.user_edit')
  312. def edit(self, id):
  313. current_user = tmpl_context.current_user
  314. api = UserApi(current_user)
  315. user = api.get_one(id)
  316. dictified_user = Context(CTX.USER).toDict(user, 'user')
  317. return DictLikeClass(
  318. result=dictified_user,
  319. timezones=pytz.all_timezones,
  320. )
  321. @tg.require(predicates.in_group(Group.TIM_MANAGER_GROUPNAME))
  322. @tg.expose()
  323. def put(self, user_id, name, email, timezone: str='', next_url=''):
  324. api = UserApi(tmpl_context.current_user)
  325. user = api.get_one(int(user_id))
  326. api.update(user, name, email, True, timezone=timezone)
  327. tg.flash(_('User {} updated.').format(user.get_display_name()), CST.STATUS_OK)
  328. if next_url:
  329. tg.redirect(next_url)
  330. tg.redirect(self.url())
  331. @tg.require(predicates.in_group(Group.TIM_ADMIN_GROUPNAME))
  332. @tg.expose()
  333. def enable(self, id, next_url=None):
  334. current_user = tmpl_context.current_user
  335. api = UserApi(current_user)
  336. user = api.get_one(id)
  337. user.is_active = True
  338. api.save(user)
  339. tg.flash(_('User {} enabled.').format(user.get_display_name()), CST.STATUS_OK)
  340. if next_url == 'user':
  341. tg.redirect(self.url(id=user.user_id))
  342. tg.redirect(self.url())
  343. @tg.require(predicates.in_group(Group.TIM_ADMIN_GROUPNAME))
  344. @tg.expose()
  345. def disable(self, id, next_url=None):
  346. id = int(id)
  347. current_user = tmpl_context.current_user
  348. api = UserApi(current_user)
  349. if current_user.user_id == id:
  350. tg.flash(_('You can\'t de-activate your own account'), CST.STATUS_ERROR)
  351. else:
  352. user = api.get_one(id)
  353. user.is_active = False
  354. api.save(user)
  355. tg.flash(_('User {} disabled').format(user.get_display_name()), CST.STATUS_OK)
  356. if next_url == 'user':
  357. tg.redirect(self.url(id=user.user_id))
  358. tg.redirect(self.url())