On 11.6.2014 15:19, Petr Vobornik wrote:
Patch set contains both API/server and Web UI parts.

[PATCH] 659 ldap2: add otp support to modify_password
[PATCH] 660 rpcserver: add otp support to change_password handler
[PATCH] 661 ipa-passwd: add OTP support
[PATCH] 662 webui: support password change with OTP in login screen
[PATCH] 663 webui: placeholder attribute support in textbox and textarea
[PATCH] 664 webui: add placeholders to login screen
[PATCH] 665 webui: rebase user password dialog on password dialog and
add otp support
[PATCH] 666 webui: support otp in reset_password.html

https://fedorahosted.org/freeipa/ticket/4262

attaching rebased patches (mainly because of VERSION conflict)


--
Petr Vobornik
From 7722d49fdc782d14e5bcc3fbefaee70e5fff62a4 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Thu, 22 May 2014 14:47:44 +0200
Subject: [PATCH] webui: placeholder attribute support in textbox and textarea

---
 install/ui/src/freeipa/widget.js | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/install/ui/src/freeipa/widget.js b/install/ui/src/freeipa/widget.js
index cfa9417c5ddbaf94b45aa35498d6076a55fac493..9677a168eb2a137ed1c42b9b2b5d62f43c3a735d 100644
--- a/install/ui/src/freeipa/widget.js
+++ b/install/ui/src/freeipa/widget.js
@@ -269,6 +269,12 @@ IPA.input_widget = function(spec) {
     var that = IPA.widget(spec);
 
     /**
+     * Placeholder
+     * @property {string}
+     */
+    that.placeholder = text.get(spec.placeholder);
+
+    /**
      * Widget's width.
      * @deprecated
      * @property {number}
@@ -709,6 +715,7 @@ IPA.text_widget = function(spec) {
             'class': 'form-control',
             size: that.size,
             title: that.tooltip,
+            placeholder: that.placeholder,
             keyup: function() {
                 that.on_value_changed();
             }
@@ -1975,6 +1982,7 @@ IPA.textarea_widget = function (spec) {
             'class': 'form-control',
             readOnly: !!that.read_only,
             title: that.tooltip,
+            placeholder: that.placeholder,
             keyup: function() {
                 that.on_value_changed();
             }
-- 
1.9.0

From 6b680e0092ca8518b6aebb974ee982139e85d266 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Thu, 22 May 2014 14:48:22 +0200
Subject: [PATCH] webui: add placeholders to login screen

---
 install/ui/src/freeipa/widgets/LoginScreen.js | 8 +++++++-
 install/ui/test/data/ipa_init.json            | 3 +++
 ipalib/plugins/internal.py                    | 3 +++
 3 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/install/ui/src/freeipa/widgets/LoginScreen.js b/install/ui/src/freeipa/widgets/LoginScreen.js
index 701c88cf12f07e7d7e4d4efc44d980f332656042..78991d0a93d6b9d3507d3afe31587ed1f3b55ed9 100644
--- a/install/ui/src/freeipa/widgets/LoginScreen.js
+++ b/install/ui/src/freeipa/widgets/LoginScreen.js
@@ -568,6 +568,7 @@ define(['dojo/_base/declare',
             $type: 'text',
             name: 'username',
             label: text.get('@i18n:login.username', "Username"),
+            placeholder: text.get('@i18n:login.username', "Username"),
             show_errors: false,
             undo: false
         },
@@ -575,6 +576,7 @@ define(['dojo/_base/declare',
             $type: 'password',
             name: 'password',
             label: text.get('@i18n:login.password', "Password"),
+            placeholder: text.get('@i18n:login.password_and_otp', 'Password or Password+One-Time-Password'),
             show_errors: false,
             undo: false
         },
@@ -589,13 +591,15 @@ define(['dojo/_base/declare',
             name: 'current_password',
             $type: 'password',
             label: text.get('@i18n:login.current_password', "Current Password"),
+            placeholder: text.get('@i18n:login.current_password', "Current Password"),
             show_errors: false,
             undo: false
         },
         {
             name: 'otp',
             $type: 'password',
-            label: text.get('@i18n:login.current_password', "OTP"),
+            label: text.get('@i18n:password.otp', "OTP"),
+            placeholder: text.get('@i18n:password.otp_long', 'One-Time-Password'),
             show_errors: false,
             undo: false
         },
@@ -604,6 +608,7 @@ define(['dojo/_base/declare',
             $type: 'password',
             required: true,
             label: text.get('@i18n:password.new_password)', "New Password"),
+            placeholder: text.get('@i18n:password.new_password)', "New Password"),
             show_errors: false,
             undo: false
         },
@@ -612,6 +617,7 @@ define(['dojo/_base/declare',
             $type: 'password',
             required: true,
             label: text.get('@i18n:password.verify_password', "Verify Password"),
+            placeholder: text.get('@i18n:password.new_password)', "New Password"),
             validators: [{
                 $type: 'same_password',
                 other_field: 'new_password'
diff --git a/install/ui/test/data/ipa_init.json b/install/ui/test/data/ipa_init.json
index 8a96e23307de76624f94f2046d288181841b25ed..2ce5120e9042a3b4e4f20f3ae24c60880da813da 100644
--- a/install/ui/test/data/ipa_init.json
+++ b/install/ui/test/data/ipa_init.json
@@ -512,7 +512,10 @@
                         "invalid_password": "The password or username you entered is incorrect.",
                         "new_password": "New Password",
                         "new_password_required": "New password is required",
+                        "otp": "OTP",
+                        "otp_long": "One-Time-Password",
                         "password": "Password",
+                        "password_and_otp": "Password or Password+One-Time-Password",
                         "password_change_complete": "Password change complete",
                         "password_must_match": "Passwords must match",
                         "reset_failure": "Password reset was not successful.",
diff --git a/ipalib/plugins/internal.py b/ipalib/plugins/internal.py
index e3c0faff13c86bec76c5f309a7bb43cdb59b4d97..4a3745ae7a28910e2089d7775ef4b0f40bb571da 100644
--- a/ipalib/plugins/internal.py
+++ b/ipalib/plugins/internal.py
@@ -652,7 +652,10 @@ class i18n_messages(Command):
             "invalid_password": _("The password or username you entered is incorrect."),
             "new_password": _("New Password"),
             "new_password_required": _("New password is required"),
+            "otp": _("OTP"),
+            "otp_long": _("One-Time-Password"),
             "password": _("Password"),
+            "password_and_otp": _("Password or Password+One-Time-Password"),
             "password_change_complete": _("Password change complete"),
             "password_must_match": _("Passwords must match"),
             "reset_failure": _("Password reset was not successful."),
-- 
1.9.0

From cd04b0423723882ae4e0ccf121dbafac58b39516 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Thu, 22 May 2014 16:20:02 +0200
Subject: [PATCH] webui: rebase user password dialog on password dialog and add
 otp support

https://fedorahosted.org/freeipa/ticket/4262
---
 install/ui/src/freeipa/dialogs/password.js |  11 +-
 install/ui/src/freeipa/ipa.js              |  26 ++--
 install/ui/src/freeipa/user.js             | 193 ++++++++---------------------
 ipatests/test_webui/test_user.py           |   4 +-
 4 files changed, 77 insertions(+), 157 deletions(-)

diff --git a/install/ui/src/freeipa/dialogs/password.js b/install/ui/src/freeipa/dialogs/password.js
index 35f96e4b79f32c56082c0f2874afd7869cd0848a..fa672901d589aaf7308ff7e2b6af545be544df4b 100644
--- a/install/ui/src/freeipa/dialogs/password.js
+++ b/install/ui/src/freeipa/dialogs/password.js
@@ -51,6 +51,7 @@ dialogs.password.default_fields_pre_op =  function(spec) {
     spec.width = spec.width || 400;
     spec.sections = spec.sections || [
         {
+            name: 'general',
             fields: [
                 {
                     name: name,
@@ -193,7 +194,7 @@ dialogs.password.dialog = function(spec) {
         for (var j=0; j<fields.length; j++) {
             var field = fields[j];
             var values = field.save();
-            if (!values || values.length === 0) continue;
+            if (!values || values.length === 0 || !field.enabled) continue;
             if (field.flags.indexOf('no_command') > -1) continue;
 
             if (values.length === 1) {
@@ -212,10 +213,12 @@ dialogs.password.dialog = function(spec) {
     that.create_command = function() {
 
         var options = that.make_otions();
+        var entity = null;
+        if (that.entity) entity = that.entity.name;
         var command = rpc.command({
-            entity: that.entity.name,
+            entity: entity,
             method: that.method,
-            args: that.pkeys,
+            args: that.args,
             options: options,
             on_success: function(data) {
                 that.on_success();
@@ -301,7 +304,7 @@ dialogs.password.action = function(spec) {
             ds.$type = 'password';
         }
         var dialog = builder.build('dialog', ds);
-        dialog.pkeys = facet.get_pkeys();
+        dialog.args = facet.get_pkeys();
         dialog.succeeded.attach(function() {
             if (that.refresh) facet.refresh();
         });
diff --git a/install/ui/src/freeipa/ipa.js b/install/ui/src/freeipa/ipa.js
index 66d92b6e0726211f2a87bc25f84b26319756a9ee..be671d8f4246e769c019a544783314504f6977e2 100644
--- a/install/ui/src/freeipa/ipa.js
+++ b/install/ui/src/freeipa/ipa.js
@@ -619,20 +619,20 @@ IPA.update_password_expiration = function() {
  * @member IPA
  */
 IPA.password_selfservice = function() {
-    var reset_dialog = IPA.user_password_dialog({
-        pkey: IPA.whoami.uid[0],
-        on_success: function() {
-            var command = IPA.get_whoami_command();
-            var orig_on_success = command.on_success;
-            command.on_success = function(data, text_status, xhr) {
-                orig_on_success.call(this, data, text_status, xhr);
-                IPA.update_password_expiration();
-            };
-            command.execute();
+    var reset_dialog = builder.build('dialog', {
+        $type: 'user_password',
+        args: [IPA.whoami.uid[0]]
+    });
+    reset_dialog.succeeded.attach(function() {
+        var command = IPA.get_whoami_command();
+        var orig_on_success = command.on_success;
+        command.on_success = function(data, text_status, xhr) {
+            orig_on_success.call(this, data, text_status, xhr);
+            IPA.update_password_expiration();
+        };
+        command.execute();
 
-            IPA.notify_success(text.get('@i18n:password.password_change_complete'));
-            reset_dialog.close();
-        }
+        IPA.notify_success(text.get('@i18n:password.password_change_complete'));
     });
     reset_dialog.open();
 };
diff --git a/install/ui/src/freeipa/user.js b/install/ui/src/freeipa/user.js
index 3685b116aad6708f88a437a4abebc68ff9df4474..73298093bd7a606cd9ee631056b7135880827d77 100644
--- a/install/ui/src/freeipa/user.js
+++ b/install/ui/src/freeipa/user.js
@@ -22,18 +22,20 @@
  */
 
 define([
+        './builder',
         './ipa',
         './jquery',
         './phases',
         './reg',
         './rpc',
         './text',
+        './dialogs/password',
         './details',
         './search',
         './association',
         './entity',
         './certificate'],
-    function(IPA, $, phases, reg, rpc, text) {
+    function(builder, IPA, $, phases, reg, rpc, text, password_dialog) {
 
 /**
  * User module
@@ -509,155 +511,57 @@ IPA.user_password_widget = function(spec) {
     return that;
 };
 
-IPA.user_password_dialog = function(spec) {
+IPA.user.password_dialog_pre_op0 = function(spec) {
 
-    spec = spec || {};
+    spec.password_name = spec.password_name || 'password';
+    return spec;
+};
 
-    spec.width = spec.width || 400;
-    spec.title = spec.title || '@i18n:password.reset_password';
-    spec.sections = spec.sections || [];
+IPA.user.password_dialog_pre_op = function(spec) {
 
-    spec.sections.push(
-        {
-            name: 'input',
-            fields: [
-                {
-                    name: 'current_password',
-                    label: '@i18n:password.current_password',
-                    $type: 'password',
-                    required: true
-                },
-                {
-                    name: 'password1',
-                    label: '@i18n:password.new_password',
-                    $type: 'password',
-                    required: true
-                },
-                {
-                    name: 'password2',
-                    label: '@i18n:password.verify_password',
-                    $type: 'password',
-                    validators: [{
-                        $type: 'same_password',
-                        other_field: 'password1'
-                    }],
-                    required: true
-                }
-            ]
-        });
+    spec.sections[0].fields.splice(0, 0, {
+        name: 'current_password',
+        label: '@i18n:password.current_password',
+        $type: 'password',
+        required: true
+    }, {
+         name: 'otp',
+        label: '@i18n:password.otp',
+        $type: 'password'
+    });
 
-    var that = IPA.dialog(spec);
+    spec.method = spec.method || 'passwd';
 
-    IPA.confirm_mixin().apply(that);
+    return spec;
+};
 
-    that.success_handler = spec.on_success;
-    that.error_handler = spec.on_error;
-    that.pkey = spec.pkey;
+IPA.user.password_dialog = function(spec) {
+
+    var that = password_dialog.dialog(spec);
 
     that.is_self_service = function() {
-        var self_service = that.pkey === IPA.whoami.uid[0];
+        var self_service = that.args[0] === IPA.whoami.uid[0];
         return self_service;
     };
 
     that.open = function() {
-
-        var self_service = that.is_self_service();
-        var section = that.widgets.get_widget('input');
-        var current_password_f = that.fields.get_field('current_password');
-
         that.dialog_open();
-        section.set_row_visible('current_password', self_service);
-        current_password_f.set_required(self_service);
+
+        var self_service = that.is_self_service();
+        var current_pw_f = that.fields.get_field('current_password');
+        var current_pw_w = that.widgets.get_widget('general.current_password');
+        var otp_f = that.fields.get_field('otp');
+        var otp_w = that.widgets.get_widget('general.otp');
+
+        current_pw_f.set_required(self_service);
+        current_pw_f.set_enabled(self_service);
+        current_pw_w.set_visible(self_service);
+        otp_f.set_enabled(self_service);
+        otp_w.set_visible(self_service);
+
         that.focus_first_element();
     };
 
-    that.create_buttons = function() {
-
-        that.create_button({
-            name: 'reset_password',
-            label: '@i18n:password.reset_password',
-            click: that.on_reset_click
-        });
-
-        that.create_button({
-            name: 'cancel',
-            label: '@i18n:buttons.cancel',
-            click: function() {
-                that.close();
-            }
-        });
-    };
-
-    that.on_confirm = function() {
-        that.on_reset_click();
-    };
-
-    that.on_reset_click = function() {
-
-        if (!that.validate()) return;
-
-        var self_service = that.is_self_service();
-
-        var record = {};
-        that.save(record);
-
-        var current_password = self_service ? record.current_password[0] : undefined;
-        var new_password = record.password1[0];
-        var repeat_password = record.password2[0];
-
-        that.set_password(
-            that.pkey,
-            current_password,
-            new_password,
-            that.on_reset_success,
-            that.on_reset_error);
-    };
-
-    that.set_password = function(pkey, current_password, password, on_success, on_error) {
-
-        var command = rpc.command({
-            method: 'passwd',
-            args: [ pkey ],
-            options: {
-                current_password: current_password,
-                password: password
-            },
-            on_success: on_success,
-            on_error: on_error
-        });
-
-        command.execute();
-    };
-
-    that.on_reset_success = function(data, text_status, xhr) {
-
-        if (that.success_handler) {
-            that.success_handler.call(this, data, text_status, xhr);
-        } else {
-            IPA.notify_success('@i18n:password.password_change_complete');
-            that.close();
-
-            // refresh password expiration field
-            that.facet.refresh();
-
-            if (that.is_self_service()) {
-                var command = IPA.get_whoami_command();
-                command.execute();
-            }
-        }
-    };
-
-    that.on_reset_error = function(xhr, text_status, error_thrown) {
-
-        if (that.error_handler) {
-            that.error_handler.call(this, xhr, text_status, error_thrown);
-        } else {
-            that.close();
-        }
-    };
-
-    that.create_buttons();
-
     return that;
 };
 
@@ -672,10 +576,17 @@ IPA.user.reset_password_action = function(spec) {
 
     that.execute_action = function(facet) {
 
-        var dialog = IPA.user_password_dialog({
-            entity: facet.entity,
-            facet: facet,
-            pkey: facet.get_pkey()
+        var dialog = builder.build('dialog', {
+            $type: 'user_password',
+            args: [facet.get_pkey()]
+        });
+
+        dialog.succeeded.attach(function() {
+            facet.refresh();
+            if (dialog.is_self_service()) {
+                var command = IPA.get_whoami_command();
+                command.execute();
+            }
         });
 
         dialog.open();
@@ -688,8 +599,14 @@ exp.entity_spec = make_spec();
 exp.register = function() {
     var e = reg.entity;
     var a = reg.action;
+    var d = reg.dialog;
     e.register({type: 'user', spec: exp.entity_spec});
     a.register('reset_password', IPA.user.reset_password_action);
+    d.copy('password', 'user_password', {
+        factory: IPA.user.password_dialog,
+        pre_ops: [IPA.user.password_dialog_pre_op]
+    });
+    d.register_pre_op('user_password', IPA.user.password_dialog_pre_op0, true);
 };
 phases.on('registration', exp.register);
 
diff --git a/ipatests/test_webui/test_user.py b/ipatests/test_webui/test_user.py
index 766fdafb4ee700fc2a4131dbf92d694be8d17188..4c189db89accd7fc0ac242007bbc2fde0ade89d2 100644
--- a/ipatests/test_webui/test_user.py
+++ b/ipatests/test_webui/test_user.py
@@ -246,7 +246,7 @@ class test_user(UI_driver):
         self.assert_dialog()
 
         fields = [
-            ('password', 'password1', password),
+            ('password', 'password', password),
             ('password', 'password2', password),
         ]
 
@@ -254,6 +254,6 @@ class test_user(UI_driver):
             fields.append(('password', 'current_password', current))
 
         self.fill_fields(fields)
-        self.dialog_button_click('reset_password')
+        self.dialog_button_click('confirm')
         self.wait_for_request(n=3)
         self.assert_no_error_dialog()
-- 
1.9.0

From 62a649f1dd6f2a84795f7be6494b003f02d2ba5f Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 23 May 2014 16:07:33 +0200
Subject: [PATCH] webui: support otp in reset_password.html

https://fedorahosted.org/freeipa/ticket/4262
---
 install/ui/reset_password.html | 10 ++++++++++
 install/ui/reset_password.js   | 10 ++++++++--
 2 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/install/ui/reset_password.html b/install/ui/reset_password.html
index 2b758922615aacda46b9ddbdbf2a973cd2391830..598fe8f1e9cc440699ccfdd0000257dd6f569fc4 100644
--- a/install/ui/reset_password.html
+++ b/install/ui/reset_password.html
@@ -75,6 +75,16 @@
             </div>
             <div class="form-group">
                 <div class="col-sm-4 control-label">
+                    <label for="otp">OTP</label>
+                </div>
+                <div class="col-sm-8 controls">
+                    <div class="widget text-widget">
+                        <input type="password" class="form-control" name="otp" id="otp" accesskey="o">
+                    </div>
+                </div>
+            </div>
+            <div class="form-group">
+                <div class="col-sm-4 control-label">
                     <label for="new_password">New Password</label>
                 </div>
                 <div class="col-sm-8 controls">
diff --git a/install/ui/reset_password.js b/install/ui/reset_password.js
index 5555a17bc3334c60d4949e379c7374251453ec4e..bc08349876ea18d204ce4d6e8ae7724878967620 100644
--- a/install/ui/reset_password.js
+++ b/install/ui/reset_password.js
@@ -20,7 +20,7 @@
 
 var RP = {}; //Reset Password Page
 
-RP.reset_password = function(username, old_password, new_password) {
+RP.reset_password = function(username, old_password, new_password, otp) {
 
     //possible results: 'ok', 'invalid-password', 'policy-error'
 
@@ -55,6 +55,10 @@ RP.reset_password = function(username, old_password, new_password) {
         new_password: new_password
     };
 
+    if (otp) {
+        data.otp = otp;
+    }
+
     request = {
         url: '/ipa/session/change_password',
         data: data,
@@ -89,6 +93,7 @@ RP.on_submit = function() {
 
     var username = $('#user').val();
     var current_password = $('#old_password').val();
+    var otp = $('#otp').val();
     var new_password = $('#new_password').val();
     var verify_password = $('#verify_password').val();
 
@@ -102,7 +107,7 @@ RP.on_submit = function() {
         return;
     }
 
-    var result = RP.reset_password(username, current_password, new_password);
+    var result = RP.reset_password(username, current_password, new_password, otp);
 
     if (result.status !== 'ok') {
         RP.show_error(result.message);
@@ -116,6 +121,7 @@ RP.reset_form = function() {
     $('.alert-danger').css('display', 'none');
     $('.alert-success').css('display', 'none');
     $('#old_password').val('');
+    $('#otp').val('');
     $('#new_password').val('');
     $('#verify_password').val('');
 };
-- 
1.9.0

From dbb3e64a10476a91d5154e8f60e022742b2b5a0b Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 23 May 2014 15:53:54 +0200
Subject: [PATCH] ldap2: add otp support to modify_password

https://fedorahosted.org/freeipa/ticket/4262
---
 ipaserver/plugins/ldap2.py | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
index aa9a001c6c64d5e8e94316ab2f736993ce6903ee..74c9c1ec63ebb122c27a5e86e4a129af4cc9d49d 100644
--- a/ipaserver/plugins/ldap2.py
+++ b/ipaserver/plugins/ldap2.py
@@ -406,18 +406,21 @@ class ldap2(LDAPClient, CrudBackend):
 
         return False
 
-    def modify_password(self, dn, new_pass, old_pass=''):
+    def modify_password(self, dn, new_pass, old_pass='', otp='', skip_bind=False):
         """Set user password."""
 
         assert isinstance(dn, DN)
 
         # The python-ldap passwd command doesn't verify the old password
         # so we'll do a simple bind to validate it.
-        if old_pass != '':
+        if not skip_bind and old_pass != '':
+            pw = old_pass
+            if (otp):
+                pw = old_pass+otp
             with self.error_handler():
                 conn = IPASimpleLDAPObject(
                     self.ldap_uri, force_schema_updates=False)
-                conn.simple_bind_s(dn, old_pass)
+                conn.simple_bind_s(dn, pw)
                 conn.unbind_s()
 
         with self.error_handler():
-- 
1.9.0

From bad15be77084194d335c0441c77326c2e089450e Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 23 May 2014 15:54:18 +0200
Subject: [PATCH] rpcserver: add otp support to change_password handler

https://fedorahosted.org/freeipa/ticket/4262
---
 ipaserver/rpcserver.py | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
index ff1b7fe656b6655c68d5b5d4c576d001b1d266e2..30b9746397636128c0edc3fb4d2ad7f7bdc22495 100644
--- a/ipaserver/rpcserver.py
+++ b/ipaserver/rpcserver.py
@@ -1043,7 +1043,7 @@ class change_password(Backend, HTTP_Status):
             return self.bad_request(environ, start_response, "cannot parse query data")
 
         data = {}
-        for field in ('user', 'old_password', 'new_password'):
+        for field in ('user', 'old_password', 'new_password', 'otp'):
             value = query_dict.get(field, None)
             if value is not None:
                 if len(value) == 1:
@@ -1051,7 +1051,7 @@ class change_password(Backend, HTTP_Status):
                 else:
                     return self.bad_request(environ, start_response, "more than one %s parameter"
                                             % field)
-            else:
+            elif field != 'otp':  # otp is optional
                 return self.bad_request(environ, start_response, "no %s specified" % field)
 
         # start building the response
@@ -1066,9 +1066,12 @@ class change_password(Backend, HTTP_Status):
                      self.api.env.container_user, self.api.env.basedn)
 
         try:
+            pw = data['old_password']
+            if data.get('otp'):
+                pw = data['old_password'] + data['otp']
             conn = ldap2(shared_instance=False,
                          ldap_uri=self.api.env.ldap_uri)
-            conn.connect(bind_dn=bind_dn, bind_pw=data['old_password'])
+            conn.connect(bind_dn=bind_dn, bind_pw=pw)
         except (NotFound, ACIError):
             result = 'invalid-password'
             message = 'The old password or username is not correct.'
@@ -1078,7 +1081,7 @@ class change_password(Backend, HTTP_Status):
                     data['user'], str(e))
         else:
             try:
-                conn.modify_password(bind_dn, data['new_password'], data['old_password'])
+                conn.modify_password(bind_dn, data['new_password'], data['old_password'], skip_bind=True)
             except ExecutionError, e:
                 result = 'policy-error'
                 policy_error = escape(str(e))
-- 
1.9.0

From d5dac2df4a8c190aa40ea33135b5dca4c3f9f29f Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 23 May 2014 09:36:49 +0200
Subject: [PATCH] ipa-passwd: add OTP support

https://fedorahosted.org/freeipa/ticket/4262
---
 API.txt                  |  3 ++-
 VERSION                  |  4 ++--
 ipalib/plugins/passwd.py | 11 ++++++++++-
 3 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/API.txt b/API.txt
index 0dd28068edbd37f021a58195941e102c25fa360f..98b61402822174f6ef20f79307c3158b4c8b5d05 100644
--- a/API.txt
+++ b/API.txt
@@ -2409,10 +2409,11 @@ output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDA
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: passwd
-args: 3,1,3
+args: 3,2,3
 arg: Str('principal', autofill=True, cli_name='user', primary_key=True)
 arg: Password('password')
 arg: Password('current_password', autofill=True, confirm=False)
+option: Password('otp?', confirm=False)
 option: Str('version?', exclude='webui')
 output: Output('result', <type 'bool'>, None)
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
diff --git a/VERSION b/VERSION
index 65e52735079baf0e04f05d295e2b6dd8ad8515f4..a61848d62a88c02e756bc08c21fece037c16cd94 100644
--- a/VERSION
+++ b/VERSION
@@ -89,5 +89,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=93
-# Last change: mbasti - New record type added: DLV
+IPA_API_VERSION_MINOR=94
+# Last change: pvoborni - Add OTP option to passwd command
diff --git a/ipalib/plugins/passwd.py b/ipalib/plugins/passwd.py
index 95b9b6017b79260bb6fc17b4a369ef3517c9e202..f5fc14d510ea7eea1e16435fe11d982d4ca49d79 100644
--- a/ipalib/plugins/passwd.py
+++ b/ipalib/plugins/passwd.py
@@ -89,6 +89,14 @@ class passwd(Command):
         ),
     )
 
+    takes_options =  (
+        Password('otp?',
+                 label=_('OTP'),
+                 doc=_('One Time Password'),
+                 confirm=False,
+        ),
+    )
+
     has_output = output.standard_value
     msg_summary = _('Changed password for "%(value)s"')
 
@@ -121,7 +129,8 @@ class passwd(Command):
         if current_password == MAGIC_VALUE:
             ldap.modify_password(entry_attrs.dn, password)
         else:
-            ldap.modify_password(entry_attrs.dn, password, current_password)
+            otp = options.get('otp')
+            ldap.modify_password(entry_attrs.dn, password, current_password, otp)
 
         return dict(
             result=True,
-- 
1.9.0

From 2a922a040236939f409743d161f2b602325413e9 Mon Sep 17 00:00:00 2001
From: Petr Vobornik <pvobo...@redhat.com>
Date: Fri, 23 May 2014 15:54:53 +0200
Subject: [PATCH] webui: support password change with OTP in login screen

https://fedorahosted.org/freeipa/ticket/4262
---
 install/ui/src/freeipa/ipa.js                 |  6 +++-
 install/ui/src/freeipa/widgets/LoginScreen.js | 51 +++++++++++++++++++++++----
 2 files changed, 49 insertions(+), 8 deletions(-)

diff --git a/install/ui/src/freeipa/ipa.js b/install/ui/src/freeipa/ipa.js
index 8a1ebaed76e1bd5bbecec7e80a35197191c55920..66d92b6e0726211f2a87bc25f84b26319756a9ee 100644
--- a/install/ui/src/freeipa/ipa.js
+++ b/install/ui/src/freeipa/ipa.js
@@ -516,7 +516,7 @@ IPA.login_password = function(username, password) {
  * @return {string} result.status
  * @return {string} result.message
  */
-IPA.reset_password = function(username, old_password, new_password) {
+IPA.reset_password = function(username, old_password, new_password, otp) {
 
     //possible results: 'ok', 'invalid-password', 'policy-error'
 
@@ -553,6 +553,10 @@ IPA.reset_password = function(username, old_password, new_password) {
         new_password: new_password
     };
 
+    if (otp) {
+        data.otp = otp;
+    }
+
     request = {
         url: '/ipa/session/change_password',
         data: data,
diff --git a/install/ui/src/freeipa/widgets/LoginScreen.js b/install/ui/src/freeipa/widgets/LoginScreen.js
index 349a3da1d9ed082df15083dd0d7feb0f242909b2..701c88cf12f07e7d7e4d4efc44d980f332656042 100644
--- a/install/ui/src/freeipa/widgets/LoginScreen.js
+++ b/install/ui/src/freeipa/widgets/LoginScreen.js
@@ -78,6 +78,8 @@ define(['dojo/_base/declare',
 
         password_expired: "Your password has expired. Please enter a new password.",
 
+        password_change_complete: "Password change complete",
+
         denied: "Sorry you are not allowed to access this service.",
 
         caps_warning_msg: "Warning: CAPS LOCK key is on",
@@ -417,23 +419,36 @@ define(['dojo/_base/declare',
             if (!this.validate()) return;
 
             var psw_f = this.get_field('password');
+            var psw_f2 = this.get_field('current_password');
+            var otp_f = this.get_field('otp');
             var new_f = this.get_field('new_password');
             var ver_f = this.get_field('verify_password');
             var username_f = this.get_field('username');
 
+            var psw = psw_f2.get_value()[0] || psw_f.get_value()[0];
+            var otp = otp_f.get_value()[0];
+
             var result = IPA.reset_password(
                 username_f.get_value()[0],
-                psw_f.get_value()[0],
-                new_f.get_value()[0]);
+                psw,
+                new_f.get_value()[0],
+                otp);
 
             if (result.status === 'ok') {
-                psw_f.set_value(new_f.get_value());
-                this.login();
+                val_summary.add_success('login', this.password_change_complete);
+                psw_f.set_value('');
+                psw_f2.set_value('');
+                // do not login if otp is used because it will fail (reuse of OTP)
+                if (!otp) {
+                    psw_f.set_value(new_f.get_value());
+                    this.login();
+                }
                 this.set('view', 'login');
             } else {
                 val_summary.add_error('login', result.message);
             }
 
+            otp_f.set_value('');
             new_f.set_value('');
             ver_f.set_value('');
         },
@@ -456,7 +471,12 @@ define(['dojo/_base/declare',
             }
             if (this.password_enabled()) {
                 this.use_fields(['username', 'password']);
-                this.get_widget('username').focus_input();
+                var username_f = this.get_field('username');
+                if (username_f.get_value()[0]) {
+                    this.get_widget('password').focus_input();
+                } else {
+                    this.get_widget('username').focus_input();
+                }
             } else {
                 this.use_fields([]);
                 this.login_btn_node.focus();
@@ -469,14 +489,14 @@ define(['dojo/_base/declare',
             if (this.buttons_node) {
                 construct.place(this.reset_btn_node, this.buttons_node);
             }
-            this.use_fields(['username_r', 'new_password', 'verify_password']);
+            this.use_fields(['username_r', 'current_password', 'otp', 'new_password', 'verify_password']);
 
             var val_summary = this.get_widget('validation');
 
             var u_f = this.fields.get('username');
             var u_r_f = this.fields.get('username_r');
             u_r_f.set_value(u_f.get_value());
-            this.get_widget('new_password').focus_input();
+            this.get_widget('current_password').focus_input();
         },
 
         use_fields: function(names) {
@@ -536,6 +556,9 @@ define(['dojo/_base/declare',
 
             this.kerberos_msg = this.kerberos_msg.replace('${host}', window.location.hostname);
 
+            this.password_change_complete = text.get(spec.password_change_complete ||
+                '@i18n:password.password_change_complete', this.password_change_complete);
+
             this.krb_auth_failed = text.get(spec.krb_auth_failed, this.krb_auth_failed);
         }
     });
@@ -563,6 +586,20 @@ define(['dojo/_base/declare',
             undo: false
         },
         {
+            name: 'current_password',
+            $type: 'password',
+            label: text.get('@i18n:login.current_password', "Current Password"),
+            show_errors: false,
+            undo: false
+        },
+        {
+            name: 'otp',
+            $type: 'password',
+            label: text.get('@i18n:login.current_password', "OTP"),
+            show_errors: false,
+            undo: false
+        },
+        {
             name: 'new_password',
             $type: 'password',
             required: true,
-- 
1.9.0

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to