diff --git a/addons/auth_reset_password/auth_reset_password.py b/addons/auth_reset_password/auth_reset_password.py
new file mode 100644
index 00000000000..6145c9cba4d
--- /dev/null
+++ b/addons/auth_reset_password/auth_reset_password.py
@@ -0,0 +1,115 @@
+import base64
+import hashlib
+import simplejson
+import time
+import urlparse
+
+from openerp.tools import config
+from openerp.osv import osv, fields
+
+TWENTY_FOUR_HOURS = 24 * 60 * 60
+
+def message_sign(data, secret):
+ src = simplejson.dumps([data,secret], indent=None, separators=(',', ':'), sort_keys=True)
+ sign = hashlib.sha1(src).hexdigest()
+ msg = simplejson.dumps([data,sign], indent=None, separators=(',', ':'), sort_keys=True)
+ # pad message to avoid '='
+ pad = (3-len(msg)%3)%3
+ msg = msg + " " * pad
+ msg = base64.urlsafe_b64encode(msg)
+ return msg, sign
+
+def message_check(msg, secret):
+ msg = base64.urlsafe_b64decode(msg)
+ l = simplejson.loads(msg)
+ msg_data = l[0]
+ msg_sign = l[1]
+ tmp, sign = message_sign(msg_data, secret)
+ if msg_sign == sign:
+ return msg_data
+
+class res_users(osv.osv):
+ _inherit = 'res.users'
+
+ _sql_constraints = [
+ ('email_uniq', 'UNIQUE (user_email)', 'You can not have two users with the same email!')
+ ]
+
+ def _auth_reset_password_secret(self, cr, uid, ids, context=None)
+ uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid')
+ res = {
+ 'dbname': cr.dbname,
+ 'uuid': uuid,
+ 'admin_passwd': config['admin_passwd']
+ }
+ return res
+
+ def _auth_reset_password_host(self, cr, uid, ids, context=None):
+ return self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', '')
+
+ def _auth_reset_password_link(self, cr, uid, ids, context=None):
+ assert len(ids) == 1
+ user = self.browse(cr, uid, ids[0], context=context)
+ host = self._auth_reset_password_host(cr, uid, ids, context)
+ secret = self._auth_reset_password_secret(cr, uid, ids, context)
+ msg_src = {
+ 'time' : time.time(),
+ 'uid' : ids[0],
+ }
+ msg, sign = message_sign(msg_src, secret)
+ link = urlparse.urljoin(host, '/web/webclient/home#action_id=reset_password&token=%s' % msg)
+ return link
+
+ def _auth_reset_password_check_token(self, cr, uid, token, context=None):
+ secret = self._auth_reset_password_secret(cr, uid, ids, context)
+ data = message_check(token, secret)
+ if data and (time.time() - data['time'] < TWENTY_FOUR_HOURS):
+ return data
+
+ def send_reset_password_request(self, cr, uid, email, context=None):
+ ids = self.pool.get('res.users').search(cr, 1, [('user_email', '=', email)], context=context)
+ if ids:
+ model, template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'auth_reset_password', 'reset_password_email')
+ msg_id = self.pool.get('email.template').send_mail(cr, uid, template_id, ids[0], context=ctx)
+ return True
+
+class auth_reset_password(osv.TransientModel):
+ _name = 'auth.reset_password'
+ _rec_name = 'password'
+ _columns = {
+ 'password': fields.char('Password', size=64),
+ 'password_confirmation': fields.char('Confirm Password', size=64),
+ 'token': fields.char('Token', size=128),
+ 'state': fields.selection([(x, x) for x in 'draft done missmatch error'.split()], required=True),
+ }
+ _defaults = {
+ 'state': 'draft',
+ }
+
+
+ def create(self, cr, uid, values, context=None):
+ # NOTE here, invalid values raises exceptions to avoid storing
+ # sensitive data into the database (which then are available to anyone)
+
+ if values['password'] != values['password_confirmation']:
+ raise osv.except_osv('Error', 'Passwords missmatch')
+
+ Users = self.pool.get('res.users')
+ data = Users._auth_reset_password_check_token(self, cr, uid, values['token']):
+ if data:
+ Users.write(cr, 1, data['uid'], {'password': pw}, context=context)
+ else:
+ raise osv.except_osv('Error', 'Invalid token')
+
+ # Dont store password
+ values = {'state': 'done'}
+ return super(auth_reset_password, self).create(cr, uid, values, context)
+
+ def change(self, cr, uid, ids, context=None):
+ return True
+
+ def onchange_pw(self, cr, uid, ids, password, password_confirmation, context=None):
+ if password != password_confirmation:
+ return {'value': {'state': 'missmatch'}}
+ return {'value': {'state': 'draft'}}
+
diff --git a/addons/auth_reset_password/res_users.xml b/addons/auth_reset_password/auth_reset_password.xml
similarity index 58%
rename from addons/auth_reset_password/res_users.xml
rename to addons/auth_reset_password/auth_reset_password.xml
index a60198750ce..55e3f4609ca 100644
--- a/addons/auth_reset_password/res_users.xml
+++ b/addons/auth_reset_password/auth_reset_password.xml
@@ -1,25 +1,45 @@
+
+
+
+ Reset Password
+
+ ]]>
+ (set by reset_password module)
+ Password reset
+
+
+
- reset_password.wizard.form
- reset_password.wizard
+ auth.reset_password.form
+ auth.reset_password
form
+
diff --git a/addons/auth_reset_password/email_templates.xml b/addons/auth_reset_password/email_templates.xml
deleted file mode 100644
index 37cd529deab..00000000000
--- a/addons/auth_reset_password/email_templates.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
- Reset Password No User
-
- ]]>
- (set by reset_password module)
- Password reset attempt
-
-
-
-
- Reset Password
-
- ]]>
- (set by reset_password module)
- Password reset
-
-
-
- Password Changed
-
- ]]>
- (set by reset_password module)
- Password changed
-
-
-
-
-
diff --git a/addons/auth_reset_password/res_users.py b/addons/auth_reset_password/res_users.py
deleted file mode 100644
index 0b93a9a37ea..00000000000
--- a/addons/auth_reset_password/res_users.py
+++ /dev/null
@@ -1,118 +0,0 @@
-import urlparse
-import itsdangerous
-from openerp.tools import config
-from openerp.osv import osv, fields
-
-TWENTY_FOUR_HOURS = 24 * 60 * 60
-
-def serializer(dbname):
- key = '%s.%s' % (dbname, config['admin_passwd'])
- return itsdangerous.URLSafeTimedSerializer(key)
-
-def generate_token(dbname, user):
- s = serializer(dbname)
- return s.dumps((user.id, user.user_email))
-
-def valid_token(dbname, token, max_age=TWENTY_FOUR_HOURS):
- try:
- unsign_token(dbname, token, max_age)
- return True
- except itsdangerous.BadSignature:
- return False
-
-def unsign_token(dbname, token, max_age=TWENTY_FOUR_HOURS):
- # TODO avoid replay by comparing timestamp with last connection date of user ? (need a query)
- s = serializer(dbname)
- return s.loads(token, max_age)
-
-class res_users(osv.osv):
- _inherit = 'res.users'
-
- _sql_constraints = [
- ('email_uniq', 'UNIQUE (user_email)', 'You can not have two users with the same email!')
- ]
-
- def _rp_send_email(self, cr, uid, email, tpl_name, res_id, context=None):
- model, tpl_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reset_password', tpl_name)
- assert model == 'email.template'
-
- host = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', '')
- ctx = dict(context or {}, url=host)
-
- msg_id = self.pool.get(model).send_mail(cr, uid, tpl_id, res_id, force_send=False, context=ctx)
- MailMessage = self.pool.get('mail.message')
- MailMessage.write(cr, uid, [msg_id], {'email_to': email}, context=context)
- MailMessage.send(cr, uid, [msg_id], context=context)
-
- def _rp_get_link(self, cr, uid, ids, context=None):
- assert len(ids) == 1
- user = self.browse(cr, uid, ids[0], context=context)
- host = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', '')
- token = generate_token(cr.dbname, user)
- link = urlparse.urljoin(host, '/reset_password?db=%s&token=%s' % (cr.dbname, token))
- return link
-
- def send_reset_password_request(self, cr, uid, email, context=None):
- uid = 1
- ids = self.search(cr, uid, [('user_email', '=', email)], context=context)
- assert len(ids) <= 1
- if not ids:
- _m, company_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'main_company')
- self._rp_send_email(cr, uid, email, 'email_no_user', company_id, context=context)
- else:
- self._rp_send_email(cr, uid, email, 'email_reset_link', ids[0], context=context)
- return True
-
-res_users()
-
-
-class reset_pw_wizard(osv.TransientModel):
- _name = 'reset_password.wizard'
- _rec_name = 'pw'
- _columns = {
- 'pw': fields.char('Password', size=64),
- 'cpw': fields.char('Confirm Password', size=64),
- 'token': fields.char('Token', size=128),
- 'state': fields.selection([(x, x) for x in 'draft done missmatch error'.split()], required=True),
- }
- _defaults = {
- 'state': 'draft',
- }
-
- def create(self, cr, uid, values, context=None):
- # NOTE here, invalid values raises exceptions to avoid storing
- # sensitive data into the database (which then are available to anyone)
-
- token = values.get('token')
- pw = values.get('pw')
- cpw = values.get('cpw')
-
- if pw != cpw:
- raise osv.except_osv('Error', 'Passwords missmatch')
-
- Users = self.pool.get('res.users')
-
- try:
- user_id, user_email = unsign_token(cr.dbname, token)
- except Exception:
- raise osv.except_osv('Error', 'Invalid token')
-
- Users.write(cr, 1, user_id, {'password': pw}, context=context)
- Users._rp_send_email(cr, 1, user_email, 'email_password_changed', user_id, context=context)
-
- values = {'state': 'done'}
-
- return super(reset_pw_wizard, self).create(cr, uid, values, context)
-
- def change(self, cr, uid, ids, context=None):
- return True
-
- def onchange_token(self, cr, uid, ids, token, context=None):
- if not valid_token(cr.dbname, token):
- return {'value': {'state': 'error'}}
- return {}
-
- def onchange_pw(self, cr, uid, ids, pw, cpw, context=None):
- if pw != cpw:
- return {'value': {'state': 'missmatch'}}
- return {'value': {'state': 'draft'}}
diff --git a/addons/auth_reset_password/static/src/js/reset_password.js b/addons/auth_reset_password/static/src/js/reset_password.js
index 9151764947b..a4d023e9869 100644
--- a/addons/auth_reset_password/static/src/js/reset_password.js
+++ b/addons/auth_reset_password/static/src/js/reset_password.js
@@ -21,7 +21,6 @@ openerp.reset_password = function(instance) {
this.do_warn(_t("Login"), _t("No database selected !"));
return false;
}
-
var $m = $e.find('form input[name=is_reset_pw]');
if ($m.is(':checked')) {
var email = $e.find('form input[name=email]').val()
@@ -30,7 +29,6 @@ openerp.reset_password = function(instance) {
return this._super(ev);
}
},
-
do_reset_password: function(db, email) {
var self = this;
instance.connection.session_authenticate(db, 'anonymous', 'anonymous', true).pipe(function () {