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