URL: https://github.com/freeipa/freeipa/pull/2356
Author: serg-cymbaluk
 Title: #2356: [Backport][ipa-4-7] Fix reset_password page translations
Action: opened

PR body:
"""
This PR was opened automatically because PR #2109 was pushed to master and 
backport to ipa-4-7 is required.
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/2356/head:pr2356
git checkout pr2356
From 722b6b316c7f3403996bb948dc9da102d0c096b0 Mon Sep 17 00:00:00 2001
From: Stanislav Levin <s...@altlinux.org>
Date: Fri, 6 Jul 2018 09:31:37 +0300
Subject: [PATCH 1/7] Replace the direct URL with config's one

To be customizable URL should be placed to "config"

Fixes: https://pagure.io/freeipa/issue/7619
---
 install/ui/src/freeipa/config.js              | 7 ++++++-
 install/ui/src/freeipa/ipa.js                 | 8 ++++----
 install/ui/src/freeipa/widgets/LoginScreen.js | 8 ++++----
 3 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/install/ui/src/freeipa/config.js b/install/ui/src/freeipa/config.js
index 14e80956e5..50595b3da1 100644
--- a/install/ui/src/freeipa/config.js
+++ b/install/ui/src/freeipa/config.js
@@ -66,10 +66,15 @@ define([
         /**
          * Forms based login url
          */
-        frms_login_url: '/ipa/session/login_password',
+        forms_login_url: '/ipa/session/login_password',
 
         //logout_url: '/ipa/session/json',
 
+        /**
+         * certificate/smartcard authentication url
+         */
+        x509_login_url: '/ipa/session/login_x509',
+
         /**
          * Password reset url
          */
diff --git a/install/ui/src/freeipa/ipa.js b/install/ui/src/freeipa/ipa.js
index 016ca819b0..2be64742b9 100644
--- a/install/ui/src/freeipa/ipa.js
+++ b/install/ui/src/freeipa/ipa.js
@@ -62,7 +62,7 @@ var IPA = function () {
     that.jsonrpc_id = 0;
 
     // live server path
-    that.url = '/ipa/ui/';
+    that.url = config.url;
 
     /**
      * jQuery AJAX options used by RPC commands
@@ -122,7 +122,7 @@ var IPA = function () {
 
         // if current path matches live server path, use live data
         if (that.url && window.location.pathname.substring(0, that.url.length) === that.url) {
-            that.json_url = params.url || '/ipa/session/json';
+            that.json_url = params.url || config.json_url;
 
         } else { // otherwise use fixtures
             that.json_path = params.url || "test/data";
@@ -514,7 +514,7 @@ IPA.login_password = function(username, password) {
     };
 
     var request = {
-        url: '/ipa/session/login_password',
+        url: config.forms_login_url,
         data: data,
         contentType: 'application/x-www-form-urlencoded',
         processData: true,
@@ -587,7 +587,7 @@ IPA.reset_password = function(username, old_password, new_password, otp) {
     }
 
     request = {
-        url: '/ipa/session/change_password',
+        url: config.reset_psw_url,
         data: data,
         contentType: 'application/x-www-form-urlencoded',
         processData: true,
diff --git a/install/ui/src/freeipa/widgets/LoginScreen.js b/install/ui/src/freeipa/widgets/LoginScreen.js
index 9e5f38b480..818b5a3fff 100644
--- a/install/ui/src/freeipa/widgets/LoginScreen.js
+++ b/install/ui/src/freeipa/widgets/LoginScreen.js
@@ -27,6 +27,7 @@ define(['dojo/_base/declare',
         'dojo/topic',
         '../ipa',
         '../auth',
+        '../config',
         '../reg',
         '../FieldBinder',
         '../text',
@@ -34,7 +35,8 @@ define(['dojo/_base/declare',
         './LoginScreenBase'
        ],
        function(declare, Deferred, construct, dom_style, query, on, topic,
-                IPA, auth, reg, FieldBinder, text, util, LoginScreenBase) {
+                IPA, auth, config, reg, FieldBinder, text, util,
+                LoginScreenBase) {
 
 
     /**
@@ -78,8 +80,6 @@ define(['dojo/_base/declare',
 
         user_locked: "The user account you entered is locked. ",
 
-        x509_url: '/ipa/session/login_x509',
-
         //nodes:
         login_btn_node: null,
         reset_btn_node: null,
@@ -345,7 +345,7 @@ define(['dojo/_base/declare',
             var login = this.get_field('username').get_value()[0];
 
             var request = {
-                url: this.x509_url,
+                url: config.x509_login_url,
                 cache: false,
                 type: "GET",
                 data: $.param({

From 3d5b36ce503c667257e39c74fab1b9700f2bc9a3 Mon Sep 17 00:00:00 2001
From: Stanislav Levin <s...@altlinux.org>
Date: Fri, 6 Jul 2018 10:42:25 +0300
Subject: [PATCH 2/7] Add "reset_and_login" view to LoginScreen widget

Previous "reset" view is splitted to "reset" and "reset_and_login"
ones. "reset" is used to render "just reset password" logic. And
"reset_and_login" - "reset password and then log in".

Fixes: https://pagure.io/freeipa/issue/7619
---
 install/ui/src/freeipa/widgets/LoginScreen.js | 101 ++++++++++++++----
 1 file changed, 78 insertions(+), 23 deletions(-)

diff --git a/install/ui/src/freeipa/widgets/LoginScreen.js b/install/ui/src/freeipa/widgets/LoginScreen.js
index 818b5a3fff..3092970e46 100644
--- a/install/ui/src/freeipa/widgets/LoginScreen.js
+++ b/install/ui/src/freeipa/widgets/LoginScreen.js
@@ -88,7 +88,7 @@ define(['dojo/_base/declare',
         /**
          * View this form is in.
          *
-         * Possible views: ['login', 'reset']
+         * Possible views: ['login', 'reset', 'reset_and_login']
          * @property {string}
          */
         view: 'login',
@@ -164,14 +164,16 @@ define(['dojo/_base/declare',
         },
 
         post_create_fields: function() {
-            var u_f = this.get_field('username');
-            var p_f = this.get_field('password');
-            var otp_f = this.get_field('otp');
-
-            u_f.on('value-change', this.on_form_change.bind(this));
-            p_f.on('value-change', this.on_form_change.bind(this));
-            otp_f.on('value-change', this.on_otp_change.bind(this));
-            this.on_form_change();
+            if (this.view === 'login') {
+                var u_f = this.get_field('username');
+                var p_f = this.get_field('password');
+                var otp_f = this.get_field('otp');
+
+                u_f.on('value-change', this.on_form_change.bind(this));
+                p_f.on('value-change', this.on_form_change.bind(this));
+                otp_f.on('value-change', this.on_otp_change.bind(this));
+                this.on_form_change();
+            }
         },
 
         on_form_change: function(event) {
@@ -185,7 +187,7 @@ define(['dojo/_base/declare',
         },
 
         on_otp_change: function(event) {
-            if (this.view === 'login') return;
+            if (this.view === 'login' || this.view === 'reset') return;
             if (!event.value[0]) {
                 this.set_visible_buttons(['cancel', 'reset_and_login']);
             } else {
@@ -200,10 +202,12 @@ define(['dojo/_base/declare',
         },
 
         on_confirm: function() {
-            if (this.view == 'login') {
+            if (this.view === 'login') {
                 this.login();
-            } else {
-                this.login_and_reset();
+            } else if (this.view === 'reset_and_login') {
+                this.reset_and_login();
+            } else if (this.view === 'reset') {
+                this.reset();
             }
         },
 
@@ -254,7 +258,7 @@ define(['dojo/_base/declare',
                     this.emit('logged_in');
                     password_f.set_value('');
                 } else if (result === 'password-expired') {
-                    this.set('view', 'reset');
+                    this.set('view', 'reset_and_login');
                     val_summary.add_info('login', this.password_expired);
                 } else if (result === 'krbprincipal-expired') {
                     password_f.set_value('');
@@ -284,12 +288,7 @@ define(['dojo/_base/declare',
             }.bind(this));
         },
 
-        login_and_reset: function() {
-
-            var val_summary = this.get_widget('validation');
-            val_summary.remove('login');
-
-            if (!this.validate()) return;
+        reset_password: function() {
 
             var psw_f = this.get_field('password');
             var psw_f2 = this.get_field('current_password');
@@ -310,7 +309,29 @@ define(['dojo/_base/declare',
             if (result.status === 'ok') {
                 psw_f.set_value('');
                 psw_f2.set_value('');
-                // do not login if otp is used because it will fail (reuse of OTP)
+            } else {
+                otp_f.set_value('');
+                new_f.set_value('');
+                ver_f.set_value('');
+            }
+            return result;
+        },
+
+        reset_and_login: function() {
+
+            if (!this.validate()) return;
+            var val_summary = this.get_widget('validation');
+            val_summary.remove('login');
+            var psw_f = this.get_field('password');
+            var new_f = this.get_field('new_password');
+            var otp_f = this.get_field('otp');
+            var otp = otp_f.get_value()[0];
+
+            var result = this.reset_password();
+            if (result.status === 'ok') {
+                /* 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();
@@ -318,12 +339,29 @@ define(['dojo/_base/declare',
                 val_summary.add_success('login', this.password_change_complete);
                 this.set('view', 'login');
             } else {
+                val_summary.add_error('login', result.message);
+            }
+        },
+
+        reset: function() {
+
+            if (!this.validate()) return;
+            var val_summary = this.get_widget('validation');
+            val_summary.remove('login');
+            var otp_f = this.get_field('otp');
+            var new_f = this.get_field('new_password');
+            var ver_f = this.get_field('verify_password');
+
+            var result = this.reset_password();
+            if (result.status === 'ok') {
                 otp_f.set_value('');
                 new_f.set_value('');
                 ver_f.set_value('');
+                val_summary.add_success('login', this.password_change_complete);
+                this.redirect();
+            } else {
                 val_summary.add_error('login', result.message);
             }
-
         },
 
         lookup_credentials: function() {
@@ -363,6 +401,8 @@ define(['dojo/_base/declare',
         refresh: function() {
             if (this.view === 'reset') {
                 this.show_reset_view();
+            } else if (this.view === 'reset_and_login') {
+                    this.show_reset_and_login_view();
             } else {
                 this.show_login_view();
             }
@@ -391,9 +431,24 @@ define(['dojo/_base/declare',
 
         show_reset_view: function() {
 
+            this.set_reset_aside_text();
+            this.set_visible_buttons(['reset']);
+            this.use_fields(['username', 'current_password', 'otp',
+                             'new_password', 'verify_password']);
+
+            var val_summary = this.get_widget('validation');
+            this.fields.get('username').set_required(true);
+            this.fields.get('current_password').set_required(true);
+
+            this.get_widget('username').focus_input();
+        },
+
+        show_reset_and_login_view: function() {
+
             this.set_reset_aside_text();
             this.set_visible_buttons(['cancel', 'reset_and_login']);
-            this.use_fields(['username_r', 'current_password', 'otp', 'new_password', 'verify_password']);
+            this.use_fields(['username_r', 'current_password', 'otp',
+                             'new_password', 'verify_password']);
 
             var val_summary = this.get_widget('validation');
 

From 90643b39ecfc87341e94ca6826bdb15154c01db4 Mon Sep 17 00:00:00 2001
From: Stanislav Levin <s...@altlinux.org>
Date: Fri, 6 Jul 2018 11:38:01 +0300
Subject: [PATCH 3/7] Use "login" plugin instead of standalone JS file

Plugin "login" already has the same functionality as a JS code in
separated javascript file. There is no need to duplicate it.

Fixes: https://pagure.io/freeipa/issue/7619
---
 install/ui/reset_password.html | 158 +++++++++++----------------------
 1 file changed, 53 insertions(+), 105 deletions(-)

diff --git a/install/ui/reset_password.html b/install/ui/reset_password.html
index ec09bbfafc..e5700c9647 100644
--- a/install/ui/reset_password.html
+++ b/install/ui/reset_password.html
@@ -1,122 +1,70 @@
 <!DOCTYPE html>
-<html class="login-pf">
+<html>
 <head>
     <meta charset="utf-8">
     <title>IPA: Identity Policy Audit</title>
+
+    <!--[if IE]>
+    <meta id="ie-detector">
+    <![endif]-->
+
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <script type="text/javascript" src="js/libs/loader.js"></script>
     <script type="text/javascript">
+
+        var dojoConfig = {
+            baseUrl: "js",
+            has: {
+                'dojo-firebug': false,
+                'dojo-debug-messages': true
+            },
+            parseOnLoad: false,
+            async: true,
+            packages: [
+                {
+                    name:'dojo',
+                    location:'dojo'
+                },
+                {
+                    name: 'freeipa',
+                    location: 'freeipa'
+                }
+            ],
+            cacheBust: ipa_loader.num_version || ""
+        };
+
         (function() {
+            var ie = !!document.getElementById('ie-detector');
             var styles = [
-                '../ui/css/patternfly.css',
-                '../ui/css/ipa.css',
-                '../ui/ipa.css'
+                'css/patternfly.css',
+                'css/ipa.css',
+                'ipa.css'
             ];
-            ipa_loader.styles(styles);
-            ipa_loader.scripts([
+            if (ie) styles.push('ie.css');
+            var icons = ['favicon.ico'];
+            var scripts = [
+                'js/libs/json2.js',
                 'js/libs/jquery.js',
-                'reset_password.js'
-            ]);
+                'js/libs/bootstrap.js',
+                'js/libs/jquery.ordered-map.js',
+                'js/libs/browser.js',
+                'js/dojo/dojo.js'
+            ];
+            ipa_loader.scripts(scripts, function() {
+                require(['freeipa/core', 'dojo/domReady!'], function(app) {
+                    var reset_pass = require('freeipa/plugins/login');
+                    reset_pass.facet_spec.widgets[1].view = "reset";
+                    app.run_simple('login');
+                });
+            });
+            ipa_loader.styles(styles);
+            ipa_loader.icons(icons);
+
         })();
     </script>
 </head>
 
 <body>
-
-<div class="login-pf-body">
-<span id="badge">
-    <img src="images/login-screen-logo.png" alt="" />
-</span>
-<div class="container">
-
-<div class="row">
-
-    <div class="col-sm-12">
-      <div id="brand">
-        <img src="images/product-name.png" alt="">
-      </div>
-    </div>
-
-    <div class="col-sm-7 col-md-6 col-lg-5 login">
-        <form class="form-horizontal" role="form" action="/ipa/session/change_password" method="post" id="reset_password">
-            <div class="form-group validation-summary-group">
-                <div class="col-sm-12 controls">
-                    <div class="widget validation-summary">
-                        <div class="alert alert-success" style="display:none;">
-                            <span class="fa fa-check-circle-o"></span><p></p>
-                        </div>
-                        <div class="alert alert-danger" style="display:none;">
-                            <span class="fa fa-exclamation-circle"></span><p></p>
-                        </div>
-                        <div class="alert alert-warning" style="display:none;">
-                            <span class="fa fa-warning"></span><p></p>
-                        </div>
-                        <div class="alert alert-info" style="display:none;">
-                            <span class="fa fa-info-circle"></span><p></p>
-                        </div>
-                    </div>
-                </div>
-            </div>
-            <div class="form-group">
-                <div class="col-sm-4 control-label">
-                    <label for="user">Username</label>
-                </div>
-                <div class="col-sm-8 controls">
-                    <div class="widget text-widget">
-                        <input type="text" class="form-control" name="user" id="user" title="Username" accesskey="u">
-                    </div>
-                </div>
-            </div>
-            <div class="form-group">
-                <div class="col-sm-4 control-label">
-                    <label for="old_password">Current Password</label>
-                </div>
-                <div class="col-sm-8 controls">
-                    <div class="widget text-widget">
-                        <input type="password" class="form-control" name="old_password" id="old_password" accesskey="p">
-                    </div>
-                </div>
-            </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">
-                    <div class="widget text-widget">
-                        <input type="password" class="form-control" name="new_password" id="new_password"  accesskey="n">
-                    </div>
-                </div>
-            </div>
-            <div class="form-group">
-                <div class="col-sm-4 control-label">
-                    <label for="verify_password">Verify Password</label>
-                </div>
-                <div class="col-sm-8 controls">
-                    <div class="widget text-widget">
-                        <input type="password" class="form-control" id="verify_password" accesskey="v">
-                    </div>
-                </div>
-            </div>
-            <div class="form-group">
-                <div class="col-xs-4 col-sm-offset-4 col-sm-8 submit">
-                    <input name="submit" class="btn btn-primary btn-lg" value="Reset" type="submit" />
-                </div>
-            </div>
-        </form>
-    </div>
-    <div class="col-sm-5 col-md-6 col-lg-7 details">
-    </div>
-</div>
-</div>
-</div>
+    <noscript>This application requires JavaScript enabled.</noscript>
 </body>
 </html>

From 6477264945d8beaecdd233207437d8155d2d4478 Mon Sep 17 00:00:00 2001
From: Stanislav Levin <s...@altlinux.org>
Date: Fri, 6 Jul 2018 11:49:20 +0300
Subject: [PATCH 4/7] Clean up reset_password.js file from project

reset_password.js is no longer needed as it's functionality is moved
to "login" plugin.

Fixes: https://pagure.io/freeipa/issue/7619
---
 freeipa.spec.in              |   1 -
 install/ui/Makefile.am       |   1 -
 install/ui/reset_password.js | 224 -----------------------------------
 3 files changed, 226 deletions(-)
 delete mode 100644 install/ui/reset_password.js

diff --git a/freeipa.spec.in b/freeipa.spec.in
index ffa12a776d..84e46d4dc8 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -1428,7 +1428,6 @@ fi
 %{_usr}/share/ipa/ui/sync_otp.html
 %{_usr}/share/ipa/ui/*.ico
 %{_usr}/share/ipa/ui/*.css
-%{_usr}/share/ipa/ui/*.js
 %dir %{_usr}/share/ipa/ui/css
 %{_usr}/share/ipa/ui/css/*.css
 %dir %{_usr}/share/ipa/ui/js
diff --git a/install/ui/Makefile.am b/install/ui/Makefile.am
index 8f9a9556fb..a8179198eb 100644
--- a/install/ui/Makefile.am
+++ b/install/ui/Makefile.am
@@ -15,7 +15,6 @@ app_DATA =				\
 	index.html 			\
 	ie.css				\
 	ipa.css				\
-	reset_password.js		\
 	reset_password.html		\
 	sync_otp.html		\
 	$(NULL)
diff --git a/install/ui/reset_password.js b/install/ui/reset_password.js
deleted file mode 100644
index 8dcdefdd03..0000000000
--- a/install/ui/reset_password.js
+++ /dev/null
@@ -1,224 +0,0 @@
-/*  Authors:
- *    Petr Vobornik <pvobo...@redhat.com>
- *
- * Copyright (C) 2010 Red Hat
- * see file 'COPYING' for use and warranty information
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-var RP = {}; //Reset Password Page
-
-RP.reset_password = function(username, old_password, new_password, otp) {
-
-    //possible results: 'ok', 'invalid-password', 'policy-error'
-
-    var status, result, reason, invalid, failure, data, request;
-
-    status = 'invalid';
-    result = {
-        status: status,
-        message: "Password reset was not successful."
-    };
-
-    function success_handler(data, text_status, xhr) {
-
-        result.status = xhr.getResponseHeader("X-IPA-Pwchange-Result") || status;
-
-        if (result.status === 'policy-error') {
-            result.message = xhr.getResponseHeader("X-IPA-Pwchange-Policy-Error");
-        } else if (result.status === 'invalid-password') {
-            result.message = "The password or username you entered is incorrect.";
-        }
-
-        return result;
-    }
-
-    function error_handler(xhr, text_status, error_thrown) {
-        return result;
-    }
-
-    data = {
-        user: username,
-        old_password: old_password,
-        new_password: new_password
-    };
-
-    if (otp) {
-        data.otp = otp;
-    }
-
-    request = {
-        url: '/ipa/session/change_password',
-        data: data,
-        contentType: 'application/x-www-form-urlencoded',
-        processData: true,
-        dataType: 'html',
-        async: false,
-        type: 'POST',
-        success: success_handler,
-        error: error_handler
-    };
-
-    $.ajax(request);
-
-    return result;
-};
-
-RP.verify_required = function(field, value) {
-
-    var valid = true;
-
-    if (!value || value === '') {
-        valid = false;
-        RP.show_error(field +" is required");
-    }
-
-    return valid;
-};
-
-
-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();
-
-    if (!RP.verify_required('Username', username)) return;
-    if (!RP.verify_required('Current Password', current_password)) return;
-    if (!RP.verify_required('New Password', new_password)) return;
-    if (!RP.verify_required('Verify Password', verify_password)) return;
-
-    if (new_password !== verify_password) {
-        RP.show_error("Passwords must match");
-        return;
-    }
-
-    var result = RP.reset_password(username, current_password, new_password, otp);
-
-    if (result.status !== 'ok') {
-        RP.show_error(result.message);
-    } else {
-        RP.reset_form();
-        RP.show_success("Password reset was successful.");
-        RP.redirect();
-    }
-};
-
-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('');
-};
-
-RP.show_error = function(message) {
-
-    $('.alert-danger > p').text(message);
-    $('.alert-danger').css('display', '');
-    $('.alert-success').css('display', 'none');
-};
-
-RP.show_info = function(message) {
-
-    $('.alert-info > p').text(message || '');
-    if (!message) {
-        $('.alert-info').css('display', 'none');
-    } else {
-        $('.alert-info').css('display', '');
-    }
-};
-
-RP.show_success = function(message) {
-
-    $('.alert-success > p').text(message);
-    $('.alert-danger').css('display', 'none');
-    $('.alert-success').css('display', '');
-};
-
-RP.parse_uri = function() {
-    var opts = {};
-    if (window.location.search.length > 1) {
-        var couples = window.location.search.substr(1).split("&");
-        for (var i=0,l=couples.length; i < l; i++) {
-            var couple = couples[i].split("=");
-            var key = decodeURIComponent(couple[0]);
-            var value = couple.length > 1 ? decodeURIComponent(couple[1]) : '';
-            opts[key] = value;
-        }
-    }
-    return opts;
-};
-
-RP.redirect = function() {
-
-    var opts = RP.parse_uri();
-    var url = opts['url'];
-    var delay = parseInt(opts['delay'], 10);
-
-    var msg_cont = $('.alert-success > p');
-    $('.redirect', msg_cont).remove();
-
-    // button for manual redirection
-    if (url) {
-        var redir_cont = $('<span/>', { 'class': 'redirect' }).
-            append(' ').
-            append($('<a/>', {
-                href: url,
-                text: 'Continue to next page'
-            })).
-            appendTo(msg_cont);
-    } else {
-        return;
-    }
-
-    if (delay <= 0 || delay > 0) { // NaN check
-        RP.redir_url = url;
-        RP.redir_delay = delay;
-        RP.redir_count_down();
-    }
-};
-
-RP.redir_count_down = function() {
-
-    RP.show_info("You will be redirected in " + Math.max(RP.redir_delay, 0) + "s");
-    if (RP.redir_delay <= 0) {
-        window.location = RP.redir_url;
-        return;
-    }
-    window.setTimeout(RP.redir_count_down, 1000);
-    RP.redir_delay--;
-};
-
-
-RP.init = function() {
-    var opts = RP.parse_uri();
-    if (opts['user']) {
-        $("#user").val(opts['user']);
-    }
-
-    $('#reset_password').submit(function() {
-        RP.on_submit();
-        return false;
-    });
-};
-
-/* main (document onready event handler) */
-$(function() {
-    RP.init();
-});

From 6e6e2e4e234cbe254d6be847597617e9b0494f9b Mon Sep 17 00:00:00 2001
From: Stanislav Levin <s...@altlinux.org>
Date: Fri, 6 Jul 2018 11:56:26 +0300
Subject: [PATCH 5/7] Fix translations of messages in LoginScreen widget

To be translatable messages should be marked with '@i18n' and
present in "i18n_messages" dictionary.

Fixes: https://pagure.io/freeipa/issue/7619
---
 install/ui/src/freeipa/widgets/LoginScreen.js | 167 ++++++++++++++----
 ipaserver/plugins/internal.py                 |  22 ++-
 2 files changed, 148 insertions(+), 41 deletions(-)

diff --git a/install/ui/src/freeipa/widgets/LoginScreen.js b/install/ui/src/freeipa/widgets/LoginScreen.js
index 3092970e46..ce7681c7c6 100644
--- a/install/ui/src/freeipa/widgets/LoginScreen.js
+++ b/install/ui/src/freeipa/widgets/LoginScreen.js
@@ -53,32 +53,35 @@ define(['dojo/_base/declare',
 
         expired_msg: "Your session has expired. Please re-login.",
 
-        form_auth_msg: "<i class=\"fa fa-info-circle\"></i> To log in with <strong>username and password</strong>, enter them in the corresponding fields, then click Login.",
+        form_auth_msg: "<i class=\"fa fa-info-circle\"></i> To log in with " +
+            "<strong>username and password</strong>, enter them in the " +
+            "corresponding fields, then click Login.",
 
-        kerberos_msg: "<i class=\"fa fa-info-circle\"></i> To log in with <strong>Kerberos</strong>, please make sure you" +
-                    " have valid tickets (obtainable via kinit) and " +
-                    "<a href='http://${host}/ipa/config/ssbrowser.html'>configured</a>" +
-                    " the browser correctly, then click Login. ",
-        cert_msg: "<i class=\"fa fa-info-circle\"></i> To log in with <strong>certificate</strong>," +
-              " please make sure you have valid personal certificate. ",
+        kerberos_msg: "<i class=\"fa fa-info-circle\"></i> To log in with " +
+            "<strong>Kerberos</strong>, please make sure you" +
+            " have valid tickets (obtainable via kinit) and <a href=" +
+            "'http://${host}/ipa/config/ssbrowser.html'>configured</a>" +
+            " the browser correctly, then click Login. ",
+        cert_msg: "<i class=\"fa fa-info-circle\"></i> To log in with " +
+            "<strong>certificate</strong>, please make sure you have valid " +
+            "personal certificate. ",
 
-        form_auth_failed: "Login failed due to an unknown reason. ",
+        form_auth_failed: "Login failed due to an unknown reason",
 
         krb_auth_failed: "Authentication with Kerberos failed",
 
         cert_auth_failed: "Authentication with personal certificate failed",
 
-        password_expired: "Your password has expired. Please enter a new password.",
+        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.",
+        krbprincipal_expired: "Kerberos Principal you entered is expired",
 
-        krbprincipal_expired: "Kerberos Principal you entered is expired.",
+        invalid_password: "The password or username you entered is incorrect",
 
-        invalid_password: "The password you entered is incorrect. ",
-
-        user_locked: "The user account you entered is locked. ",
+        user_locked: "The user account you entered is locked",
 
         //nodes:
         login_btn_node: null,
@@ -136,7 +139,8 @@ define(['dojo/_base/declare',
 
             this.reset_btn_node = IPA.button({
                 name: 'reset',
-                label: text.get('@i18n:buttons.reset_password', "Reset Password"),
+                label: text.get('@i18n:buttons.reset_password',
+                                "Reset Password"),
                 'class': 'btn-primary btn-lg',
                 click: this.on_confirm.bind(this)
             })[0];
@@ -145,7 +149,8 @@ define(['dojo/_base/declare',
 
             this.reset_and_login_btn_node = IPA.button({
                 name: 'reset_and_login',
-                label: text.get('@i18n:buttons.reset_password_and_login', "Reset Password and Login"),
+                label: text.get('@i18n:buttons.reset_password_and_login',
+                                "Reset Password and Log in"),
                 'class': 'btn-primary btn-lg',
                 click: this.on_confirm.bind(this)
             })[0];
@@ -480,21 +485,81 @@ define(['dojo/_base/declare',
         constructor: function(spec) {
             spec = spec || {};
 
-            this.expired_msg = text.get(spec.expired_msg || '@i18n:ajax.401.message',
-                this.expired_msg);
-
-            this.form_auth_msg = text.get(spec.form_auth_msg || '@i18n:login.form_auth',
-                this.form_auth_msg);
-
-            this.kerberos_msg = text.get(spec.kerberos_msg || '@i18n:login.krb_auth_msg',
-                this.kerberos_msg);
-
-            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);
+            this.expired_msg = text.get(
+                spec.expired_msg || '@i18n:ajax.401.message',
+                this.expired_msg
+            );
+
+            this.form_auth_msg = text.get(
+                spec.form_auth_msg || '@i18n:login.form_auth',
+                this.form_auth_msg
+            );
+
+            this.kerberos_msg = text.get(
+                spec.kerberos_msg || '@i18n:login.krb_auth_msg',
+                this.kerberos_msg
+            );
+
+            this.cert_msg = text.get(
+                spec.cert_msg || '@i18n:login.cert_msg',
+                this.cert_msg
+            );
+
+            this.redirect_msg = text.get(
+                spec.redirect_msg || '@i18n:login.redirect_msg',
+                this.redirect_msg
+            );
+
+            this.continue_msg = text.get(
+                spec.continue_msg || '@i18n:login.continue_msg',
+                this.continue_msg
+            );
+
+            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.form_auth_failed = text.get(
+                spec.form_auth_failed || '@i18n:login.form_auth_failed',
+                this.form_auth_failed
+            );
+
+            this.krb_auth_failed = text.get(
+                spec.krb_auth_failed || '@i18n:login.krb_auth_failed',
+                this.krb_auth_failed
+            );
+
+            this.cert_auth_failed = text.get(
+                spec.cert_auth_failed || '@i18n:login.cert_auth_failed',
+                this.cert_auth_failed
+            );
+
+            this.password_expired = text.get(
+                spec.password_expired || '@i18n:password.password_expired',
+                this.password_expired
+            );
+
+            this.krbprincipal_expired = text.get(
+                spec.krbprincipal_expired ||
+                    '@i18n:login.krbprincipal_expired',
+                this.krbprincipal_expired
+            );
+
+            this.invalid_password = text.get(
+                spec.invalid_password || '@i18n:password.invalid_password',
+                this.invalid_password
+            );
+
+            this.user_locked = text.get(
+                spec.user_locked || '@i18n:login.user_locked',
+                this.user_locked
+            );
 
             this.field_specs = LoginScreen.field_specs;
         }
@@ -513,7 +578,10 @@ 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'),
+            placeholder: text.get(
+                '@i18n:login.password_and_otp',
+                'Password or Password+One-Time-Password'
+            ),
             show_errors: false,
             undo: false
         },
@@ -527,8 +595,14 @@ 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"),
+            label: text.get(
+                '@i18n:password.current_password',
+                "Current Password"
+            ),
+            placeholder: text.get(
+                '@i18n:password.current_password',
+                "Current Password"
+            ),
             show_errors: false,
             undo: false
         },
@@ -536,7 +610,10 @@ define(['dojo/_base/declare',
             name: 'otp',
             $type: 'password',
             label: text.get('@i18n:password.otp', "OTP"),
-            placeholder: text.get('@i18n:password.otp_long', 'One-Time-Password'),
+            placeholder: text.get(
+                '@i18n:password.otp_long',
+                'One-Time-Password'
+            ),
             show_errors: false,
             undo: false
         },
@@ -544,8 +621,14 @@ define(['dojo/_base/declare',
             name: 'new_password',
             $type: 'password',
             required: true,
-            label: text.get('@i18n:password.new_password)', "New Password"),
-            placeholder: text.get('@i18n:password.new_password)', "New Password"),
+            label: text.get(
+                '@i18n:password.new_password',
+                "New Password"
+            ),
+            placeholder: text.get(
+                '@i18n:password.new_password',
+                "New Password"
+            ),
             show_errors: false,
             undo: false
         },
@@ -553,8 +636,14 @@ define(['dojo/_base/declare',
             name: 'verify_password',
             $type: 'password',
             required: true,
-            label: text.get('@i18n:password.verify_password', "Verify Password"),
-            placeholder: text.get('@i18n:password.new_password)', "New Password"),
+            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/ipaserver/plugins/internal.py b/ipaserver/plugins/internal.py
index e5c8f720a1..d3dd4aa58c 100644
--- a/ipaserver/plugins/internal.py
+++ b/ipaserver/plugins/internal.py
@@ -241,7 +241,8 @@ class i18n_messages(Command):
             "remove": _("Delete"),
             "remove_hold": _("Remove hold"),
             "reset": _("Reset"),
-            "reset_password_and_login": _("Reset Password and Login"),
+            "reset_password": _("Reset Password"),
+            "reset_password_and_login": _("Reset Password and Log in"),
             "restore": _("Restore"),
             "retry": _("Retry"),
             "revert": _("Revert"),
@@ -342,11 +343,21 @@ class i18n_messages(Command):
         },
         "login": {
             "authenticating": _("Authenticating"),
+            "cert_auth_failed": _(
+                "Authentication with personal certificate failed"),
+            "cert_msg": _(
+                "<i class=\"fa fa-info-circle\"></i> To log in with "
+                "<strong>certificate</strong>, please make sure you have "
+                "valid personal certificate. "
+            ),
+            "continue_msg": _("Continue to next page"),
             "form_auth": _(
                 "<i class=\"fa fa-info-circle\"></i> To log in with "
                 "<strong>username and password</strong>, enter them in the "
                 "corresponding fields, then click 'Log in'."),
+            "form_auth_failed": _("Login failed due to an unknown reason"),
             "header": _("Logged In As"),
+            "krb_auth_failed": _("Authentication with Kerberos failed"),
             "krb_auth_msg": _(
                 "<i class=\"fa fa-info-circle\"></i> To log in with "
                 "<strong>Kerberos</strong>, please make sure you have valid "
@@ -354,6 +365,8 @@ class i18n_messages(Command):
                 "ipa/config/ssbrowser.html'>configured</a> the browser "
                 "correctly, then click 'Log in'."),
             "loading": _("Loading"),
+            "krbprincipal_expired": _(
+                "Kerberos Principal you entered is expired"),
             "loading_md": _("Loading data"),
             "login": _("Log in"),
             "login_certificate": _("Log In Using Certificate"),
@@ -362,9 +375,11 @@ class i18n_messages(Command):
             "logout_error": _("Log out error"),
             "password": _("Password"),
             "password_and_otp": _("Password or Password+One-Time-Password"),
+            "redirect_msg": _("You will be redirected in ${count}s"),
             "sync_otp_token": _("Sync OTP Token"),
             "synchronizing": _("Synchronizing"),
             "username": _("Username"),
+            "user_locked": _("The user account you entered is locked"),
         },
         "measurement_units": {
             "number_of_passwords": _("number of passwords"),
@@ -930,7 +945,8 @@ class i18n_messages(Command):
             "current_password_required": _("Current password is required"),
             "expires_in": _("Your password expires in ${days} days."),
             "first_otp": _("First OTP"),
-            "invalid_password": _("The password or username you entered is incorrect."),
+            "invalid_password": _(
+                "The password or username you entered is incorrect"),
             "new_password": _("New Password"),
             "new_password_required": _("New password is required"),
             "otp": _("OTP"),
@@ -942,6 +958,8 @@ class i18n_messages(Command):
             "password": _("Password"),
             "password_and_otp": _("Password or Password+One-Time-Password"),
             "password_change_complete": _("Password change complete"),
+            "password_expired": _(
+                "Your password has expired. Please enter a new password."),
             "password_must_match": _("Passwords must match"),
             "reset_failure": _("Password reset was not successful."),
             "reset_password": _("Reset Password"),

From dc718b822d75ced303d714e28fc1871fb9ab8c54 Mon Sep 17 00:00:00 2001
From: Stanislav Levin <s...@altlinux.org>
Date: Thu, 12 Jul 2018 13:23:42 +0300
Subject: [PATCH 6/7] Add "bounce" logic from "reset_password.js"

This should add support for https://pagure.io/freeipa/issue/4440

Fixes: https://pagure.io/freeipa/issue/7619
---
 install/ui/src/freeipa/widgets/LoginScreen.js | 67 +++++++++++++++++++
 1 file changed, 67 insertions(+)

diff --git a/install/ui/src/freeipa/widgets/LoginScreen.js b/install/ui/src/freeipa/widgets/LoginScreen.js
index ce7681c7c6..b4af193bb8 100644
--- a/install/ui/src/freeipa/widgets/LoginScreen.js
+++ b/install/ui/src/freeipa/widgets/LoginScreen.js
@@ -66,6 +66,8 @@ define(['dojo/_base/declare',
             "<strong>certificate</strong>, please make sure you have valid " +
             "personal certificate. ",
 
+        continue_msg: "Continue to next page",
+
         form_auth_failed: "Login failed due to an unknown reason",
 
         krb_auth_failed: "Authentication with Kerberos failed",
@@ -77,6 +79,8 @@ define(['dojo/_base/declare',
 
         password_change_complete: "Password change complete",
 
+        redirect_msg: "You will be redirected in ${count}s",
+
         krbprincipal_expired: "Kerberos Principal you entered is expired",
 
         invalid_password: "The password or username you entered is incorrect",
@@ -293,6 +297,69 @@ define(['dojo/_base/declare',
             }.bind(this));
         },
 
+        parse_uri: function() {
+            var opts = {};
+            if (window.location.search.length > 1) {
+                var couples = window.location.search.substr(1).split("&");
+                for (var i=0,l=couples.length; i < l; i++) {
+                    var couple = couples[i].split("=");
+                    var key = decodeURIComponent(couple[0]);
+                    var value = couple.length > 1 ? decodeURIComponent(couple[1]) : '';
+                    opts[key] = value;
+                }
+            }
+            return opts;
+        },
+
+        redir_count_down: function(redir_url, redir_delay) {
+            var val_summary = this.get_widget('validation');
+            val_summary.add_info('redirect',
+                this.redirect_msg.replace('${count}', Math.max(redir_delay, 0)));
+
+            if (redir_delay <= 0) {
+                window.location = redir_url;
+                return;
+            }
+            window.setTimeout(this.redir_count_down.bind(this), 1000,
+                redir_url, redir_delay-1);
+        },
+
+        /* If password reset was initiated due to a failed log in to some
+         * external application one could use a redirection to the desired URL
+         * after a successfull password reset.
+         *
+         * The next uri components are supported:
+         * - 'url' destination URL, which must be URI encoded
+         * - 'delay' time in seconds to delay before redirection
+         *
+         * For example,
+         * https://ipa.demo1.freeipa.org/ipa/ui/reset_password.html?url=http%3A%2F%2Fpvoborni.fedorapeople.org%2Fdoc%2F%23!%2Fguide%2FDebugging&delay=5
+        */
+        redirect: function() {
+            var opts = this.parse_uri();
+            var url = opts['url'];
+            var delay = parseInt(opts['delay'], 10);
+
+            var val_summary = this.get_widget('validation');
+            // button for manual redirection
+            if (url) {
+                val_summary.add_success(
+                    'login',
+                    "".concat(
+                        this.password_change_complete,
+                        ' <a href="', url, '">',
+                        this.continue_msg, '</a>'
+                    )
+                );
+            } else {
+                return;
+            }
+
+            if (delay <= 0 || delay > 0) { // NaN check
+                this.redir_count_down(url, delay);
+            }
+        },
+
         reset_password: function() {
 
             var psw_f = this.get_field('password');

From 918762a9b22b796fe23fc00c49158e0b9c6853e8 Mon Sep 17 00:00:00 2001
From: Stanislav Levin <s...@altlinux.org>
Date: Fri, 24 Aug 2018 18:00:38 +0300
Subject: [PATCH 7/7] Add tests for LoginScreen widget

Add some basic tests for different aspects of LoginScreen such as
'login', 'reset_and_login', 'reset' views.

Fixes: https://pagure.io/freeipa/issue/7619
---
 ipatests/test_webui/data_loginscreen.py | 161 ++++++++++
 ipatests/test_webui/test_loginscreen.py | 401 ++++++++++++++++++++++++
 2 files changed, 562 insertions(+)
 create mode 100644 ipatests/test_webui/data_loginscreen.py
 create mode 100644 ipatests/test_webui/test_loginscreen.py

diff --git a/ipatests/test_webui/data_loginscreen.py b/ipatests/test_webui/data_loginscreen.py
new file mode 100644
index 0000000000..62063a606d
--- /dev/null
+++ b/ipatests/test_webui/data_loginscreen.py
@@ -0,0 +1,161 @@
+#
+# Copyright (C) 2018  FreeIPA Contributors see COPYING for license
+#
+
+ENTITY = 'user'
+
+PKEY = 'itest-user'
+PASSWD_ITEST_USER = '12345678'
+PASSWD_ITEST_USER_NEW = '87654321'
+
+# used for add/delete fixture test user
+DATA_ITEST_USER = {
+    'pkey': PKEY,
+    'add': [
+        ('textbox', 'uid', PKEY),
+        ('textbox', 'givenname', 'itest-user-name'),
+        ('textbox', 'sn', 'itest-user-surname'),
+        ('password', 'userpassword', PASSWD_ITEST_USER),
+        ('password', 'userpassword2', PASSWD_ITEST_USER),
+    ]
+}
+
+# used for checking login form after click Cancel on 'reset' view
+FILLED_LOGIN_FORM = {
+    # structure of rows
+    # label_name, label_text,
+    # required, editable,
+    # input_type, input_name,
+    # input_text, placeholder
+    'rows': [
+        ('username', 'Username', True, True, 'text', 'username',
+         PKEY, 'Username'),
+        ('password', 'Password', True, True, 'password', 'password',
+         PASSWD_ITEST_USER, 'Password or Password+One-Time-Password'),
+    ],
+    # structure of buttons
+    # button_name, button_title
+    'buttons': [
+        ('cert_auth', 'Log in using personal certificate'),
+        ('sync', 'Sync OTP Token'),
+        ('login', 'Log in'),
+    ],
+    'required_msg': [
+        ('Username: Required field',),
+        ('Password: Required field',),
+    ],
+}
+
+# used for checking 'reset_and_login' view
+RESET_AND_LOGIN_FORM = {
+    # structure of rows
+    # label_name, label_text,
+    # required, editable,
+    # input_type, input_name,
+    # input_text, placeholder
+    'rows': [
+        ('username_r', 'Username', False, False, None, 'username_r',
+         PKEY, None),
+        ('current_password', 'Current Password', False, True, 'password',
+         'current_password', '', 'Current Password'),
+        ('otp', 'OTP', False, True, 'password', 'otp', '',
+         'One-Time-Password'),
+        ('new_password', 'New Password', True, True, 'password',
+         'new_password', '', 'New Password'),
+        ('verify_password', 'Verify Password', True, True, 'password',
+         'verify_password', '', 'New Password'),
+    ],
+    # structure of buttons
+    # button_name, button_title
+    'buttons': [
+        ('cancel', 'Cancel'),
+        ('reset_and_login', 'Reset Password and Log in'),
+    ],
+    'required_msg': [
+        ('New Password: Required field',),
+        ('Verify Password: Required field',),
+    ],
+}
+
+# used for checking 'reset' view
+RESET_PASSWORD_FORM = {
+    # structure of rows
+    # label_name, label_text,
+    # required, editable,
+    # input_type, input_name,
+    # input_text, placeholder
+    'rows': [
+        ('username', 'Username', True, True, 'text', 'username', '',
+         'Username'),
+        ('current_password', 'Current Password', True, True, 'password',
+         'current_password', '', 'Current Password'),
+        ('otp', 'OTP', False, True, 'password', 'otp', '',
+         'One-Time-Password'),
+        ('new_password', 'New Password', True, True, 'password',
+         'new_password', '', 'New Password'),
+        ('verify_password', 'Verify Password', True, True, 'password',
+         'verify_password', '', 'New Password'),
+    ],
+    # structure of buttons
+    # button_name, button_title
+    'buttons': [
+        ('reset', 'Reset Password'),
+    ],
+    'required_msg': [
+        ('Username: Required field',),
+        ('Current Password: Required field',),
+        ('New Password: Required field',),
+        ('Verify Password: Required field',),
+    ],
+}
+
+
+# used for checking empty 'login' view
+EMPTY_LOGIN_FORM = {
+    # structure of rows
+    # label_name, label_text,
+    # required, editable,
+    # input_type, input_name,
+    # input_text, placeholder
+    'rows': [
+        ('username', 'Username', False, True, 'text', 'username', '',
+         'Username'),
+        ('password', 'Password', False, True, 'password', 'password', '',
+         'Password or Password+One-Time-Password'),
+    ],
+    # structure of buttons
+    # button_name, button_title
+    'buttons': [
+        ('cert_auth', 'Log in using personal certificate'),
+        ('sync', 'Sync OTP Token'),
+        ('login', 'Log in'),
+    ],
+    'required_msg': [
+        ('Authentication with Kerberos failed',),
+    ],
+}
+
+# used for checking 'login' view
+LOGIN_FORM = {
+    # structure of rows
+    # label_name, label_text,
+    # required, editable,
+    # input_type, input_name,
+    # input_text, placeholder
+    'rows': [
+        ('username', 'Username', True, True, 'text', 'username', PKEY,
+         'Username'),
+        ('password', 'Password', True, True, 'password', 'password', '',
+         'Password or Password+One-Time-Password'),
+    ],
+    # structure of buttons
+    # button_name, button_title
+    'buttons': [
+        ('cert_auth', 'Log in using personal certificate'),
+        ('sync', 'Sync OTP Token'),
+        ('login', 'Log in'),
+    ],
+    'required_msg': [
+        ('Password: Required field',),
+    ],
+}
diff --git a/ipatests/test_webui/test_loginscreen.py b/ipatests/test_webui/test_loginscreen.py
new file mode 100644
index 0000000000..ef4a26ad56
--- /dev/null
+++ b/ipatests/test_webui/test_loginscreen.py
@@ -0,0 +1,401 @@
+#
+# Copyright (C) 2018  FreeIPA Contributors see COPYING for license
+#
+
+"""
+Test LoginScreen widget and all it's views
+"""
+
+from ipatests.test_webui.ui_driver import UI_driver
+from ipatests.test_webui.ui_driver import screenshot
+import ipatests.test_webui.data_loginscreen as loginscreen
+
+try:
+    from selenium.webdriver.common.by import By
+    from selenium.webdriver.common.keys import Keys
+    from selenium.webdriver.support.wait import WebDriverWait
+except ImportError:
+    pass
+
+import pytest
+from six.moves import urllib
+
+
+@pytest.mark.tier1
+class TestLoginScreen(UI_driver):
+
+    def setup(self, *args, **kwargs):
+        super(TestLoginScreen, self).setup(*args, **kwargs)
+        self.init_app()
+        self.add_test_user()
+        self.logout()
+
+    def teardown(self, *args, **kwargs):
+        # log out first
+        if (self.logged_in()):
+            self.logout()
+        else:
+            self.load_url(self.get_base_url())
+        # log in as administrator
+        self.login()
+        self.delete_test_user()
+        super(TestLoginScreen, self).teardown(*args, **kwargs)
+
+    def delete_test_user(self):
+        """
+        Delete user for tests
+        """
+        # User is not logged in
+        assert self.logged_in()
+        self.navigate_to_entity(loginscreen.ENTITY)
+        self.delete_record(loginscreen.PKEY)
+
+    def add_test_user(self):
+        """
+        Add user for tests
+        """
+        # User is not logged in
+        assert self.logged_in()
+        self.add_record(loginscreen.ENTITY, loginscreen.DATA_ITEST_USER,
+                        navigate=False)
+
+    def assert_notification(self, type='success', assert_text=None,
+                            link_text=None, link_url=None):
+        """
+        Assert whether we have a notification of particular type
+        """
+
+        notification_type = 'div.validation-summary .alert-{}'.format(type)
+        # wait for a half sec for notification to appear
+        self.wait(0.5)
+        is_present = self.find(notification_type, By.CSS_SELECTOR)
+        # Notification not present
+        assert is_present
+        if assert_text:
+            assert assert_text in is_present.text
+
+        if link_text and link_url:
+            link = self.find_xelement(".//a", is_present)
+            # Text on link placed on validation widget
+            assert link_text == link.text
+            # URL of link placed on validation widget
+            assert link_url == link.get_attribute('href')
+
+    def find_xelement(self, expression, parent, strict=True):
+        """
+        Find element by xpath related to a given parent
+        """
+        return self.find(expression, By.XPATH, parent, many=False,
+                         strict=strict)
+
+    def find_xelements(self, expression, parent, strict=True):
+        """
+        Find elements by xpath related to the given parent
+        """
+        return self.find(expression, By.XPATH, parent, many=True,
+                         strict=strict)
+
+    def button_click_on_login_screen(self, name):
+        """
+        Find a button with the given name on LoginScreen widget and
+        then click on
+        """
+        login_screen = self.get_login_screen()
+        button = self.find_xelement(".//button[@name='{}']".format(name),
+                                    login_screen)
+        assert button.is_displayed()
+        button.click()
+
+    def get_input_field(self, name, parent):
+        """
+        Find a input field with the given name and parent
+        """
+        return self.find_xelement(
+            ".//input[@name='{}']".format(name),
+            parent
+        )
+
+    def load_url(self, url):
+        """
+        Navigate to Web page and wait for loading of all dependencies.
+        """
+        self.driver.get(url)
+        runner = self
+        WebDriverWait(self.driver, 10).until(
+            lambda d: runner.files_loaded()
+        )
+
+    def relogin_with_new_password(self):
+        """
+        Log out and then log in using a new password.
+        It is need to check a new password
+        """
+        if (self.logged_in()):
+            self.logout()
+        else:
+            self.load_url(self.get_base_url())
+        self.login(loginscreen.PKEY, loginscreen.PASSWD_ITEST_USER_NEW)
+        # User is not logged in
+        assert self.logged_in()
+
+    def reset_password(self, username=None, current_password=None,
+                       new_password=None, link_text=None, link_url=None):
+        """
+        Reset password with the given one
+        """
+        login_screen = self.get_login_screen()
+
+        if username is not None:
+            username_field = self.get_input_field('username', login_screen)
+        cur_pass_field = self.get_input_field('current_password', login_screen)
+        new_pass_field = self.get_input_field('new_password', login_screen)
+        verify_pass_field = self.get_input_field('verify_password',
+                                                 login_screen)
+        if username is not None:
+            username_field.send_keys(username)
+        cur_pass_field.send_keys(current_password)
+        new_pass_field.send_keys(new_password)
+        verify_pass_field.send_keys(new_password)
+        verify_pass_field.send_keys(Keys.RETURN)
+        self.wait()
+        self.assert_notification(assert_text='Password change complete',
+                                 link_text=link_text, link_url=link_url)
+
+    def get_data_from_form_row(self, form_row):
+        """
+        Parse data from the form record to a comparable structure
+        """
+        result = []
+        label = self.find_xelement(".//label", form_row)
+        assert label.is_displayed()
+        result.append(label.get_attribute('name'))
+        result.append(label.text)
+        req = self.find_xelement("./../..", label)
+        result.append(self.has_class(req, 'required'))
+
+        field = self.find_xelement(".//input", form_row)
+        # not editable field
+        editable = field.is_displayed()
+        if not editable:
+            field = self.find_xelement(".//p", form_row)
+
+        result.append(editable)
+        assert field.is_displayed()
+        result.append(field.get_attribute('type'))
+        result.append(field.get_attribute('name'))
+        if not editable:
+            result.append(field.text)
+        else:
+            result.append(field.get_attribute('value'))
+        result.append(field.get_attribute('placeholder'))
+
+        return tuple(result)
+
+    def get_data_from_button(self, button):
+        """
+        Parse data from the button to a comparable structure
+        """
+        result = []
+        result.append(button.get_attribute('name'))
+        result.append(button.get_attribute('title'))
+
+        return tuple(result)
+
+    def assert_form_equals(self, actual_form, expected_form):
+        """
+        Compare two forms
+        """
+        assert len(actual_form) == len(expected_form)
+        for act_row, exp_row in zip(actual_form, expected_form):
+            # structure of rows
+            # label_name, label_text,
+            # required, editable,
+            # input_type, input_name,
+            # input_text, placeholder
+            assert self.get_data_from_form_row(act_row) == exp_row
+
+    def assert_buttons_equal(self, actual_buttons, expected_buttons):
+        """
+        Compare button sets
+        """
+        assert len(actual_buttons) == len(expected_buttons)
+        for act_button, exp_button in zip(actual_buttons, expected_buttons):
+            assert self.get_data_from_button(act_button) == exp_button
+
+    def assert_validations_equal(self, actual_alerts, expected_alerts):
+        """
+        Compare validation sets
+        """
+        assert len(actual_alerts) == len(expected_alerts)
+        for act_alert, exp_alert in zip(actual_alerts, expected_alerts):
+            assert (act_alert.text,) == exp_alert
+
+    def has_validation(self, parent):
+        return self.find_xelement(".//div[@name='validation']", parent,
+                                  strict=False)
+
+    def check_elements_of_form(self, form_data):
+
+        login_screen = self.get_login_screen()
+        form = self.find_xelement(".//div[@class='form-horizontal']",
+                                  login_screen)
+        # rows
+        form_rows = self.find_xelements(
+            ".//div[contains(@class, 'form-group')]", form
+        )
+
+        form_rows = [el for el in form_rows if el.is_displayed() and not
+                     self.has_validation(el)]
+        self.assert_form_equals(form_rows, form_data['rows'])
+
+        # buttons
+        buttons = self.find_xelements(".//button", login_screen)
+        buttons = [el for el in buttons if el.is_displayed()]
+        self.assert_buttons_equal(buttons, form_data['buttons'])
+
+    def check_alerts(self, form_data):
+        login_screen = self.get_login_screen()
+
+        # Push the the most rigth button to see the Required fields
+        # it should be either 'Reset' or 'Reset and Login' or 'Login' button
+        self.button_click_on_login_screen(form_data['buttons'][-1][0])
+
+        alerts = self.find_xelements(
+            ".//*[@data-name][contains(@class, 'alert-danger')]", login_screen
+        )
+        required_msgs = form_data['required_msg']
+        self.assert_validations_equal(alerts, required_msgs)
+
+    def load_reset_and_login_view(self):
+
+        self.load()
+        assert self.login_screen_visible()
+        username = loginscreen.PKEY
+        current_password = loginscreen.PASSWD_ITEST_USER
+
+        login_screen = self.get_login_screen()
+        username_field = self.get_input_field('username', login_screen)
+        cur_pass_field = self.get_input_field('password', login_screen)
+        username_field.send_keys(username)
+        cur_pass_field.send_keys(current_password)
+        cur_pass_field.send_keys(Keys.RETURN)
+        self.wait()
+        self.assert_notification(
+            type='info',
+            assert_text=(
+                'Your password has expired. Please enter a new password.'
+            )
+        )
+
+    def check_cancel(self):
+        """
+        Check 'login' view after a cancel of password reset
+        """
+        self.button_click_on_login_screen('cancel')
+        self.check_elements_of_form(loginscreen.FILLED_LOGIN_FORM)
+
+    @screenshot
+    def test_reset_password_view(self):
+
+        self.load_url('/'.join((self.get_base_url(), 'reset_password.html')))
+        assert self.login_screen_visible()
+
+        self.check_elements_of_form(loginscreen.RESET_PASSWORD_FORM)
+        self.check_alerts(loginscreen.RESET_PASSWORD_FORM)
+
+        username = loginscreen.PKEY
+        current_password = loginscreen.PASSWD_ITEST_USER
+        new_password = loginscreen.PASSWD_ITEST_USER_NEW
+        self.reset_password(username, current_password, new_password)
+        self.relogin_with_new_password()
+
+    @screenshot
+    def test_reset_password_view_with_redirect(self):
+
+        redir_url = self.get_base_url().lower()
+        encoded_redir_url = urllib.parse.urlencode({'url': redir_url})
+        target_url = '/'.join((self.get_base_url(), 'reset_password.html?{}'))
+        self.load_url(target_url.format(encoded_redir_url))
+        assert self.login_screen_visible()
+
+        self.check_elements_of_form(loginscreen.RESET_PASSWORD_FORM)
+        self.check_alerts(loginscreen.RESET_PASSWORD_FORM)
+
+        username = loginscreen.PKEY
+        current_password = loginscreen.PASSWD_ITEST_USER
+        new_password = loginscreen.PASSWD_ITEST_USER_NEW
+        self.reset_password(username, current_password, new_password,
+                            link_text='Continue to next page',
+                            link_url=redir_url,
+                            )
+        self.relogin_with_new_password()
+
+    @screenshot
+    def test_reset_password_view_with_delayed_redirect(self):
+
+        redir_url = self.get_base_url().lower() + '/'
+        encoded_redir_url = urllib.parse.urlencode(
+            {'url': redir_url, 'delay': 5}
+        )
+        target_url = '/'.join((self.get_base_url(), 'reset_password.html?{}'))
+        self.load_url(target_url.format(encoded_redir_url))
+        assert self.login_screen_visible()
+
+        self.check_elements_of_form(loginscreen.RESET_PASSWORD_FORM)
+        self.check_alerts(loginscreen.RESET_PASSWORD_FORM)
+        username = loginscreen.PKEY
+        current_password = loginscreen.PASSWD_ITEST_USER
+        new_password = loginscreen.PASSWD_ITEST_USER_NEW
+        self.reset_password(username, current_password, new_password,
+                            link_text='Continue to next page',
+                            link_url=redir_url,
+                            )
+        self.assert_notification(type='info',
+                                 assert_text='You will be redirected in ')
+        self.wait(3)
+        # check url after start delay timer, but before end
+        assert self.driver.current_url != redir_url
+        self.wait(5)
+        assert self.driver.current_url == redir_url
+        self.relogin_with_new_password()
+
+    @screenshot
+    def test_reset_password_and_login_view(self):
+
+        self.load_reset_and_login_view()
+        self.check_elements_of_form(loginscreen.RESET_AND_LOGIN_FORM)
+        self.check_alerts(loginscreen.RESET_AND_LOGIN_FORM)
+
+        # check click on 'Cancel' button
+        self.check_cancel()
+        self.button_click_on_login_screen('login')
+
+        # check if user is not logged
+        assert not self.logged_in()
+        current_password = loginscreen.PASSWD_ITEST_USER
+        new_password = loginscreen.PASSWD_ITEST_USER_NEW
+        # username is already here, do not fill up
+        self.reset_password(current_password=current_password,
+                            new_password=new_password)
+        # waiting for auto login process
+        self.wait(5)
+        assert self.logged_in()
+        # check again if new password is valid
+        self.relogin_with_new_password()
+
+    @screenshot
+    def test_login_view(self):
+
+        self.load()
+
+        # check empty 'login' view
+        self.check_elements_of_form(loginscreen.EMPTY_LOGIN_FORM)
+        self.check_alerts(loginscreen.EMPTY_LOGIN_FORM)
+
+        # check non empty 'login' view
+        login_screen = self.get_login_screen()
+        username_field = self.get_input_field('username', login_screen)
+        username_field.send_keys(loginscreen.PKEY)
+        self.wait(0.5)
+        self.check_elements_of_form(loginscreen.LOGIN_FORM)
+        self.check_alerts(loginscreen.LOGIN_FORM)
_______________________________________________
FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org
To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org
Fedora Code of Conduct: https://getfedora.org/code-of-conduct.html
List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines
List Archives: 
https://lists.fedorahosted.org/archives/list/freeipa-devel@lists.fedorahosted.org

Reply via email to