reset_password cleanups

bzr revid: al@openerp.com-20120725225828-ai1uvon05e48p69t
This commit is contained in:
Antony Lesuisse
2012-07-26 00:58:28 +02:00
parent ecd5ca73c8
commit 2ceb36a09b
5 changed files with 142 additions and 181 deletions
@@ -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'}}
@@ -1,25 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<!-- Email template for reset password -->
<record id="reset_password_email" model="email.template">
<field name="name">Reset Password</field>
<field name="model_id" ref="base.model_res_users"/>
<field name="email_from"><![CDATA[${object.company_id.name} <${object.company_id.email}>]]></field>
<field name="email_to">(set by reset_password module)</field>
<field name="subject">Password reset</field>
<field name="body_text"><![CDATA[
A password reset was requested the OpenERP account linked to this email on ${object._auth_reset_password_host()}
You may change your password following this link:
${object._auth_reset_password_link()}
If you don't have asked for password reset, you can safely ignore this email.
]]></field>
</record>
<!-- TODO get own css -->
<record id="reset_password_wizard_form_view" model="ir.ui.view">
<field name="name">reset_password.wizard.form</field>
<field name="model">reset_password.wizard</field>
<field name="name">auth.reset_password.form</field>
<field name="model">auth.reset_password</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Reset Password" version="7.0">
<field name="state" invisible="1"/>
<field name="token" on_change="onchange_token(token)" invisible="1"/>
<group colspan="4" states="draft,missmatch">
<field name="pw" required='1' on_change="onchange_pw(pw,cpw)"/>
<field name="cpw" required='1' on_change="onchange_pw(pw,cpw)"/>
<group colspan="4" states="draft,missmatch">
<field name="password" required='1' on_change="onchange_pw(password,password_confirmation)"/>
<field name="password_confirmation" required='1' on_change="onchange_pw(password,password_confirmation)"/>
<group colspan="4" states="missmatch">
<div>Passwords missmatch</div>
</group>
<group colspan="2" col="1">
<button string="Change Password" name="change" icon="gtk-dialog-authentication" attrs="{'readonly': [('state', '=', 'missmatch')]}"/>
</group>
</group>
</group>
<group colspan="4" states="error" col="1">
<div>Invalid or expired token</div>
<button special="cancel" string="Close"/>
@@ -41,5 +61,6 @@
<field name="target">new</field>
</record>
</data>
</openerp>
@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record model="email.template" id="email_no_user">
<field name="name">Reset Password No User</field>
<field name="model_id" ref="base.model_res_company"/>
<field name="email_from"><![CDATA[${object.name} <${object.email}>]]></field>
<field name="email_to">(set by reset_password module)</field>
<field name="subject">Password reset attempt</field>
<field name="body_text"><![CDATA[
You (or someone else) enter this email address when asking for password reset for an OpenERP account on ${ctx['url']}.
However this email is not associated to any account.
If you have an OpenERP account at this url, please verify your email on your preferences.
If you don't have an OpenERP account, you can ignore this email.
For more information about OpenERP, visit http://www.openerp.com
Kind Regards.
]]></field>
</record>
<record model="email.template" id="email_reset_link">
<field name="name">Reset Password</field>
<field name="model_id" ref="base.model_res_users"/>
<field name="email_from"><![CDATA[${object.company_id.name} <${object.company_id.email}>]]></field>
<field name="email_to">(set by reset_password module)</field>
<field name="subject">Password reset</field>
<field name="body_text"><![CDATA[
You (or someone else) enter this email address when asking for password reset for an OpenERP account on ${ctx['url']}.
If you don't have asked for password reset, you can ignore this email.
To continue the password reset process, use the following link: ${object._rp_get_link()}
Kind Regards.
]]></field>
</record>
<record model="email.template" id="email_password_changed">
<field name="name">Password Changed</field>
<field name="model_id" ref="base.model_res_users"/>
<field name="email_from"><![CDATA[${object.company_id.name} <${object.company_id.email}>]]></field>
<field name="email_to">(set by reset_password module)</field>
<field name="subject">Password changed</field>
<field name="body_text"><![CDATA[
Your password for ${ctx['url']} has been changed.
Kind Regards.
]]></field>
</record>
</data>
</openerp>
-118
View File
@@ -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'}}
@@ -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 () {