user.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. # -*- coding: utf-8 -*-
  2. from smtplib import SMTPException
  3. import transaction
  4. import typing as typing
  5. from sqlalchemy.orm import Session
  6. from sqlalchemy.orm.exc import NoResultFound
  7. from tracim_backend import CFG
  8. from tracim_backend.models.auth import User
  9. from tracim_backend.models.auth import Group
  10. from tracim_backend.exceptions import NoUserSetted
  11. from tracim_backend.exceptions import PasswordDoNotMatch
  12. from tracim_backend.exceptions import EmailValidationFailed
  13. from tracim_backend.exceptions import UserDoesNotExist
  14. from tracim_backend.exceptions import WrongUserPassword
  15. from tracim_backend.exceptions import AuthenticationFailed
  16. from tracim_backend.exceptions import NotificationNotSend
  17. from tracim_backend.exceptions import UserNotActive
  18. from tracim_backend.models.context_models import UserInContext
  19. from tracim_backend.lib.mail_notifier.notifier import get_email_manager
  20. from tracim_backend.models.context_models import TypeUser
  21. class UserApi(object):
  22. def __init__(
  23. self,
  24. current_user: typing.Optional[User],
  25. session: Session,
  26. config: CFG,
  27. ) -> None:
  28. self._session = session
  29. self._user = current_user
  30. self._config = config
  31. def _base_query(self):
  32. return self._session.query(User)
  33. def get_user_with_context(self, user: User) -> UserInContext:
  34. """
  35. Return UserInContext object from User
  36. """
  37. user = UserInContext(
  38. user=user,
  39. dbsession=self._session,
  40. config=self._config,
  41. )
  42. return user
  43. # Getters
  44. def get_one(self, user_id: int) -> User:
  45. """
  46. Get one user by user id
  47. """
  48. try:
  49. user = self._base_query().filter(User.user_id == user_id).one()
  50. except NoResultFound as exc:
  51. raise UserDoesNotExist('User "{}" not found in database'.format(user_id)) from exc # nopep8
  52. return user
  53. def get_one_by_email(self, email: str) -> User:
  54. """
  55. Get one user by email
  56. :param email: Email of the user
  57. :return: one user
  58. """
  59. try:
  60. user = self._base_query().filter(User.email == email).one()
  61. except NoResultFound as exc:
  62. raise UserDoesNotExist('User "{}" not found in database'.format(email)) from exc # nopep8
  63. return user
  64. def get_one_by_public_name(self, public_name: str) -> User:
  65. """
  66. Get one user by public_name
  67. """
  68. try:
  69. user = self._base_query().filter(User.display_name == public_name).one()
  70. except NoResultFound as exc:
  71. raise UserDoesNotExist('User "{}" not found in database'.format(public_name)) from exc # nopep8
  72. return user
  73. # FIXME - G.M - 24-04-2018 - Duplicate method with get_one.
  74. def get_one_by_id(self, id: int) -> User:
  75. return self.get_one(user_id=id)
  76. def get_current_user(self) -> User:
  77. """
  78. Get current_user
  79. """
  80. if not self._user:
  81. raise UserDoesNotExist('There is no current user')
  82. return self._user
  83. def get_all(self) -> typing.Iterable[User]:
  84. return self._session.query(User).order_by(User.display_name).all()
  85. def find(
  86. self,
  87. user_id: int=None,
  88. email: str=None,
  89. public_name: str=None
  90. ) -> typing.Tuple[TypeUser, User]:
  91. """
  92. Find existing user from all theses params.
  93. Check is made in this order: user_id, email, public_name
  94. If no user found raise UserDoesNotExist exception
  95. """
  96. user = None
  97. if user_id:
  98. try:
  99. user = self.get_one(user_id)
  100. return TypeUser.USER_ID, user
  101. except UserDoesNotExist:
  102. pass
  103. if email:
  104. try:
  105. user = self.get_one_by_email(email)
  106. return TypeUser.EMAIL, user
  107. except UserDoesNotExist:
  108. pass
  109. if public_name:
  110. try:
  111. user = self.get_one_by_public_name(public_name)
  112. return TypeUser.PUBLIC_NAME, user
  113. except UserDoesNotExist:
  114. pass
  115. raise UserDoesNotExist('User not found with any of given params.')
  116. # Check methods
  117. def user_with_email_exists(self, email: str) -> bool:
  118. try:
  119. self.get_one_by_email(email)
  120. return True
  121. # TODO - G.M - 09-04-2018 - Better exception
  122. except:
  123. return False
  124. def authenticate_user(self, email: str, password: str) -> User:
  125. """
  126. Authenticate user with email and password, raise AuthenticationFailed
  127. if uncorrect.
  128. :param email: email of the user
  129. :param password: cleartext password of the user
  130. :return: User who was authenticated.
  131. """
  132. try:
  133. user = self.get_one_by_email(email)
  134. if not user.is_active:
  135. raise UserNotActive('User "{}" is not active'.format(email))
  136. if user.validate_password(password):
  137. return user
  138. else:
  139. raise WrongUserPassword('User "{}" password is incorrect'.format(email)) # nopep8
  140. except (WrongUserPassword, UserDoesNotExist) as exc:
  141. raise AuthenticationFailed('User "{}" authentication failed'.format(email)) from exc # nopep8
  142. # Actions
  143. def set_password(
  144. self,
  145. user: User,
  146. loggedin_user_password: str,
  147. new_password: str,
  148. new_password2: str,
  149. do_save: bool=True
  150. ):
  151. """
  152. Set User password if loggedin user password is correct
  153. and both new_password are the same.
  154. :param user: User who need password changed
  155. :param loggedin_user_password: cleartext password of logged user (not
  156. same as user)
  157. :param new_password: new password for user
  158. :param new_password2: should be same as new_password
  159. :param do_save: should we save new user password ?
  160. :return:
  161. """
  162. if not self._user:
  163. raise NoUserSetted('Current User should be set in UserApi to use this method') # nopep8
  164. if not self._user.validate_password(loggedin_user_password): # nopep8
  165. raise WrongUserPassword(
  166. 'Wrong password for authenticated user {}'. format(self._user.user_id) # nopep8
  167. )
  168. if new_password != new_password2:
  169. raise PasswordDoNotMatch('Passwords given are different')
  170. self.update(
  171. user=user,
  172. password=new_password,
  173. do_save=do_save,
  174. )
  175. if do_save:
  176. # TODO - G.M - 2018-07-24 - Check why commit is needed here
  177. transaction.commit()
  178. return user
  179. def set_email(
  180. self,
  181. user: User,
  182. loggedin_user_password: str,
  183. email: str,
  184. do_save: bool = True
  185. ):
  186. """
  187. Set email address of user if loggedin user password is correct
  188. :param user: User who need email changed
  189. :param loggedin_user_password: cleartext password of logged user (not
  190. same as user)
  191. :param email:
  192. :param do_save:
  193. :return:
  194. """
  195. if not self._user:
  196. raise NoUserSetted('Current User should be set in UserApi to use this method') # nopep8
  197. if not self._user.validate_password(loggedin_user_password): # nopep8
  198. raise WrongUserPassword(
  199. 'Wrong password for authenticated user {}'. format(self._user.user_id) # nopep8
  200. )
  201. self.update(
  202. user=user,
  203. email=email,
  204. do_save=do_save,
  205. )
  206. return user
  207. def _check_email(self, email: str) -> bool:
  208. # TODO - G.M - 2018-07-05 - find a better way to check email
  209. if not email:
  210. return False
  211. email = email.split('@')
  212. if len(email) != 2:
  213. return False
  214. return True
  215. def update(
  216. self,
  217. user: User,
  218. name: str=None,
  219. email: str=None,
  220. password: str=None,
  221. timezone: str=None,
  222. groups: typing.Optional[typing.List[Group]]=None,
  223. do_save=True,
  224. ) -> User:
  225. if name is not None:
  226. user.display_name = name
  227. if email is not None:
  228. email_exist = self._check_email(email)
  229. if not email_exist:
  230. raise EmailValidationFailed('Email given form {} is uncorrect'.format(email)) # nopep8
  231. user.email = email
  232. if password is not None:
  233. user.password = password
  234. if timezone is not None:
  235. user.timezone = timezone
  236. if groups is not None:
  237. # INFO - G.M - 2018-07-18 - Delete old groups
  238. for group in user.groups:
  239. if group not in groups:
  240. user.groups.remove(group)
  241. # INFO - G.M - 2018-07-18 - add new groups
  242. for group in groups:
  243. if group not in user.groups:
  244. user.groups.append(group)
  245. if do_save:
  246. self.save(user)
  247. return user
  248. def create_user(
  249. self,
  250. email,
  251. password: str = None,
  252. name: str = None,
  253. timezone: str = '',
  254. groups=[],
  255. do_save: bool=True,
  256. do_notify: bool=True,
  257. ) -> User:
  258. new_user = self.create_minimal_user(email, groups, save_now=False)
  259. self.update(
  260. user=new_user,
  261. name=name,
  262. email=email,
  263. password=password,
  264. timezone=timezone,
  265. do_save=False,
  266. )
  267. if do_notify:
  268. try:
  269. email_manager = get_email_manager(self._config, self._session)
  270. email_manager.notify_created_account(
  271. new_user,
  272. password=password
  273. )
  274. except SMTPException as e:
  275. raise NotificationNotSend()
  276. if do_save:
  277. self.save(new_user)
  278. return new_user
  279. def create_minimal_user(
  280. self,
  281. email,
  282. groups=[],
  283. save_now=False
  284. ) -> User:
  285. """Previous create_user method"""
  286. user = User()
  287. email_exist = self._check_email(email)
  288. if not email_exist:
  289. raise EmailValidationFailed('Email given form {} is uncorrect'.format(email)) # nopep8
  290. user.email = email
  291. user.display_name = email.split('@')[0]
  292. for group in groups:
  293. user.groups.append(group)
  294. self._session.add(user)
  295. if save_now:
  296. self._session.flush()
  297. return user
  298. def enable(self, user: User, do_save=False):
  299. user.is_active = True
  300. if do_save:
  301. self.save(user)
  302. def disable(self, user:User, do_save=False):
  303. user.is_active = False
  304. if do_save:
  305. self.save(user)
  306. def save(self, user: User):
  307. self._session.flush()
  308. def execute_created_user_actions(self, created_user: User) -> None:
  309. """
  310. Execute actions when user just been created
  311. :return:
  312. """
  313. # NOTE: Cyclic import
  314. # TODO - G.M - 28-03-2018 - [Calendar] Reenable Calendar stuff
  315. #from tracim.lib.calendar import CalendarManager
  316. #from tracim.model.organisational import UserCalendar
  317. # TODO - G.M - 04-04-2018 - [auth]
  318. # Check if this is already needed with
  319. # new auth system
  320. created_user.ensure_auth_token(
  321. session=self._session,
  322. validity_seconds=self._config.USER_AUTH_TOKEN_VALIDITY
  323. )
  324. # Ensure database is up-to-date
  325. self._session.flush()
  326. transaction.commit()
  327. # TODO - G.M - 28-03-2018 - [Calendar] Reenable Calendar stuff
  328. # calendar_manager = CalendarManager(created_user)
  329. # calendar_manager.create_then_remove_fake_event(
  330. # calendar_class=UserCalendar,
  331. # related_object_id=created_user.user_id,
  332. # )