Christophe (OpenERP) has proposed merging
lp:~openerp-dev/openobject-addons/trunk-reset_password-chs into
lp:openobject-addons.
Requested reviews:
OpenERP Core Team (openerp)
For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-addons/trunk-reset_password-chs/+merge/111582
--
https://code.launchpad.net/~openerp-dev/openobject-addons/trunk-reset_password-chs/+merge/111582
Your team OpenERP R&D Team is subscribed to branch
lp:~openerp-dev/openobject-addons/trunk-reset_password-chs.
=== added directory 'reset_password'
=== added file 'reset_password/__init__.py'
--- reset_password/__init__.py 1970-01-01 00:00:00 +0000
+++ reset_password/__init__.py 2012-06-22 11:46:12 +0000
@@ -0,0 +1,2 @@
+import res_users
+import controllers
=== added file 'reset_password/__openerp__.py'
--- reset_password/__openerp__.py 1970-01-01 00:00:00 +0000
+++ reset_password/__openerp__.py 2012-06-22 11:46:12 +0000
@@ -0,0 +1,23 @@
+{
+ 'name': 'Reset Password',
+ 'description': 'Allow users to reset their password from the login page',
+ 'author': 'OpenERP SA',
+ 'version': '1.0',
+ 'category': 'Tools',
+ 'website': 'http://www.openerp.com',
+ 'installable': True,
+ 'depends': ['anonymous', 'email_template'],
+ 'data': [
+ 'email_templates.xml',
+ 'res_users.xml',
+ ],
+ 'js': [
+ 'static/src/js/reset_password.js',
+ ],
+ 'css': [
+ 'static/src/css/reset_password.css',
+ ],
+ 'qweb': [
+ 'static/src/xml/reset_password.xml',
+ ],
+}
=== added file 'reset_password/controllers.py'
--- reset_password/controllers.py 1970-01-01 00:00:00 +0000
+++ reset_password/controllers.py 2012-06-22 11:46:12 +0000
@@ -0,0 +1,17 @@
+import simplejson
+import urllib2
+import werkzeug
+
+from openerp.addons.web.common import http as oeweb
+
+class ResetPassword(oeweb.Controller):
+ _cp_path = '/reset_password'
+
+ @oeweb.httprequest
+ def index(self, req, db, token):
+ req.session.authenticate(db, 'anonymous', 'anonymous', {})
+ url = '/web/webclient/home#client_action=reset_password&token=%s' % (token,)
+ redirect = werkzeug.utils.redirect(url)
+ cookie_val = urllib2.quote(simplejson.dumps(req.session_id))
+ redirect.set_cookie('instance0|session_id', cookie_val)
+ return redirect
=== added file 'reset_password/email_templates.xml'
--- reset_password/email_templates.xml 1970-01-01 00:00:00 +0000
+++ reset_password/email_templates.xml 2012-06-22 11:46:12 +0000
@@ -0,0 +1,55 @@
+<?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>
=== added file 'reset_password/res_users.py'
--- reset_password/res_users.py 1970-01-01 00:00:00 +0000
+++ reset_password/res_users.py 2012-06-22 11:46:12 +0000
@@ -0,0 +1,118 @@
+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'}}
=== added file 'reset_password/res_users.xml'
--- reset_password/res_users.xml 1970-01-01 00:00:00 +0000
+++ reset_password/res_users.xml 2012-06-22 11:46:12 +0000
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+ <data>
+ <!-- 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="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="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 colspan="4" states="error" col="1">
+ <div>Invalid or expired token</div>
+ <button special="cancel" string="Close"/>
+ </group>
+ <group colspan="4" states="done" col="1">
+ <div>Password changed. We sent you an email confirming the password change.</div>
+ <button special="cancel" string="Close"/>
+ </group>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_reset" model="ir.actions.act_window">
+ <field name="name">Reset Password</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">reset_password.wizard</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+ </record>
+
+ </data>
+</openerp>
=== added directory 'reset_password/static'
=== added directory 'reset_password/static/src'
=== added directory 'reset_password/static/src/css'
=== added file 'reset_password/static/src/css/reset_password.css'
--- reset_password/static/src/css/reset_password.css 1970-01-01 00:00:00 +0000
+++ reset_password/static/src/css/reset_password.css 2012-06-22 11:46:12 +0000
@@ -0,0 +1,12 @@
+.openerp .oe_login .oe_login_pane {
+ height: 152px;
+}
+.openerp .oe_login .oe_login_pane ul.oe_login_switch a {
+ color: #eeeeee;
+ margin: 0 8px;
+}
+.openerp .oe_login .oe_login_pane ul.oe_login_switch a:hover {
+ text-decoration: underline;
+}
+
+
=== added directory 'reset_password/static/src/js'
=== added file 'reset_password/static/src/js/reset_password.js'
--- reset_password/static/src/js/reset_password.js 1970-01-01 00:00:00 +0000
+++ reset_password/static/src/js/reset_password.js 2012-06-22 11:46:12 +0000
@@ -0,0 +1,75 @@
+openerp.reset_password = function(instance) {
+ var _t = instance.web._t;
+ instance.web.Login.include({
+ start: function() {
+ var $e = this.$element;
+ $e.find('.oe_login_switch a').click(function() {
+ $e.find('.oe_login_switch').toggle();
+ var $m = $e.find('form input[name=is_reset_pw]');
+ $m.attr('checked', !$m.is(':checked'));
+ });
+ return this._super();
+ },
+ on_submit: function(ev) {
+ if(ev) {
+ ev.preventDefault();
+ }
+
+ var $e = this.$element;
+ var db = $e.find("form [name=db]").val();
+ if (!db) {
+ 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()
+ return this.do_reset_password(db, email);
+ } else {
+ return this._super(ev);
+ }
+ },
+
+ do_reset_password: function(db, email) {
+ var self = this;
+ instance.connection.session_authenticate(db, 'anonymous', 'anonymous', true).pipe(function () {
+ var func = new instance.web.Model("res.users").get_func("send_reset_password_request");
+ return func(email).then(function(res) {
+ // show message
+ self.do_notify(_t('Reset Password'), _.str.sprintf(_t('We have sent an email to %s with further instructions'), email), true);
+ }, function(error, event) {
+ // no traceback please
+ event.preventDefault();
+ });
+ }).fail(function(error, event) {
+ // cannot log as anonymous or reset_password not installed
+ self.do_warn(_t('Reset Password'), _.str.sprintf(_t('Reset Password functionnality is not available for database %s'), db), true);
+ });
+ },
+ });
+
+ instance.reset_password = {};
+ instance.reset_password.ResetPassword = instance.web.Widget.extend({
+ init: function(parent, params) {
+ this._super(parent);
+ this.token = (params && params.token) || false;
+ },
+ start: function() {
+ this.do_action({
+ name: 'Reset Password',
+ type: 'ir.actions.act_window',
+ context: {default_token: this.token},
+ res_model: 'reset_password.wizard',
+ target: 'new',
+ views: [[false, 'form']],
+ });
+
+ }
+ });
+
+
+ instance.web.client_actions.add("reset_password", "instance.reset_password.ResetPassword");
+
+
+};
=== added directory 'reset_password/static/src/xml'
=== added file 'reset_password/static/src/xml/reset_password.xml'
--- reset_password/static/src/xml/reset_password.xml 1970-01-01 00:00:00 +0000
+++ reset_password/static/src/xml/reset_password.xml 2012-06-22 11:46:12 +0000
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vim:fdl=1:
+-->
+<templates id="template" xml:space="preserve">
+
+<t t-extend="Login">
+ <t t-jquery="form ul:first">
+ // addClass does not work :(
+ this.attr('class', 'oe_login_switch');
+ </t>
+ <t t-jquery="form ul:first li:last" t-operation="after">
+ <li>
+ <a href="#">Forgot your password?</a>
+ </li>
+ </t>
+ <t t-jquery="form ul:first" t-operation="after">
+ <ul class="oe_login_switch" style="display:none;">
+ <li style="display:none;"><input type="checkbox" name="is_reset_pw"/></li>
+ <li>Email</li>
+ <li><input type="email" name="email"/></li>
+ <li><button name="submit">Reset Password</button></li>
+ <li><a href="#">< Back</a></li>
+ </ul>
+ </t>
+</t>
+</templates>
_______________________________________________
Mailing list: https://launchpad.net/~openerp-dev-gtk
Post to : [email protected]
Unsubscribe : https://launchpad.net/~openerp-dev-gtk
More help : https://help.launchpad.net/ListHelp