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
*