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="#">&lt; 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

Reply via email to