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 import CFG
  8. from tracim.models.auth import User
  9. from tracim.models.auth import Group
  10. from tracim.exceptions import NoUserSetted
  11. from tracim.exceptions import PasswordDoNotMatch
  12. from tracim.exceptions import EmailValidationFailed
  13. from tracim.exceptions import UserDoesNotExist
  14. from tracim.exceptions import WrongUserPassword
  15. from tracim.exceptions import AuthenticationFailed
  16. from tracim.exceptions import NotificationNotSend
  17. from tracim.exceptions import UserNotActive
  18. from tracim.models.context_models import UserInContext
  19. from tracim.lib.mail_notifier.notifier import get_email_manager
  20. from tracim.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. # )