2012-09-21 10:42:37 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
|
|
#
|
|
|
|
# OpenERP, Open Source Management Solution
|
|
|
|
# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>)
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
# License, or (at your option) any later version
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Affero General Public License for more details
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
|
|
#
|
|
|
|
##############################################################################
|
2012-12-03 14:44:24 +00:00
|
|
|
from datetime import datetime, timedelta
|
2012-10-11 00:05:34 +00:00
|
|
|
import random
|
2012-12-14 10:43:29 +00:00
|
|
|
from urlparse import urljoin
|
2014-03-11 20:53:37 +00:00
|
|
|
import werkzeug
|
2012-09-21 10:42:37 +00:00
|
|
|
|
2013-05-24 11:48:57 +00:00
|
|
|
from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
|
2012-09-21 10:42:37 +00:00
|
|
|
from openerp.osv import osv, fields
|
2014-02-18 12:17:26 +00:00
|
|
|
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT, ustr
|
2013-08-09 14:55:06 +00:00
|
|
|
from ast import literal_eval
|
2012-12-03 14:44:24 +00:00
|
|
|
from openerp.tools.translate import _
|
2012-09-21 10:42:37 +00:00
|
|
|
|
2012-11-03 14:42:37 +00:00
|
|
|
class SignupError(Exception):
|
|
|
|
pass
|
2012-10-23 19:18:05 +00:00
|
|
|
|
2012-09-21 10:42:37 +00:00
|
|
|
def random_token():
|
2012-09-25 10:40:13 +00:00
|
|
|
# the token has an entropy of about 120 bits (6 bits/char * 20 chars)
|
|
|
|
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
2015-07-27 16:27:21 +00:00
|
|
|
return ''.join(random.SystemRandom().choice(chars) for i in xrange(20))
|
2012-12-03 14:44:24 +00:00
|
|
|
|
|
|
|
def now(**kwargs):
|
|
|
|
dt = datetime.now() + timedelta(**kwargs)
|
|
|
|
return dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
2012-09-21 10:42:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class res_partner(osv.Model):
|
|
|
|
_inherit = 'res.partner'
|
2012-09-25 10:40:13 +00:00
|
|
|
|
2012-09-28 08:58:43 +00:00
|
|
|
def _get_signup_valid(self, cr, uid, ids, name, arg, context=None):
|
|
|
|
dt = now()
|
|
|
|
res = {}
|
|
|
|
for partner in self.browse(cr, uid, ids, context):
|
2012-10-01 15:03:33 +00:00
|
|
|
res[partner.id] = bool(partner.signup_token) and \
|
|
|
|
(not partner.signup_expiration or dt <= partner.signup_expiration)
|
2012-09-28 08:58:43 +00:00
|
|
|
return res
|
|
|
|
|
2014-03-11 20:53:37 +00:00
|
|
|
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):
|
2012-11-16 16:15:54 +00:00
|
|
|
""" generate a signup url for the given partner ids and action, possibly overriding
|
|
|
|
the url state components (menu_id, id, view_type) """
|
2013-05-22 10:53:32 +00:00
|
|
|
if context is None:
|
|
|
|
context= {}
|
2012-09-28 08:58:43 +00:00
|
|
|
res = dict.fromkeys(ids, False)
|
2012-11-16 16:15:54 +00:00
|
|
|
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
2012-09-28 08:58:43 +00:00
|
|
|
for partner in self.browse(cr, uid, ids, context):
|
2012-11-16 16:15:54 +00:00
|
|
|
# when required, make sure the partner has a valid signup token
|
2013-05-22 10:53:32 +00:00
|
|
|
if context.get('signup_valid') and not partner.user_ids:
|
2012-11-16 16:15:54 +00:00
|
|
|
self.signup_prepare(cr, uid, [partner.id], context=context)
|
2012-12-03 14:44:24 +00:00
|
|
|
|
2014-02-05 18:32:32 +00:00
|
|
|
route = 'login'
|
2014-01-17 14:18:06 +00:00
|
|
|
# the parameters to encode for the query
|
|
|
|
query = dict(db=cr.dbname)
|
2013-05-22 10:53:32 +00:00
|
|
|
signup_type = context.get('signup_force_type_in_url', partner.signup_type or '')
|
2014-01-21 15:20:27 +00:00
|
|
|
if signup_type:
|
2014-02-05 18:32:32 +00:00
|
|
|
route = 'reset_password' if signup_type == 'reset' else signup_type
|
2012-12-14 10:43:29 +00:00
|
|
|
|
2013-05-22 10:53:32 +00:00
|
|
|
if partner.signup_token and signup_type:
|
2014-01-17 14:18:06 +00:00
|
|
|
query['token'] = partner.signup_token
|
2012-10-01 09:14:41 +00:00
|
|
|
elif partner.user_ids:
|
2014-01-17 14:18:06 +00:00
|
|
|
query['login'] = partner.user_ids[0].login
|
2012-12-14 10:43:29 +00:00
|
|
|
else:
|
|
|
|
continue # no signup token, no user, thus no signup url!
|
|
|
|
|
2014-01-17 14:18:06 +00:00
|
|
|
fragment = dict()
|
2014-03-11 20:53:37 +00:00
|
|
|
if action:
|
|
|
|
fragment['action'] = action
|
2012-12-14 10:43:29 +00:00
|
|
|
if view_type:
|
|
|
|
fragment['view_type'] = view_type
|
|
|
|
if menu_id:
|
|
|
|
fragment['menu_id'] = menu_id
|
2013-01-04 11:32:41 +00:00
|
|
|
if model:
|
|
|
|
fragment['model'] = model
|
2012-12-14 10:43:29 +00:00
|
|
|
if res_id:
|
|
|
|
fragment['id'] = res_id
|
|
|
|
|
2014-03-11 20:53:37 +00:00
|
|
|
if fragment:
|
|
|
|
query['redirect'] = '/web#' + werkzeug.url_encode(fragment)
|
|
|
|
|
2014-03-24 13:38:11 +00:00
|
|
|
res[partner.id] = urljoin(base_url, "/web/%s?%s" % (route, werkzeug.url_encode(query)))
|
2012-12-14 10:43:29 +00:00
|
|
|
|
2012-09-28 08:58:43 +00:00
|
|
|
return res
|
|
|
|
|
2012-11-16 16:15:54 +00:00
|
|
|
def _get_signup_url(self, cr, uid, ids, name, arg, context=None):
|
|
|
|
""" proxy for function field towards actual implementation """
|
|
|
|
return self._get_signup_url_for_action(cr, uid, ids, context=context)
|
|
|
|
|
2012-09-21 10:42:37 +00:00
|
|
|
_columns = {
|
2014-07-06 14:44:26 +00:00
|
|
|
'signup_token': fields.char('Signup Token', copy=False),
|
|
|
|
'signup_type': fields.char('Signup Token Type', copy=False),
|
|
|
|
'signup_expiration': fields.datetime('Signup Expiration', copy=False),
|
2012-09-28 08:58:43 +00:00
|
|
|
'signup_valid': fields.function(_get_signup_valid, type='boolean', string='Signup Token is Valid'),
|
|
|
|
'signup_url': fields.function(_get_signup_url, type='char', string='Signup URL'),
|
2012-09-21 10:42:37 +00:00
|
|
|
}
|
|
|
|
|
2012-09-28 08:58:43 +00:00
|
|
|
def action_signup_prepare(self, cr, uid, ids, context=None):
|
|
|
|
return self.signup_prepare(cr, uid, ids, context=context)
|
2012-09-27 08:27:04 +00:00
|
|
|
|
2013-05-24 11:48:57 +00:00
|
|
|
def signup_cancel(self, cr, uid, ids, context=None):
|
|
|
|
return self.write(cr, uid, ids, {'signup_token': False, 'signup_type': False, 'signup_expiration': False}, context=context)
|
|
|
|
|
2012-12-17 16:33:57 +00:00
|
|
|
def signup_prepare(self, cr, uid, ids, signup_type="signup", expiration=False, context=None):
|
2012-09-28 08:58:43 +00:00
|
|
|
""" generate a new token for the partners with the given validity, if necessary
|
2012-09-21 10:42:37 +00:00
|
|
|
:param expiration: the expiration datetime of the token (string, optional)
|
|
|
|
"""
|
2012-09-28 08:58:43 +00:00
|
|
|
for partner in self.browse(cr, uid, ids, context):
|
|
|
|
if expiration or not partner.signup_valid:
|
|
|
|
token = random_token()
|
|
|
|
while self._signup_retrieve_partner(cr, uid, token, context=context):
|
|
|
|
token = random_token()
|
2012-12-17 16:33:57 +00:00
|
|
|
partner.write({'signup_token': token, 'signup_type': signup_type, 'signup_expiration': expiration})
|
2012-09-28 08:58:43 +00:00
|
|
|
return True
|
2012-09-21 10:42:37 +00:00
|
|
|
|
2012-09-28 14:21:03 +00:00
|
|
|
def _signup_retrieve_partner(self, cr, uid, token,
|
|
|
|
check_validity=False, raise_exception=False, context=None):
|
|
|
|
""" find the partner corresponding to a token, and possibly check its validity
|
|
|
|
:param token: the token to resolve
|
|
|
|
:param check_validity: if True, also check validity
|
|
|
|
:param raise_exception: if True, raise exception instead of returning False
|
2012-09-25 10:40:13 +00:00
|
|
|
:return: partner (browse record) or False (if raise_exception is False)
|
|
|
|
"""
|
2012-09-21 10:42:37 +00:00
|
|
|
partner_ids = self.search(cr, uid, [('signup_token', '=', token)], context=context)
|
2012-09-25 10:40:13 +00:00
|
|
|
if not partner_ids:
|
|
|
|
if raise_exception:
|
2012-10-23 19:18:05 +00:00
|
|
|
raise SignupError("Signup token '%s' is not valid" % token)
|
2012-09-25 10:40:13 +00:00
|
|
|
return False
|
|
|
|
partner = self.browse(cr, uid, partner_ids[0], context)
|
2012-09-28 14:21:03 +00:00
|
|
|
if check_validity and not partner.signup_valid:
|
2012-09-25 10:40:13 +00:00
|
|
|
if raise_exception:
|
2012-10-23 19:18:05 +00:00
|
|
|
raise SignupError("Signup token '%s' is no longer valid" % token)
|
2012-09-25 10:40:13 +00:00
|
|
|
return False
|
|
|
|
return partner
|
|
|
|
|
|
|
|
def signup_retrieve_info(self, cr, uid, token, context=None):
|
|
|
|
""" retrieve the user info about the token
|
2012-09-28 14:21:03 +00:00
|
|
|
:return: a dictionary with the user information:
|
|
|
|
- 'db': the name of the database
|
|
|
|
- 'token': the token, if token is valid
|
|
|
|
- 'name': the name of the partner, if token is valid
|
|
|
|
- 'login': the user login, if the user already exists
|
|
|
|
- 'email': the partner email, if the user does not exist
|
2012-09-25 10:40:13 +00:00
|
|
|
"""
|
|
|
|
partner = self._signup_retrieve_partner(cr, uid, token, raise_exception=True, context=None)
|
2012-09-28 14:21:03 +00:00
|
|
|
res = {'db': cr.dbname}
|
|
|
|
if partner.signup_valid:
|
|
|
|
res['token'] = token
|
|
|
|
res['name'] = partner.name
|
2012-09-25 10:40:13 +00:00
|
|
|
if partner.user_ids:
|
2012-09-28 14:21:03 +00:00
|
|
|
res['login'] = partner.user_ids[0].login
|
2012-09-25 10:40:13 +00:00
|
|
|
else:
|
2012-09-28 14:21:03 +00:00
|
|
|
res['email'] = partner.email or ''
|
|
|
|
return res
|
2012-09-21 10:42:37 +00:00
|
|
|
|
2012-09-25 10:40:13 +00:00
|
|
|
class res_users(osv.Model):
|
|
|
|
_inherit = 'res.users'
|
|
|
|
|
2012-10-02 14:57:57 +00:00
|
|
|
def _get_state(self, cr, uid, ids, name, arg, context=None):
|
2012-11-28 09:03:35 +00:00
|
|
|
res = {}
|
|
|
|
for user in self.browse(cr, uid, ids, context):
|
2013-03-14 16:01:41 +00:00
|
|
|
res[user.id] = ('active' if user.login_date else 'new')
|
2012-11-28 09:03:35 +00:00
|
|
|
return res
|
2012-10-02 14:57:57 +00:00
|
|
|
|
|
|
|
_columns = {
|
2012-10-12 11:42:58 +00:00
|
|
|
'state': fields.function(_get_state, string='Status', type='selection',
|
2013-03-14 16:01:41 +00:00
|
|
|
selection=[('new', 'Never Connected'), ('active', 'Activated')]),
|
2012-10-02 14:57:57 +00:00
|
|
|
}
|
|
|
|
|
2012-09-25 10:40:13 +00:00
|
|
|
def signup(self, cr, uid, values, token=None, context=None):
|
2012-09-21 10:42:37 +00:00
|
|
|
""" signup a user, to either:
|
|
|
|
- create a new user (no token), or
|
|
|
|
- create a user for a partner (with token, but no user for partner), or
|
|
|
|
- change the password of a user (with token, and existing user).
|
2012-11-23 15:35:38 +00:00
|
|
|
:param values: a dictionary with field values that are written on user
|
2012-09-21 10:42:37 +00:00
|
|
|
:param token: signup token (optional)
|
2012-09-24 08:26:09 +00:00
|
|
|
:return: (dbname, login, password) for the signed up user
|
2012-09-21 10:42:37 +00:00
|
|
|
"""
|
|
|
|
if token:
|
|
|
|
# signup with a token: find the corresponding partner id
|
2012-09-25 10:40:13 +00:00
|
|
|
res_partner = self.pool.get('res.partner')
|
2012-11-23 15:35:38 +00:00
|
|
|
partner = res_partner._signup_retrieve_partner(
|
|
|
|
cr, uid, token, check_validity=True, raise_exception=True, context=None)
|
2012-09-26 15:04:59 +00:00
|
|
|
# invalidate signup token
|
2012-12-17 16:33:57 +00:00
|
|
|
partner.write({'signup_token': False, 'signup_type': False, 'signup_expiration': False})
|
2012-11-23 15:35:38 +00:00
|
|
|
|
|
|
|
partner_user = partner.user_ids and partner.user_ids[0] or False
|
2014-06-30 09:58:11 +00:00
|
|
|
|
|
|
|
# avoid overwriting existing (presumably correct) values with geolocation data
|
|
|
|
if partner.country_id or partner.zip or partner.city:
|
|
|
|
values.pop('city', None)
|
|
|
|
values.pop('country_id', None)
|
|
|
|
if partner.lang:
|
|
|
|
values.pop('lang', None)
|
|
|
|
|
2012-11-23 15:35:38 +00:00
|
|
|
if partner_user:
|
|
|
|
# user exists, modify it according to values
|
|
|
|
values.pop('login', None)
|
|
|
|
values.pop('name', None)
|
|
|
|
partner_user.write(values)
|
|
|
|
return (cr.dbname, partner_user.login, values.get('password'))
|
2012-09-24 08:26:09 +00:00
|
|
|
else:
|
|
|
|
# user does not exist: sign up invited user
|
2012-11-23 15:35:38 +00:00
|
|
|
values.update({
|
2012-09-28 12:09:44 +00:00
|
|
|
'name': partner.name,
|
2012-09-24 08:26:09 +00:00
|
|
|
'partner_id': partner.id,
|
2012-11-23 15:35:38 +00:00
|
|
|
'email': values.get('email') or values.get('login'),
|
|
|
|
})
|
2013-04-15 17:39:01 +00:00
|
|
|
if partner.company_id:
|
|
|
|
values['company_id'] = partner.company_id.id
|
2013-05-24 11:48:57 +00:00
|
|
|
values['company_ids'] = [(6, 0, [partner.company_id.id])]
|
2012-11-23 15:35:38 +00:00
|
|
|
self._signup_create_user(cr, uid, values, context=context)
|
|
|
|
else:
|
|
|
|
# no token, sign up an external user
|
|
|
|
values['email'] = values.get('email') or values.get('login')
|
|
|
|
self._signup_create_user(cr, uid, values, context=context)
|
|
|
|
|
|
|
|
return (cr.dbname, values.get('login'), values.get('password'))
|
2012-09-24 08:26:09 +00:00
|
|
|
|
2012-09-26 15:04:59 +00:00
|
|
|
def _signup_create_user(self, cr, uid, values, context=None):
|
2012-09-24 08:26:09 +00:00
|
|
|
""" create a new user from the template user """
|
|
|
|
ir_config_parameter = self.pool.get('ir.config_parameter')
|
2013-08-09 14:55:06 +00:00
|
|
|
template_user_id = literal_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.template_user_id', 'False'))
|
2012-09-26 15:04:59 +00:00
|
|
|
assert template_user_id and self.exists(cr, uid, template_user_id, context=context), 'Signup: invalid template user'
|
|
|
|
|
2012-09-28 12:09:44 +00:00
|
|
|
# check that uninvited users may sign up
|
|
|
|
if 'partner_id' not in values:
|
2013-08-09 14:55:06 +00:00
|
|
|
if not literal_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.allow_uninvited', 'False')):
|
2012-10-23 19:18:05 +00:00
|
|
|
raise SignupError('Signup is not allowed for uninvited users')
|
2012-09-24 08:26:09 +00:00
|
|
|
|
2012-11-23 15:35:38 +00:00
|
|
|
assert values.get('login'), "Signup: no login given for new user"
|
|
|
|
assert values.get('partner_id') or values.get('name'), "Signup: no name or partner given for new user"
|
|
|
|
|
2012-09-28 12:09:44 +00:00
|
|
|
# create a copy of the template user (attached to a specific partner_id if given)
|
|
|
|
values['active'] = True
|
2013-11-26 16:16:58 +00:00
|
|
|
context = dict(context or {}, no_reset_password=True)
|
2014-02-18 12:17:26 +00:00
|
|
|
try:
|
2014-02-18 12:41:15 +00:00
|
|
|
with cr.savepoint():
|
|
|
|
return self.copy(cr, uid, template_user_id, values, context=context)
|
2014-02-18 12:17:26 +00:00
|
|
|
except Exception, e:
|
|
|
|
# copy may failed if asked login is not available.
|
|
|
|
raise SignupError(ustr(e))
|
2012-12-03 14:44:24 +00:00
|
|
|
|
|
|
|
def reset_password(self, cr, uid, login, context=None):
|
|
|
|
""" retrieve the user corresponding to login (login or email),
|
|
|
|
and reset their password
|
|
|
|
"""
|
|
|
|
user_ids = self.search(cr, uid, [('login', '=', login)], context=context)
|
|
|
|
if not user_ids:
|
|
|
|
user_ids = self.search(cr, uid, [('email', '=', login)], context=context)
|
|
|
|
if len(user_ids) != 1:
|
2015-06-29 09:24:34 +00:00
|
|
|
raise Exception(_('Reset password: invalid username or email'))
|
2012-12-03 14:44:24 +00:00
|
|
|
return self.action_reset_password(cr, uid, user_ids, context=context)
|
|
|
|
|
|
|
|
def action_reset_password(self, cr, uid, ids, context=None):
|
|
|
|
""" create signup token for each user, and send their signup url by email """
|
|
|
|
# prepare reset password signup
|
|
|
|
res_partner = self.pool.get('res.partner')
|
|
|
|
partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context)]
|
2012-12-17 16:33:57 +00:00
|
|
|
res_partner.signup_prepare(cr, uid, partner_ids, signup_type="reset", expiration=now(days=+1), context=context)
|
2012-12-03 14:44:24 +00:00
|
|
|
|
2015-10-22 06:21:55 +00:00
|
|
|
context = dict(context or {})
|
2013-01-31 12:08:34 +00:00
|
|
|
|
2012-12-03 14:44:24 +00:00
|
|
|
# send email to users with their signup url
|
2013-01-22 14:59:25 +00:00
|
|
|
template = False
|
|
|
|
if context.get('create_user'):
|
2014-01-23 11:20:14 +00:00
|
|
|
try:
|
|
|
|
# get_object() raises ValueError if record does not exist
|
|
|
|
template = self.pool.get('ir.model.data').get_object(cr, uid, 'auth_signup', 'set_password_email')
|
|
|
|
except ValueError:
|
|
|
|
pass
|
2013-01-22 14:59:25 +00:00
|
|
|
if not bool(template):
|
|
|
|
template = self.pool.get('ir.model.data').get_object(cr, uid, 'auth_signup', 'reset_password_email')
|
2012-12-03 14:44:24 +00:00
|
|
|
assert template._name == 'email.template'
|
2013-02-14 17:31:56 +00:00
|
|
|
|
2012-12-03 14:44:24 +00:00
|
|
|
for user in self.browse(cr, uid, ids, context):
|
|
|
|
if not user.email:
|
2013-01-02 10:18:05 +00:00
|
|
|
raise osv.except_osv(_("Cannot send email: user has no email address."), user.name)
|
2015-10-22 06:21:55 +00:00
|
|
|
context['lang'] = user.lang # translate in targeted user language
|
2014-01-23 11:20:14 +00:00
|
|
|
self.pool.get('email.template').send_mail(cr, uid, template.id, user.id, force_send=True, raise_exception=True, context=context)
|
2012-12-03 15:57:57 +00:00
|
|
|
|
|
|
|
def create(self, cr, uid, values, context=None):
|
2013-05-15 08:29:39 +00:00
|
|
|
if context is None:
|
|
|
|
context = {}
|
2012-12-03 15:57:57 +00:00
|
|
|
# overridden to automatically invite user to sign up
|
|
|
|
user_id = super(res_users, self).create(cr, uid, values, context=context)
|
|
|
|
user = self.browse(cr, uid, user_id, context=context)
|
2013-05-15 08:29:39 +00:00
|
|
|
if user.email and not context.get('no_reset_password'):
|
2014-07-06 14:44:26 +00:00
|
|
|
context = dict(context, create_user=True)
|
2013-05-24 11:48:57 +00:00
|
|
|
try:
|
|
|
|
self.action_reset_password(cr, uid, [user.id], context=context)
|
|
|
|
except MailDeliveryException:
|
|
|
|
self.pool.get('res.partner').signup_cancel(cr, uid, [user.partner_id.id], context=context)
|
2012-12-31 15:38:50 +00:00
|
|
|
return user_id
|
2015-10-02 14:32:55 +00:00
|
|
|
|
|
|
|
def copy(self, cr, uid, id, default=None, context=None):
|
|
|
|
if not default or not default.get('email'):
|
|
|
|
# avoid sending email to the user we are duplicating
|
|
|
|
context = dict(context or {}, reset_password=False)
|
|
|
|
return super(res_users, self).copy(cr, uid, id, default=default, context=context)
|