From 7667689d65b812426eb049a55c91ae3f2561d455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Tue, 14 Feb 2017 15:56:22 +0100 Subject: [PATCH 1/3] Use more reasonable timeouts for keepalive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - always update before session expiry - shorter interval in the end Fixes #11231 Signed-off-by: Michal Čihař --- js/functions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/functions.js b/js/functions.js index 9c662c7620..d0e30932c3 100644 --- a/js/functions.js +++ b/js/functions.js @@ -940,11 +940,11 @@ AJAX.registerOnload('functions.js', function () { var remaining = PMA_commonParams.get('LoginCookieValidity') - _idleSecondsCounter; if (remaining > 5) { // max value for setInterval() function - var interval = Math.min(remaining * 1000, Math.pow(2, 31) - 1); + var interval = Math.min((remaining - 1) * 1000, Math.pow(2, 31) - 1); updateTimeout = window.setTimeout(UpdateIdleTime, interval); } else if (remaining > 0) { // We're close to session expiry - updateTimeout = window.setTimeout(UpdateIdleTime, 2000); + updateTimeout = window.setTimeout(UpdateIdleTime, 1000); } } else { //timeout occurred if(isStorageSupported('sessionStorage')){ From dc3ef3ce7f55cd19d01192ccd029230833f28cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Tue, 14 Feb 2017 16:01:38 +0100 Subject: [PATCH 2/3] Try harder to honor LoginCookieValidity setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Even when it is lower than session.gc_maxlifetime we try to keepalive the session by AJAX. Fixes #11231 Signed-off-by: Michal Čihař --- ChangeLog | 1 + js/functions.js | 25 ++++++++++++++++++------- libraries/Header.php | 1 + 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index 991f4b4e07..0456555691 100644 --- a/ChangeLog +++ b/ChangeLog @@ -76,6 +76,7 @@ phpMyAdmin - ChangeLog - issue #12976 Improved foreign key editor behavior - issue #12958 Always show error reporting dialog on top - issue #12693 Improved support for TokuDB +- issue #11231 Try harder to honor LoginCookieValidity setting 4.6.6 (2017-01-23) - issue #12759 Fix Notice regarding 'Undefined index: old_usergroup' diff --git a/js/functions.js b/js/functions.js index d0e30932c3..7843eb483d 100644 --- a/js/functions.js +++ b/js/functions.js @@ -937,15 +937,22 @@ AJAX.registerOnload('functions.js', function () { data: params, success: function (data) { if (data.success) { - var remaining = PMA_commonParams.get('LoginCookieValidity') - _idleSecondsCounter; + if (PMA_commonParams.get('LoginCookieValidity') - _idleSecondsCounter < 0) { + /* There is other active window, let's reset counter */ + _idleSecondsCounter = 0; + } + var remaining = Math.min( + /* Remaining login validity */ + PMA_commonParams.get('LoginCookieValidity') - _idleSecondsCounter, + /* Remaining time till session GC */ + PMA_commonParams.get('session_gc_maxlifetime') + ); + var interval = 1000; if (remaining > 5) { // max value for setInterval() function - var interval = Math.min((remaining - 1) * 1000, Math.pow(2, 31) - 1); - updateTimeout = window.setTimeout(UpdateIdleTime, interval); - } else if (remaining > 0) { - // We're close to session expiry - updateTimeout = window.setTimeout(UpdateIdleTime, 1000); + interval = Math.min((remaining - 1) * 1000, Math.pow(2, 31) - 1); } + updateTimeout = window.setTimeout(UpdateIdleTime, interval); } else { //timeout occurred if(isStorageSupported('sessionStorage')){ window.sessionStorage.clear(); @@ -958,7 +965,11 @@ AJAX.registerOnload('functions.js', function () { } if (PMA_commonParams.get('logged_in') && PMA_commonParams.get('auth_type') == 'cookie') { IncInterval = window.setInterval(SetIdleTime, 1000); - var interval = (PMA_commonParams.get('LoginCookieValidity') - 5) * 1000; + var session_timeout = Math.min( + PMA_commonParams.get('LoginCookieValidity'), + PMA_commonParams.get('session_gc_maxlifetime') + ); + var interval = (session_timeout - 5) * 1000; if (interval > Math.pow(2, 31) - 1) { // max value for setInterval() function interval = Math.pow(2, 31) - 1; } diff --git a/libraries/Header.php b/libraries/Header.php index 4f608f4e87..5f49e269a6 100644 --- a/libraries/Header.php +++ b/libraries/Header.php @@ -252,6 +252,7 @@ class Header 'pftext' => $pftext, 'confirm' => $GLOBALS['cfg']['Confirm'], 'LoginCookieValidity' => $GLOBALS['cfg']['LoginCookieValidity'], + 'session_gc_maxlifetime' => (int)@ini_get('session.gc_maxlifetime'), 'logged_in' => isset($GLOBALS['userlink']) ? true : false, 'PMA_VERSION' => PMA_VERSION ); From 82f13e34d0f9e96960f5bdbf7fd3edc048c69f58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Tue, 14 Feb 2017 16:56:57 +0100 Subject: [PATCH 3/3] Better handle multiple open browser windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calculate session expiry per browser (tracking is based on sessionStorage which is unique per tab). With this we're able to detect that there is another window open and actively used and avoid logging it out by inactive one. Issue #11231 Signed-off-by: Michal Čihař --- js/functions.js | 19 ++++++++++++++++++- libraries/plugins/AuthenticationPlugin.php | 14 ++++++++++---- .../plugins/auth/AuthenticationCookie.php | 10 ++++++++-- .../plugin/auth/AuthenticationCookieTest.php | 4 ++-- 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/js/functions.js b/js/functions.js index 7843eb483d..afbb09683f 100644 --- a/js/functions.js +++ b/js/functions.js @@ -918,17 +918,31 @@ AJAX.registerOnload('functions.js', function () { document.onkeypress = function() { _idleSecondsCounter = 0; }; + function guid() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + } function SetIdleTime() { _idleSecondsCounter++; } function UpdateIdleTime() { var href = 'index.php'; + var guid = 'default'; + if (isStorageSupported('sessionStorage')) { + guid = window.sessionStorage.guid; + } var params = { 'ajax_request' : true, 'token' : PMA_commonParams.get('token'), 'server' : PMA_commonParams.get('server'), 'db' : PMA_commonParams.get('db'), + 'guid': guid, 'access_time':_idleSecondsCounter }; $.ajax({ @@ -954,7 +968,7 @@ AJAX.registerOnload('functions.js', function () { } updateTimeout = window.setTimeout(UpdateIdleTime, interval); } else { //timeout occurred - if(isStorageSupported('sessionStorage')){ + if (isStorageSupported('sessionStorage')){ window.sessionStorage.clear(); } window.location.reload(true); @@ -969,6 +983,9 @@ AJAX.registerOnload('functions.js', function () { PMA_commonParams.get('LoginCookieValidity'), PMA_commonParams.get('session_gc_maxlifetime') ); + if (isStorageSupported('sessionStorage')) { + window.sessionStorage.setItem('guid', guid()); + } var interval = (session_timeout - 5) * 1000; if (interval > Math.pow(2, 31) - 1) { // max value for setInterval() function interval = Math.pow(2, 31) - 1; diff --git a/libraries/plugins/AuthenticationPlugin.php b/libraries/plugins/AuthenticationPlugin.php index 630610f286..479b06d51a 100644 --- a/libraries/plugins/AuthenticationPlugin.php +++ b/libraries/plugins/AuthenticationPlugin.php @@ -145,17 +145,23 @@ abstract class AuthenticationPlugin * * @return void */ - public function setSessionAccessTime() - { + public function setSessionAccessTime() + { + if (isset($_REQUEST['guid'])) { + $guid = (string)$_REQUEST['guid']; + } else { + $guid = 'default'; + } if (isset($_REQUEST['access_time'])) { // Ensure access_time is in range <0, LoginCookieValidity + 1> // to avoid excessive extension of validity. // // Negative values can cause session expiry extension // Too big values can cause overflow and lead to same - $_SESSION['last_access_time'] = time() - min(max(0, intval($_REQUEST['access_time'])), $GLOBALS['cfg']['LoginCookieValidity'] + 1); + $time = time() - min(max(0, intval($_REQUEST['access_time'])), $GLOBALS['cfg']['LoginCookieValidity'] + 1); } else { - $_SESSION['last_access_time'] = time(); + $time = time(); } + $_SESSION['browser_access_time'][$guid] = $time; } } diff --git a/libraries/plugins/auth/AuthenticationCookie.php b/libraries/plugins/auth/AuthenticationCookie.php index 8a444d6c4a..a26152fcca 100644 --- a/libraries/plugins/auth/AuthenticationCookie.php +++ b/libraries/plugins/auth/AuthenticationCookie.php @@ -379,13 +379,19 @@ class AuthenticationCookie extends AuthenticationPlugin ); // user was never logged in since session start - if (empty($_SESSION['last_access_time'])) { + if (empty($_SESSION['browser_access_time'])) { return false; } // User inactive too long $last_access_time = time() - $GLOBALS['cfg']['LoginCookieValidity']; - if ($_SESSION['last_access_time'] < $last_access_time) { + foreach ($_SESSION['browser_access_time'] as $key => $value) { + if ($value < $last_access_time) { + unset($_SESSION['browser_access_time'][$key]); + } + } + // All sessions expired + if (empty($_SESSION['browser_access_time'])) { Util::cacheUnset('is_create_db_priv'); Util::cacheUnset('is_reload_priv'); Util::cacheUnset('db_to_create'); diff --git a/test/classes/plugin/auth/AuthenticationCookieTest.php b/test/classes/plugin/auth/AuthenticationCookieTest.php index 1a37db1587..1b5bffb686 100644 --- a/test/classes/plugin/auth/AuthenticationCookieTest.php +++ b/test/classes/plugin/auth/AuthenticationCookieTest.php @@ -562,7 +562,7 @@ class AuthenticationCookieTest extends PMATestCase $GLOBALS['cfg']['blowfish_secret'] = 'secret'; $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = ''; $GLOBALS['cfg']['CaptchaLoginPublicKey'] = ''; - $_SESSION['last_access_time'] = time() - 1000; + $_SESSION['browser_access_time']['default'] = time() - 1000; $GLOBALS['cfg']['LoginCookieValidity'] = 1440; // mock for blowfish function @@ -608,7 +608,7 @@ class AuthenticationCookieTest extends PMATestCase $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = ''; $GLOBALS['cfg']['CaptchaLoginPublicKey'] = ''; $GLOBALS['cfg']['LoginCookieValidity'] = 0; - $_SESSION['last_access_time'] = -1; + $_SESSION['browser_access_time']['default'] = -1; // mock for blowfish function $this->object = $this->getMockBuilder('PMA\libraries\plugins\auth\AuthenticationCookie') ->disableOriginalConstructor()