diff options
author | diosmosis <diosmosis@users.noreply.github.com> | 2019-06-17 07:03:06 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-06-17 07:03:06 +0300 |
commit | 824204a88d80b229df3708d1edcf5416eeb2ea44 (patch) | |
tree | 52b1cfd1bcb0e5961920094a9f69517d5cbefd55 /core | |
parent | d61a9ea28a798f5598e2cb69c0ca6b3342b6c157 (diff) |
Detect expired session use (#14502)
* Add INI config option and add tests.
* Detect expired sessions.
* Update config docs.
* Apply review feedback including storing expiration in session fingerprint.
* fixing tests.
* fix unit tests
* fix test
Diffstat (limited to 'core')
-rw-r--r-- | core/Date.php | 7 | ||||
-rw-r--r-- | core/Session/SessionAuth.php | 26 | ||||
-rw-r--r-- | core/Session/SessionFingerprint.php | 52 |
3 files changed, 76 insertions, 9 deletions
diff --git a/core/Date.php b/core/Date.php index ec2a4dfd06..b91bbcd0ce 100644 --- a/core/Date.php +++ b/core/Date.php @@ -1070,7 +1070,12 @@ class Date return new Exception($message . ": $dateString"); } - private static function getNowTimestamp() + /** + * For tests. + * @return int|null + * @ignore + */ + public static function getNowTimestamp() { return isset(self::$now) ? self::$now : time(); } diff --git a/core/Session/SessionAuth.php b/core/Session/SessionAuth.php index a64f042d05..835d3a2cb4 100644 --- a/core/Session/SessionAuth.php +++ b/core/Session/SessionAuth.php @@ -88,6 +88,11 @@ class SessionAuth implements Auth $sessionFingerprint = new SessionFingerprint(); $userModel = $this->userModel; + if ($this->isExpiredSession($sessionFingerprint)) { + $sessionFingerprint->clear(); + return $this->makeAuthFailure(); + } + $userForSession = $sessionFingerprint->getUser(); if (empty($userForSession)) { return $this->makeAuthFailure(); @@ -106,9 +111,7 @@ class SessionAuth implements Auth return $this->makeAuthFailure(); } - if ($sessionFingerprint->isRemembered()) { - $this->updateSessionExpireTime(); - } + $this->updateSessionExpireTime($sessionFingerprint); return $this->makeAuthSuccess($user); } @@ -178,12 +181,27 @@ class SessionAuth implements Auth return $this->user['token_auth']; } - private function updateSessionExpireTime() + private function updateSessionExpireTime(SessionFingerprint $sessionFingerprint) { $sessionParams = session_get_cookie_params(); + // we update the session cookie to make sure expired session cookies are not available client side... $sessionCookieLifetime = Config::getInstance()->General['login_cookie_expire']; setcookie(session_name(), session_id(), time() + $sessionCookieLifetime, $sessionParams['path'], $sessionParams['domain'], $sessionParams['secure'], $sessionParams['httponly']); + + // ...and we also update the expiration time stored server side so we can prevent expired sessions from being reused + $sessionFingerprint->updateSessionExpirationTime(); + } + + private function isExpiredSession(SessionFingerprint $sessionFingerprint) + { + $expirationTime = $sessionFingerprint->getExpirationTime(); + if (empty($expirationTime)) { + return true; + } + + $isExpired = Date::now()->getTimestampUTC() > $expirationTime; + return $isExpired; } } diff --git a/core/Session/SessionFingerprint.php b/core/Session/SessionFingerprint.php index e886ef9c8c..475bf6e004 100644 --- a/core/Session/SessionFingerprint.php +++ b/core/Session/SessionFingerprint.php @@ -9,6 +9,7 @@ namespace Piwik\Session; +use Piwik\Config; use Piwik\Date; /** @@ -73,19 +74,29 @@ class SessionFingerprint public function initialize($userName, $isRemembered = false, $time = null) { + $time = $time ?: Date::now()->getTimestampUTC(); $_SESSION[self::USER_NAME_SESSION_VAR_NAME] = $userName; $_SESSION[self::SESSION_INFO_TWO_FACTOR_AUTH_VERIFIED] = 0; $_SESSION[self::SESSION_INFO_SESSION_VAR_NAME] = [ - 'ts' => $time ?: Date::now()->getTimestampUTC(), + 'ts' => $time, 'remembered' => $isRemembered, + 'expiration' => $this->getExpirationTimeFromNow($time), ]; } public function clear() { - unset($_SESSION[self::USER_NAME_SESSION_VAR_NAME]); - unset($_SESSION[self::SESSION_INFO_SESSION_VAR_NAME]); - unset($_SESSION[self::SESSION_INFO_TWO_FACTOR_AUTH_VERIFIED]); + if (isset($_SESSION[self::USER_NAME_SESSION_VAR_NAME])) { // may not be available during tests + unset($_SESSION[self::USER_NAME_SESSION_VAR_NAME]); + } + + if (isset($_SESSION[self::SESSION_INFO_SESSION_VAR_NAME])) { // may not be available during tests + unset($_SESSION[self::SESSION_INFO_SESSION_VAR_NAME]); + } + + if (isset($_SESSION[self::SESSION_INFO_TWO_FACTOR_AUTH_VERIFIED])) { // may not be available during tests + unset($_SESSION[self::SESSION_INFO_TWO_FACTOR_AUTH_VERIFIED]); + } } public function getSessionStartTime() @@ -100,9 +111,42 @@ class SessionFingerprint return $userInfo['ts']; } + public function getExpirationTime() + { + $userInfo = $this->getUserInfo(); + if (empty($userInfo) + || empty($userInfo['expiration']) + ) { + return null; + } + + return $userInfo['expiration']; + } + public function isRemembered() { $userInfo = $this->getUserInfo(); return !empty($userInfo['remembered']); } + + public function updateSessionExpirationTime() + { + $_SESSION[self::SESSION_INFO_SESSION_VAR_NAME]['expiration'] = $this->getExpirationTimeFromNow(); + } + + private function getExpirationTimeFromNow($time = null) + { + $time = $time ?: Date::now()->getTimestampUTC(); + + $nonRememberedSessionExpireTime = Config::getInstance()->General['login_session_not_remembered_idle_timeout']; + $sessionCookieLifetime = Config::getInstance()->General['login_cookie_expire']; + + if ($this->isRemembered()) { + $expireDuration = $sessionCookieLifetime; + } else { + $expireDuration = $nonRememberedSessionExpireTime; + } + + return $time + $expireDuration; + } } |