PageRenderTime 63ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 0ms

/addons/auth_signup/res_users.py

https://gitlab.com/thanhchatvn/cloud-odoo
Python | 308 lines | 280 code | 13 blank | 15 comment | 18 complexity | c5d68cab793c086f8fc1721ba462634e MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. from datetime import datetime, timedelta
  4. import random
  5. from urlparse import urljoin
  6. import werkzeug
  7. from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
  8. from openerp.osv import osv, fields
  9. from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT, ustr
  10. from ast import literal_eval
  11. from openerp.tools.translate import _
  12. from openerp.exceptions import UserError
  13. class SignupError(Exception):
  14. pass
  15. def random_token():
  16. # the token has an entropy of about 120 bits (6 bits/char * 20 chars)
  17. chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
  18. return ''.join(random.SystemRandom().choice(chars) for i in xrange(20))
  19. def now(**kwargs):
  20. dt = datetime.now() + timedelta(**kwargs)
  21. return dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
  22. class res_partner(osv.Model):
  23. _inherit = 'res.partner'
  24. def _get_signup_valid(self, cr, uid, ids, name, arg, context=None):
  25. dt = now()
  26. res = {}
  27. for partner in self.browse(cr, uid, ids, context):
  28. res[partner.id] = bool(partner.signup_token) and \
  29. (not partner.signup_expiration or dt <= partner.signup_expiration)
  30. return res
  31. def _get_signup_url_for_action(self, cr, uid, ids, action=None, view_type=None, menu_id=None, res_id=None, model=None, context=None):
  32. """ generate a signup url for the given partner ids and action, possibly overriding
  33. the url state components (menu_id, id, view_type) """
  34. if context is None:
  35. context= {}
  36. res = dict.fromkeys(ids, False)
  37. base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
  38. for partner in self.browse(cr, uid, ids, context):
  39. # when required, make sure the partner has a valid signup token
  40. if context.get('signup_valid') and not partner.user_ids:
  41. self.signup_prepare(cr, uid, [partner.id], context=context)
  42. route = 'login'
  43. # the parameters to encode for the query
  44. query = dict(db=cr.dbname)
  45. signup_type = context.get('signup_force_type_in_url', partner.signup_type or '')
  46. if signup_type:
  47. route = 'reset_password' if signup_type == 'reset' else signup_type
  48. if partner.signup_token and signup_type:
  49. query['token'] = partner.signup_token
  50. elif partner.user_ids:
  51. query['login'] = partner.user_ids[0].login
  52. else:
  53. continue # no signup token, no user, thus no signup url!
  54. fragment = dict()
  55. base = '/web#'
  56. if action == '/mail/view':
  57. base = '/mail/view?'
  58. elif action:
  59. fragment['action'] = action
  60. if view_type:
  61. fragment['view_type'] = view_type
  62. if menu_id:
  63. fragment['menu_id'] = menu_id
  64. if model:
  65. fragment['model'] = model
  66. if res_id:
  67. fragment['res_id'] = res_id
  68. if fragment:
  69. query['redirect'] = base + werkzeug.url_encode(fragment)
  70. res[partner.id] = urljoin(base_url, "/web/%s?%s" % (route, werkzeug.url_encode(query)))
  71. return res
  72. def _get_signup_url(self, cr, uid, ids, name, arg, context=None):
  73. """ proxy for function field towards actual implementation """
  74. return self._get_signup_url_for_action(cr, uid, ids, context=context)
  75. _columns = {
  76. 'signup_token': fields.char('Signup Token', copy=False),
  77. 'signup_type': fields.char('Signup Token Type', copy=False),
  78. 'signup_expiration': fields.datetime('Signup Expiration', copy=False),
  79. 'signup_valid': fields.function(_get_signup_valid, type='boolean', string='Signup Token is Valid'),
  80. 'signup_url': fields.function(_get_signup_url, type='char', string='Signup URL'),
  81. }
  82. def action_signup_prepare(self, cr, uid, ids, context=None):
  83. return self.signup_prepare(cr, uid, ids, context=context)
  84. def signup_cancel(self, cr, uid, ids, context=None):
  85. return self.write(cr, uid, ids, {'signup_token': False, 'signup_type': False, 'signup_expiration': False}, context=context)
  86. def signup_prepare(self, cr, uid, ids, signup_type="signup", expiration=False, context=None):
  87. """ generate a new token for the partners with the given validity, if necessary
  88. :param expiration: the expiration datetime of the token (string, optional)
  89. """
  90. for partner in self.browse(cr, uid, ids, context):
  91. if expiration or not partner.signup_valid:
  92. token = random_token()
  93. while self._signup_retrieve_partner(cr, uid, token, context=context):
  94. token = random_token()
  95. partner.write({'signup_token': token, 'signup_type': signup_type, 'signup_expiration': expiration})
  96. return True
  97. def _signup_retrieve_partner(self, cr, uid, token,
  98. check_validity=False, raise_exception=False, context=None):
  99. """ find the partner corresponding to a token, and possibly check its validity
  100. :param token: the token to resolve
  101. :param check_validity: if True, also check validity
  102. :param raise_exception: if True, raise exception instead of returning False
  103. :return: partner (browse record) or False (if raise_exception is False)
  104. """
  105. partner_ids = self.search(cr, uid, [('signup_token', '=', token)], context=context)
  106. if not partner_ids:
  107. if raise_exception:
  108. raise SignupError("Signup token '%s' is not valid" % token)
  109. return False
  110. partner = self.browse(cr, uid, partner_ids[0], context)
  111. if check_validity and not partner.signup_valid:
  112. if raise_exception:
  113. raise SignupError("Signup token '%s' is no longer valid" % token)
  114. return False
  115. return partner
  116. def signup_retrieve_info(self, cr, uid, token, context=None):
  117. """ retrieve the user info about the token
  118. :return: a dictionary with the user information:
  119. - 'db': the name of the database
  120. - 'token': the token, if token is valid
  121. - 'name': the name of the partner, if token is valid
  122. - 'login': the user login, if the user already exists
  123. - 'email': the partner email, if the user does not exist
  124. """
  125. partner = self._signup_retrieve_partner(cr, uid, token, raise_exception=True, context=None)
  126. res = {'db': cr.dbname}
  127. if partner.signup_valid:
  128. res['token'] = token
  129. res['name'] = partner.name
  130. if partner.user_ids:
  131. res['login'] = partner.user_ids[0].login
  132. else:
  133. res['email'] = res['login'] = partner.email or ''
  134. return res
  135. class res_users(osv.Model):
  136. _inherit = 'res.users'
  137. def _get_state(self, cr, uid, ids, name, arg, context=None):
  138. res = {}
  139. for user in self.browse(cr, uid, ids, context):
  140. res[user.id] = ('active' if user.login_date else 'new')
  141. return res
  142. _columns = {
  143. 'state': fields.function(_get_state, string='Status', type='selection',
  144. selection=[('new', 'Never Connected'), ('active', 'Connected')]),
  145. }
  146. def signup(self, cr, uid, values, token=None, context=None):
  147. """ signup a user, to either:
  148. - create a new user (no token), or
  149. - create a user for a partner (with token, but no user for partner), or
  150. - change the password of a user (with token, and existing user).
  151. :param values: a dictionary with field values that are written on user
  152. :param token: signup token (optional)
  153. :return: (dbname, login, password) for the signed up user
  154. """
  155. if token:
  156. # signup with a token: find the corresponding partner id
  157. res_partner = self.pool.get('res.partner')
  158. partner = res_partner._signup_retrieve_partner(
  159. cr, uid, token, check_validity=True, raise_exception=True, context=None)
  160. # invalidate signup token
  161. partner.write({'signup_token': False, 'signup_type': False, 'signup_expiration': False})
  162. partner_user = partner.user_ids and partner.user_ids[0] or False
  163. # avoid overwriting existing (presumably correct) values with geolocation data
  164. if partner.country_id or partner.zip or partner.city:
  165. values.pop('city', None)
  166. values.pop('country_id', None)
  167. if partner.lang:
  168. values.pop('lang', None)
  169. if partner_user:
  170. # user exists, modify it according to values
  171. values.pop('login', None)
  172. values.pop('name', None)
  173. partner_user.write(values)
  174. return (cr.dbname, partner_user.login, values.get('password'))
  175. else:
  176. # user does not exist: sign up invited user
  177. values.update({
  178. 'name': partner.name,
  179. 'partner_id': partner.id,
  180. 'email': values.get('email') or values.get('login'),
  181. })
  182. if partner.company_id:
  183. values['company_id'] = partner.company_id.id
  184. values['company_ids'] = [(6, 0, [partner.company_id.id])]
  185. self._signup_create_user(cr, uid, values, context=context)
  186. else:
  187. # no token, sign up an external user
  188. values['email'] = values.get('email') or values.get('login')
  189. self._signup_create_user(cr, uid, values, context=context)
  190. return (cr.dbname, values.get('login'), values.get('password'))
  191. def _signup_create_user(self, cr, uid, values, context=None):
  192. """ create a new user from the template user """
  193. ir_config_parameter = self.pool.get('ir.config_parameter')
  194. template_user_id = literal_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.template_user_id', 'False'))
  195. assert template_user_id and self.exists(cr, uid, template_user_id, context=context), 'Signup: invalid template user'
  196. # check that uninvited users may sign up
  197. if 'partner_id' not in values:
  198. if not literal_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.allow_uninvited', 'False')):
  199. raise SignupError('Signup is not allowed for uninvited users')
  200. assert values.get('login'), "Signup: no login given for new user"
  201. assert values.get('partner_id') or values.get('name'), "Signup: no name or partner given for new user"
  202. # create a copy of the template user (attached to a specific partner_id if given)
  203. values['active'] = True
  204. context = dict(context or {}, no_reset_password=True)
  205. try:
  206. with cr.savepoint():
  207. return self.copy(cr, uid, template_user_id, values, context=context)
  208. except Exception, e:
  209. # copy may failed if asked login is not available.
  210. raise SignupError(ustr(e))
  211. def reset_password(self, cr, uid, login, context=None):
  212. """ retrieve the user corresponding to login (login or email),
  213. and reset their password
  214. """
  215. user_ids = self.search(cr, uid, [('login', '=', login)], context=context)
  216. if not user_ids:
  217. user_ids = self.search(cr, uid, [('email', '=', login)], context=context)
  218. if len(user_ids) != 1:
  219. raise Exception(_('Reset password: invalid username or email'))
  220. return self.action_reset_password(cr, uid, user_ids, context=context)
  221. def action_reset_password(self, cr, uid, ids, context=None):
  222. """ create signup token for each user, and send their signup url by email """
  223. # prepare reset password signup
  224. if not context:
  225. context = {}
  226. create_mode = bool(context.get('create_user'))
  227. res_partner = self.pool.get('res.partner')
  228. partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context)]
  229. # no time limit for initial invitation, only for reset password
  230. expiration = False if create_mode else now(days=+1)
  231. res_partner.signup_prepare(cr, uid, partner_ids, signup_type="reset", expiration=expiration, context=context)
  232. context = dict(context or {})
  233. # send email to users with their signup url
  234. template = False
  235. if create_mode:
  236. try:
  237. # get_object() raises ValueError if record does not exist
  238. template = self.pool.get('ir.model.data').get_object(cr, uid, 'auth_signup', 'set_password_email')
  239. except ValueError:
  240. pass
  241. if not bool(template):
  242. template = self.pool.get('ir.model.data').get_object(cr, uid, 'auth_signup', 'reset_password_email')
  243. assert template._name == 'mail.template'
  244. for user in self.browse(cr, uid, ids, context):
  245. if not user.email:
  246. raise UserError(_("Cannot send email: user %s has no email address.") % user.name)
  247. context['lang'] = user.lang
  248. self.pool.get('mail.template').send_mail(cr, uid, template.id, user.id, force_send=True, raise_exception=True, context=context)
  249. def create(self, cr, uid, values, context=None):
  250. if context is None:
  251. context = {}
  252. # overridden to automatically invite user to sign up
  253. user_id = super(res_users, self).create(cr, uid, values, context=context)
  254. user = self.browse(cr, uid, user_id, context=context)
  255. if user.email and not context.get('no_reset_password'):
  256. context = dict(context, create_user=True)
  257. try:
  258. self.action_reset_password(cr, uid, [user.id], context=context)
  259. except MailDeliveryException:
  260. self.pool.get('res.partner').signup_cancel(cr, uid, [user.partner_id.id], context=context)
  261. return user_id
  262. def copy(self, cr, uid, id, default=None, context=None):
  263. if not default or not default.get('email'):
  264. # avoid sending email to the user we are duplicating
  265. context = dict(context or {}, reset_password=False)
  266. return super(res_users, self).copy(cr, uid, id, default=default, context=context)