diff --git a/js/ajax.js b/js/ajax.js index f484d522a7..1616c0545f 100644 --- a/js/ajax.js +++ b/js/ajax.js @@ -336,7 +336,115 @@ var AJAX = { if (typeof onsubmit !== 'function' || onsubmit.apply(this, [event])) { AJAX.active = true; AJAX.$msgbox = PMA_ajaxShowMessage(); - $.post(url, params, AJAX.responseHandler); + if($(this).attr('id') === 'login_form') { + $.post(url, params, AJAX.loginResponseHandler); + } else { + $.post(url, params, AJAX.responseHandler); + } + } + } + }, + /** + * Response handler to handle login request from login modal after session expiration + * + * To refer to self use 'AJAX', instead of 'this' as this function + * is called in the jQuery context. + * + * @param object data Event data + * + * @return void + */ + loginResponseHandler: function (data) { + if (typeof data === 'undefined' || data === null) { + return; + } + PMA_ajaxRemoveMessage(AJAX.$msgbox); + + PMA_commonParams.set("token", data.new_token); + + AJAX.scriptHandler.load([]); + + if (data._displayMessage) { + $('#page_content').prepend(data._displayMessage); + PMA_highlightSQL($('#page_content')); + } + + $('#pma_errors').remove(); + + var msg = ''; + if (data._errSubmitMsg) { + msg = data._errSubmitMsg; + } + if (data._errors) { + $('
', { id : 'pma_errors', class : 'clearfloat' }) + .insertAfter('#selflink') + .append(data._errors); + // bind for php error reporting forms (bottom) + $('#pma_ignore_errors_bottom').on('click', function (e) { + e.preventDefault(); + PMA_ignorePhpErrors(); + }); + $('#pma_ignore_all_errors_bottom').on('click', function (e) { + e.preventDefault(); + PMA_ignorePhpErrors(false); + }); + // In case of 'sendErrorReport'='always' + // submit the hidden error reporting form. + if (data._sendErrorAlways === '1' && + data._stopErrorReportLoop !== '1' + ) { + $('#pma_report_errors_form').submit(); + PMA_ajaxShowMessage(PMA_messages.phpErrorsBeingSubmitted, false); + $('html, body').animate({ scrollTop:$(document).height() }, 'slow'); + } else if (data._promptPhpErrors) { + // otherwise just prompt user if it is set so. + msg = msg + PMA_messages.phpErrorsFound; + // scroll to bottom where all the errors are displayed. + $('html, body').animate({ scrollTop:$(document).height() }, 'slow'); + } + } + + PMA_ajaxShowMessage(msg, false); + // bind for php error reporting forms (popup) + $('#pma_ignore_errors_popup').on('click', function () { + PMA_ignorePhpErrors(); + }); + $('#pma_ignore_all_errors_popup').on('click', function () { + PMA_ignorePhpErrors(false); + }); + + if (typeof data.success !== 'undefined' && data.success) { + // reload page if user trying to login has changed + if(PMA_commonParams.get('user') !== data._params['user']) { + window.location = "index.php"; + PMA_ajaxShowMessage(PMA_messages.strLoading, false); + AJAX.active = false; + AJAX.xhr = null; + return; + } + // remove the login modal if the login is successful otherwise show error. + if(typeof data.logged_in !== 'undefined' && data.logged_in === 1) { + if($("#modalOverlay").length) { + $("#modalOverlay").remove(); + } + $("fieldset.disabled_for_expiration").removeAttr("disabled").removeClass("disabled_for_expiration"); + AJAX.fireTeardown("functions.js"); + AJAX.fireOnload("functions.js"); + } + if(typeof data.new_token !== 'undefined') { + $("input[name=token]").val(data.new_token); + } + + } else if(typeof data.logged_in !== 'undefined' && data.logged_in === 0) { + $("#modalOverlay").replaceWith(data.error); + } else { + PMA_ajaxShowMessage(data.error, false); + AJAX.active = false; + AJAX.xhr = null; + PMA_handleRedirectAndReload(data); + if (data.fieldWithError) { + $(':input.error').removeClass('error'); + $('#' + data.fieldWithError).addClass('error'); } } }, diff --git a/js/functions.js b/js/functions.js index c16c034d8d..9262adfd9d 100644 --- a/js/functions.js +++ b/js/functions.js @@ -949,7 +949,8 @@ AJAX.registerOnload('functions.js', function () { 'server' : PMA_commonParams.get('server'), 'db' : PMA_commonParams.get('db'), 'guid': guid, - 'access_time':_idleSecondsCounter + 'access_time': _idleSecondsCounter, + 'check_timeout': 1 }; $.ajax({ type: 'POST', @@ -978,7 +979,19 @@ AJAX.registerOnload('functions.js', function () { if (isStorageSupported('sessionStorage')) { window.sessionStorage.clear(); } - window.location.reload(true); + // append the login form on the page, disable all the forms which were not disabled already, close all the open jqueryui modal boxes + if (!$("#modalOverlay").length) { + $("fieldset").not(':disabled').attr("disabled", "disabled").addClass("disabled_for_expiration"); + $('body').append(data.error); + $(".ui-dialog").each(function(i) { + $("#" + $(this).attr("aria-describedby")).dialog("close"); + }); + $("#input_username").focus(); + } else { + PMA_commonParams.set("token", data.new_token); + $("input[name=token]").val(data.new_token); + } + _idleSecondsCounter = 0; } } }); @@ -4321,6 +4334,9 @@ function PMA_slidingMessage (msg, $obj) { */ AJAX.registerOnload('functions.js', function () { var $elm = $('#sqlquery'); + if ($elm.siblings().filter(".CodeMirror").length > 0) { + return; + } if ($elm.length > 0) { if (typeof CodeMirror !== 'undefined') { codemirror_editor = PMA_getSQLEditor($elm); diff --git a/js/shortcuts_handler.js b/js/shortcuts_handler.js index 4fc3c59a7b..d53d773d11 100644 --- a/js/shortcuts_handler.js +++ b/js/shortcuts_handler.js @@ -38,6 +38,10 @@ $(document).ready(function () { } }); $(document).on('keydown', function (e) { + //disable the shortcuts when session has timed out. + if ($("#modalOverlay").length > 0) { + return; + } if (e.ctrlKey && e.altKey && e.keyCode === keyC) { PMA_console.toggle(); } diff --git a/js/sql.js b/js/sql.js index e8f299fe88..fd648b8e31 100644 --- a/js/sql.js +++ b/js/sql.js @@ -177,7 +177,7 @@ AJAX.registerTeardown('sql.js', function () { $(document).off('mouseenter', 'th.column_heading.pointer'); $(document).off('mouseleave', 'th.column_heading.pointer'); $(document).off('click', 'th.column_heading.marker'); - $(window).off('scroll'); + $(document).off('scroll', window); $(document).off('keyup', '.filter_rows'); $(document).off('click', '#printView'); if (codemirror_editor) { @@ -405,7 +405,7 @@ AJAX.registerOnload('sql.js', function () { var $stick_columns = initStickyColumns($table_results); rearrangeStickyColumns($stick_columns, $table_results); // adjust sticky columns on scroll - $(window).on('scroll', function () { + $(document).on('scroll', window, function () { handleStickyColumns($stick_columns, $table_results); }); }); diff --git a/libraries/classes/Plugins/Auth/AuthenticationCookie.php b/libraries/classes/Plugins/Auth/AuthenticationCookie.php index 0120bda179..459eaf9e2a 100644 --- a/libraries/classes/Plugins/Auth/AuthenticationCookie.php +++ b/libraries/classes/Plugins/Auth/AuthenticationCookie.php @@ -86,7 +86,10 @@ class AuthenticationCookie extends AuthenticationPlugin global $conn_error; $response = Response::getInstance(); - if ($response->loginPage()) { + + // When sending login modal after session has expired, send the new token explicitly with the response to update the token in all the forms having a hidden token. + $session_expired = isset($_REQUEST['check_timeout']) || isset($_REQUEST['session_timedout']); + if (!$session_expired && $response->loginPage()) { if (defined('TESTSUITE')) { return true; } else { @@ -94,6 +97,23 @@ class AuthenticationCookie extends AuthenticationPlugin } } + // When sending login modal after session has expired, send the new token explicitly with the response to update the token in all the forms having a hidden token. + if($session_expired) { + $response->setRequestStatus(false); + $response->addJSON( + 'new_token', + $_SESSION[' PMA_token '] + ); + } + + // logged_in response parameter is used to check if the login, using the modal was successful after session expiration + if(isset($_REQUEST['session_timedout'])) { + $response->addJSON( + 'logged_in', + 0 + ); + } + // No recall if blowfish secret is not configured as it would produce // garbage if ($GLOBALS['cfg']['LoginCookieRecall'] @@ -109,7 +129,14 @@ class AuthenticationCookie extends AuthenticationPlugin $autocomplete = ' autocomplete="off"'; } - echo $this->template->render('login/header', ['theme' => $GLOBALS['PMA_Theme']]); + // wrap the login form in a div which overlays the whole page. + if($session_expired) { + echo $this->template->render('login/header', ['theme' => $GLOBALS['PMA_Theme'], + 'add_class' => ' modal_form', 'session_expired' => 1]); + } else { + echo $this->template->render('login/header', ['theme' => $GLOBALS['PMA_Theme'], + 'add_class' => '', 'session_expired' => 0]); + } if ($GLOBALS['cfg']['DBG']['demo']) { echo '
'; @@ -148,10 +175,15 @@ class AuthenticationCookie extends AuthenticationPlugin
'; } - echo $this->template->render('login/footer'); + + // close the wrapping div tag, if the request is after session timeout + if($session_expired) { + echo $this->template->render('login/footer', ['session_expired' => 1]); + } else { + echo $this->template->render('login/footer', ['session_expired' => 0]); + } + echo Config::renderFooter(); + if (! defined('TESTSUITE')) { exit; } else { @@ -472,6 +512,7 @@ class AuthenticationCookie extends AuthenticationPlugin { // Name and password cookies need to be refreshed each time // Duration = one month for username + $this->storeUsernameCookie($this->user); // Duration = as configured @@ -480,27 +521,54 @@ class AuthenticationCookie extends AuthenticationPlugin if (! isset($_POST['change_pw'])) { $this->storePasswordCookie($this->password); } + // URL where to go: + $redirect_url = './index.php'; + // any parameters to pass? + $url_params = array(); + if (strlen($GLOBALS['db']) > 0) { + $url_params['db'] = $GLOBALS['db']; + } + if (strlen($GLOBALS['table']) > 0) { + $url_params['table'] = $GLOBALS['table']; + } + // any target to pass? + if (! empty($GLOBALS['target']) + && $GLOBALS['target'] != 'index.php' + ) { + $url_params['target'] = $GLOBALS['target']; + } + + // user logged in successfully after session expiration + if(isset($_REQUEST['session_timedout'])) { + $response = Response::getInstance(); + $response->addJSON( + 'logged_in', + 1 + ); + $response->addJSON( + 'success', + 1 + ); + $response->addJSON( + 'new_token', + $_SESSION[' PMA_token '] + ); + if($user_changed) { + $response->addJSON( + 'user_changed', + 1 + ); + } + if (! defined('TESTSUITE')) { + exit; + } else { + return false; + } + } // Set server cookies if required (once per session) and, in this case, // force reload to ensure the client accepts cookies if (! $GLOBALS['from_cookie']) { - // URL where to go: - $redirect_url = './index.php'; - - // any parameters to pass? - $url_params = []; - if (strlen($GLOBALS['db']) > 0) { - $url_params['db'] = $GLOBALS['db']; - } - if (strlen($GLOBALS['table']) > 0) { - $url_params['table'] = $GLOBALS['table']; - } - // any target to pass? - if (! empty($GLOBALS['target']) - && $GLOBALS['target'] != 'index.php' - ) { - $url_params['target'] = $GLOBALS['target']; - } /** * Clear user cache. diff --git a/libraries/classes/Util.php b/libraries/classes/Util.php index b5c4d9eb48..1f41073596 100644 --- a/libraries/classes/Util.php +++ b/libraries/classes/Util.php @@ -4285,17 +4285,26 @@ class Util public static function getStartAndNumberOfRowsPanel($sql_query) { $template = new Template(); - $pos = isset($_REQUEST['pos']) - ? $_REQUEST['pos'] - : $_SESSION['tmpval']['pos']; + if (isset($_REQUEST['session_max_rows'])) { $rows = $_REQUEST['session_max_rows']; + } else if (isset($_SESSION['tmpval']['max_rows']) + && $_SESSION['tmpval']['max_rows'] != 'all' + ) { + $rows = $_SESSION['tmpval']['max_rows']; } else { - if ($_SESSION['tmpval']['max_rows'] != 'all') { - $rows = $_SESSION['tmpval']['max_rows']; - } else { - $rows = $GLOBALS['cfg']['MaxRows']; - } + $rows = $GLOBALS['cfg']['MaxRows']; + $_SESSION['tmpval']['max_rows'] = $rows; + } + + if(isset($_REQUEST['pos'])) { + $pos = $_REQUEST['pos']; + } else if(isset($_SESSION['tmpval']['pos'])) { + $pos = $_SESSION['tmpval']['pos']; + } else { + $number_of_line = intval($_REQUEST['unlim_num_rows']); + $pos = ((ceil($number_of_line / $rows) - 1) * $rows); + $_SESSION['tmpval']['pos'] = $pos; } return $template->render('start_and_number_of_rows_panel', [ diff --git a/templates/login/footer.twig b/templates/login/footer.twig index 04f5b84499..d9e96591ba 100644 --- a/templates/login/footer.twig +++ b/templates/login/footer.twig @@ -1 +1,4 @@ +{% if check_timeout == true %} + +{% endif %} diff --git a/templates/login/header.twig b/templates/login/header.twig index b0a5e18d6a..4b890cd917 100644 --- a/templates/login/header.twig +++ b/templates/login/header.twig @@ -1,4 +1,7 @@ -
+{% if session_expired == true %} +
+{% endif %} +
diff --git a/test/classes/Plugins/Auth/AuthenticationCookieTest.php b/test/classes/Plugins/Auth/AuthenticationCookieTest.php index 8be69e47fd..f18c26e5ab 100644 --- a/test/classes/Plugins/Auth/AuthenticationCookieTest.php +++ b/test/classes/Plugins/Auth/AuthenticationCookieTest.php @@ -221,7 +221,7 @@ class AuthenticationCookieTest extends PmaTestCase $this->assertContains( '', + 'class="(disableAjax hide login js-show|login js-show)">', $result ); @@ -324,7 +324,7 @@ class AuthenticationCookieTest extends PmaTestCase $this->assertContains( '', + 'class="(disableAjax hide login js-show|login js-show)">', $result ); diff --git a/themes/original/css/common.css.php b/themes/original/css/common.css.php index 359621a084..53e188ba51 100644 --- a/themes/original/css/common.css.php +++ b/themes/original/css/common.css.php @@ -629,6 +629,28 @@ body#loginform div.container { margin: 0 auto; } +div.container.modal_form { + margin: 0 auto; + width: 30em; + text-align: center; + background: #fff; + z-index: 999; +} + +#login_form { + text-align: left; +} + +div#modalOverlay { + position: fixed; + top: 0; + left: 0; + height: 100%; + width: 100%; + background: #fff; + z-index: 900; +} + form.login label { float: ; width: 10em; diff --git a/themes/pmahomme/css/common.css.php b/themes/pmahomme/css/common.css.php index 492a363a89..476812ccf6 100644 --- a/themes/pmahomme/css/common.css.php +++ b/themes/pmahomme/css/common.css.php @@ -909,6 +909,28 @@ body#loginform div.container { margin: 0 auto; } +div.container.modal_form { + margin: 0 auto; + width: 30em; + text-align: center; + background: #fff; + z-index: 999; +} + +#login_form { + text-align: left; +} + +div#modalOverlay { + position: fixed; + top: 0; + left: 0; + height: 100%; + width: 100%; + background: #fff; + z-index: 900; +} + form.login label { float: ; width: 10em;