diff --git a/app/code/Magento/Store/etc/config.xml b/app/code/Magento/Store/etc/config.xml index 62b98ea141477..49b5a4d3aa34d 100644 --- a/app/code/Magento/Store/etc/config.xml +++ b/app/code/Magento/Store/etc/config.xml @@ -84,6 +84,7 @@ 0 0 1 + 30 1 diff --git a/app/etc/di.xml b/app/etc/di.xml index a91afbae8e34a..d78d4bad2d929 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -119,6 +119,7 @@ + diff --git a/lib/internal/Magento/Framework/Session/Actualization/Storage.php b/lib/internal/Magento/Framework/Session/Actualization/Storage.php new file mode 100644 index 0000000000000..9e326324777c2 --- /dev/null +++ b/lib/internal/Magento/Framework/Session/Actualization/Storage.php @@ -0,0 +1,119 @@ +getData(self::NEW_SESSION_ID); + } + + /** + * Set actual session id. + * + * @param string $sessionId + * @return $this + */ + public function setNewSessionId($sessionId) + { + return $this->setData(self::NEW_SESSION_ID, $sessionId); + } + + /** + * Check if session storage contains info about actual session. + * + * @return bool + */ + public function hasNewSessionId() + { + return $this->hasData(self::NEW_SESSION_ID); + } + + /** + * Get old session id. + * + * @return string + */ + public function getOldSessionId() + { + return $this->getData(self::OLD_SESSION_ID); + } + + /** + * Set old session id. + * + * @param string $sessionId + * @return $this + */ + public function setOldSessionId($sessionId) + { + return $this->setData(self::OLD_SESSION_ID, $sessionId); + } + + /** + * Check if actual session contains id of old session. + * + * @return bool + */ + public function hasOldSessionId() + { + return $this->hasData(self::OLD_SESSION_ID); + } + + /** + * Remove old session id. + * + * @return $this + */ + public function unsOldSessionId() + { + return $this->unsetData(self::OLD_SESSION_ID); + } + + /** + * Get timestamp when session become deprecated. + * + * @return int + */ + public function getSessionOldTimestamp() + { + return $this->getData(self::SESSION_OLD_TIMESTAMP); + } + + /** + * Set session as deprecated. + * + * @return $this + */ + public function setSessionOldTimestamp() + { + return $this->setData(self::SESSION_OLD_TIMESTAMP, time()); + } +} diff --git a/lib/internal/Magento/Framework/Session/Actualization/StorageInterface.php b/lib/internal/Magento/Framework/Session/Actualization/StorageInterface.php new file mode 100644 index 0000000000000..0750bb82fd4f7 --- /dev/null +++ b/lib/internal/Magento/Framework/Session/Actualization/StorageInterface.php @@ -0,0 +1,87 @@ +request = $request; $this->sidResolver = $sidResolver; @@ -124,7 +133,8 @@ public function __construct( $this->cookieManager = $cookieManager; $this->cookieMetadataFactory = $cookieMetadataFactory; $this->appState = $appState; - + $this->actualizationStorage = $actualizationStorage ?: + ObjectManager::getInstance()->get('Magento\Framework\Session\Actualization\StorageInterface'); // Enable session.use_only_cookies ini_set('session.use_only_cookies', '1'); $this->start(); @@ -186,8 +196,7 @@ public function start() $sid = $this->sidResolver->getSid($this); // potential custom logic for session id (ex. switching between hosts) $this->setSessionId($sid); - session_start(); - $this->validator->validate($this); + $this->sessionStart(); $this->renewCookie($sid); register_shutdown_function([$this, 'writeClose']); @@ -200,6 +209,50 @@ public function start() return $this; } + /** + * Start session. + * + * @return void + * @throws \Magento\Framework\Exception\SessionException + * @SuppressWarnings(PHPMD.Superglobals) + */ + private function sessionStart() + { + session_start(); + $this->validator->validate($this); + $this->actualizationStorage->init(isset($_SESSION) ? $_SESSION : []); + + $forwarded = false; + while ($this->actualizationStorage->hasNewSessionId()) { + // Looks like we work with unstable network. Start more actual session. + $this->restartSession($this->actualizationStorage->getNewSessionId()); + $this->validator->validate($this); + $forwarded = true; + } + + if ($forwarded == false && $this->actualizationStorage->hasOldSessionId()) { + // New cookie was received. Proceed with old session delete. + $this->destroyOldSession(); + } + } + + /** + * Destroy old session. + * + * @return void + */ + private function destroyOldSession() + { + $currentSessionId = $this->getSessionId(); + $oldSessionId = $this->actualizationStorage->getOldSessionId(); + $this->actualizationStorage->unsOldSessionId(); + $this->restartSession($oldSessionId); + if ($currentSessionId == $this->actualizationStorage->getNewSessionId()) { + session_destroy(); + } + $this->restartSession($currentSessionId); + } + /** * Renew session cookie to prolong session. * @@ -505,7 +558,7 @@ public function regenerateId() } if ($this->isSessionExists()) { - session_regenerate_id(true); + $this->regenerateSessionId(); } else { session_start(); } @@ -517,6 +570,40 @@ public function regenerateId() return $this; } + /** + * Regenerate session id, with saving relation between two sessions. + * + * @return void + */ + protected function regenerateSessionId() + { + $oldSessionId = session_id(); + session_regenerate_id(); + $this->actualizationStorage->setOldSessionId($oldSessionId); + + $newSessionId = session_id(); + $this->restartSession($oldSessionId); + $this->actualizationStorage->setSessionOldTimestamp(); + $this->actualizationStorage->setNewSessionId($newSessionId); + + $this->restartSession($newSessionId); + } + + /** + * Restart the session with new ID. + * + * @param string $sid + * @return void + * @SuppressWarnings(PHPMD.Superglobals) + */ + protected function restartSession($sid) + { + session_commit(); + $this->setSessionId($sid); + session_start(); + $this->actualizationStorage->init(isset($_SESSION) ? $_SESSION : []); + } + /** * Expire the session cookie for sub domains * diff --git a/lib/internal/Magento/Framework/Session/Validator.php b/lib/internal/Magento/Framework/Session/Validator.php index 65d3150cdbb59..f1464bd6c3110 100644 --- a/lib/internal/Magento/Framework/Session/Validator.php +++ b/lib/internal/Magento/Framework/Session/Validator.php @@ -7,6 +7,7 @@ use Magento\Framework\Exception\SessionException; use Magento\Framework\Phrase; +use Magento\Framework\Session\Actualization\StorageInterface as ActualizationStorageInterface; /** * Session Validator @@ -31,6 +32,8 @@ class Validator implements ValidatorInterface const XML_PATH_USE_USER_AGENT = 'web/session/use_http_user_agent'; + const XML_PATH_OLD_SESSION_ACCESS_DELTA = 'web/session/old_session_access_delta'; + /** * @var \Magento\Framework\App\Config\ScopeConfigInterface */ @@ -103,6 +106,7 @@ protected function _validate() $sessionData = $_SESSION[self::VALIDATOR_KEY]; $validatorData = $this->_getSessionEnvironment(); + $this->actualizationValidation(); if ($this->_scopeConfig->getValue( self::XML_PATH_USE_REMOTE_ADDR, $this->_scopeType @@ -163,6 +167,31 @@ protected function _validate() return true; } + /** + * Validate session actualization data. + * + * @return void + * @throws SessionException + * @SuppressWarnings(PHPMD.Superglobals) + */ + protected function actualizationValidation() + { + $storageData = isset($_SESSION[ActualizationStorageInterface::STORAGE_NAMESPACE]) ? + $_SESSION[ActualizationStorageInterface::STORAGE_NAMESPACE] : []; + + if (isset($storageData[ActualizationStorageInterface::SESSION_OLD_TIMESTAMP]) && + (time() - $storageData[ActualizationStorageInterface::SESSION_OLD_TIMESTAMP]) > + $this->_scopeConfig->getValue( + self::XML_PATH_OLD_SESSION_ACCESS_DELTA, + $this->_scopeType + ) + ) { + throw new SessionException( + new Phrase('Detected attempt to access an old session.') + ); + } + } + /** * Prepare session environment data for validation *