diff --git a/Model/Config/Source/SameSite.php b/Model/Config/Source/SameSite.php new file mode 100644 index 0000000..48a3d7b --- /dev/null +++ b/Model/Config/Source/SameSite.php @@ -0,0 +1,17 @@ + 'Lax','label' => __('Lax')], + ['value' => 'Strict','label' => __('Strict')], + ['value' => 'None','label' => __('None')] + ]; + } +} diff --git a/Plugin/Session/AddSameSite.php b/Plugin/Session/AddSameSite.php deleted file mode 100644 index 2e1ed66..0000000 --- a/Plugin/Session/AddSameSite.php +++ /dev/null @@ -1,64 +0,0 @@ -validator = $validator; - $this->header = $header; - } - - /** - * @param \Magento\Framework\Session\Config $subject - * @param $result - * @param string $cookiePath - * @param string|null $default - */ - public function afterSetCookiePath( - Config $subject, - $result, - $cookiePath, - $default = null - ) { - $version = PHP_VERSION_ID; - $agent = $this->header->getHttpUserAgent(); - $sameSite = $this->validator->shouldSendSameSiteNone($agent); - - if (!$sameSite) { - return $result; - } - - if ($version >= 70300) { - $subject->setOption('session.cookie_samesite', 'None'); - } else { - $path = $subject->getCookiePath(); - if (!preg_match('/SameSite/', $path)) { - $path .= '; SameSite=None'; - $subject->setOption('session.cookie_path', $path); - } - } - - return $result; - } -} diff --git a/Plugin/SwitchSameSite.php b/Plugin/SwitchSameSite.php new file mode 100644 index 0000000..f03bcbb --- /dev/null +++ b/Plugin/SwitchSameSite.php @@ -0,0 +1,91 @@ +validator = $validator; + $this->header = $header; + $this->scopeConfig = $scopeConfig; + } + + /** + * @param PhpCookieManager $subject + * @param string $name + * @param string $value + * @param PublicCookieMetadata|null $metadata + * @return array + */ + public function beforeSetPublicCookie( + PhpCookieManager $subject, + $name, + $value, + PublicCookieMetadata $metadata = null + ) { + if ($this->isAffectedKeys($name)) { + $agent = $this->header->getHttpUserAgent(); + $sameSite = $this->validator->shouldSendSameSiteNone($agent); + if ($sameSite === false) { + $metadata + ->setSecure(true) + ->setSameSite('None'); + } else { + $config = $this->scopeConfig->getValue(self::CONFIG_PATH, ScopeInterface::SCOPE_STORE); + + // Convert to lowercase since sometimes it comes as lower-cased string + if(strtolower($config) === 'none') + { + $metadata->setSecure(true); + } + $metadata->setSameSite($config); + } + } + + return [$name, $value, $metadata]; + } + + private function isAffectedKeys($name) + { + if (!count($this->affectedKeys)) { + $affectedKeys = $this->scopeConfig->getValue(self::CONFIG_AFFECTED_KEYS, ScopeInterface::SCOPE_STORE); + $this->affectedKeys = explode(',', strtolower($affectedKeys)); + } + + return in_array(strtolower($name), $this->affectedKeys); + } +} diff --git a/Plugin/View/Element/Js/AdjustPath.php b/Plugin/View/Element/Js/AdjustPath.php deleted file mode 100644 index 3b64712..0000000 --- a/Plugin/View/Element/Js/AdjustPath.php +++ /dev/null @@ -1,21 +0,0 @@ - configuration > web" and open cookie section. You can see "SameSite" field. +By default, this extension sets SameSite configuration value to limited cookies. If you hope to update the list, please update web/cookie/affected_keys configuration value. # Support diff --git a/Rewrite/Stdlib/Cookie/PublicCookieMetadata.php b/Rewrite/Stdlib/Cookie/PublicCookieMetadata.php deleted file mode 100644 index 473a7d2..0000000 --- a/Rewrite/Stdlib/Cookie/PublicCookieMetadata.php +++ /dev/null @@ -1,24 +0,0 @@ -set(self::KEY_SAMESITE, $sameSite); - } -} diff --git a/Stdlib/Cookie/CookieManager.php b/Stdlib/Cookie/CookieManager.php deleted file mode 100644 index e77eb19..0000000 --- a/Stdlib/Cookie/CookieManager.php +++ /dev/null @@ -1,359 +0,0 @@ -scope = $scope; - $this->reader = $reader; - $this->logger = $logger ?: ObjectManager::getInstance()->get(LoggerInterface::class); - $this->httpHeader = $httpHeader ?: ObjectManager::getInstance()->get(HttpHeader::class); - $this->validator = $validator; - } - - /** - * Set a value in a private cookie with the given $name $value pairing. - * - * Sensitive cookies cannot be accessed by JS. HttpOnly will always be set to true for these cookies. - * - * @param string $name - * @param string $value - * @param SensitiveCookieMetadata $metadata - * @return void - * @throws FailureToSendException Cookie couldn't be sent to the browser. If this exception isn't thrown, - * there is still no guarantee that the browser received and accepted the cookie. - * @throws CookieSizeLimitReachedException Thrown when the cookie is too big to store any additional data. - * @throws InputException If the cookie name is empty or contains invalid characters. - */ - public function setSensitiveCookie($name, $value, SensitiveCookieMetadata $metadata = null) - { - $metadataArray = $this->scope->getSensitiveCookieMetadata($metadata)->__toArray(); - $this->setCookie($name, $value, $metadataArray); - } - - /** - * Set a value in a public cookie with the given $name $value pairing. - * - * Public cookies can be accessed by JS. HttpOnly will be set to false by default for these cookies, - * but can be changed to true. - * - * @param string $name - * @param string $value - * @param PublicCookieMetadata $metadata - * @return void - * @throws FailureToSendException If cookie couldn't be sent to the browser. - * @throws CookieSizeLimitReachedException Thrown when the cookie is too big to store any additional data. - * @throws InputException If the cookie name is empty or contains invalid characters. - */ - public function setPublicCookie($name, $value, PublicCookieMetadata $metadata = null) - { - $metadataArray = $this->scope->getPublicCookieMetadata($metadata)->__toArray(); - $this->setCookie($name, $value, $metadataArray); - } - - /** - * Set a value in a cookie with the given $name $value pairing. - * - * @param string $name - * @param string $value - * @param array $metadataArray - * @return void - * @throws FailureToSendException If cookie couldn't be sent to the browser. - * @throws CookieSizeLimitReachedException Thrown when the cookie is too big to store any additional data. - * @throws InputException If the cookie name is empty or contains invalid characters. - */ - protected function setCookie($name, $value, array $metadataArray) - { - $expire = $this->computeExpirationTime($metadataArray); - - $this->checkAbilityToSendCookie($name, $value); - $userAgent = $this->httpHeader->getHttpUserAgent(); - $sameSite = $this->validator->shouldSendSameSiteNone($userAgent); - - $version = PHP_VERSION_ID; - if ($version >= 70300) { - $options = [ - self::KEY_EXPIRES => $expire, - self::KEY_PATH => $this->extractValue(CookieMetadata::KEY_PATH, $metadataArray, ''), - self::KEY_DOMAIN => $this->extractValue(CookieMetadata::KEY_DOMAIN, $metadataArray, ''), - self::KEY_SECURE => $this->extractValue(CookieMetadata::KEY_SECURE, $metadataArray, true), - self::KEY_HTTP_ONLY => $this->extractValue(CookieMetadata::KEY_HTTP_ONLY, $metadataArray, false) - ]; - - if (array_key_exists(ExtendPulicCookieMetadata::KEY_SAMESITE, $metadataArray)) { - $options = array_merge($options, [self::KEY_SAME_SITE => $metadataArray[ExtendPulicCookieMetadata::KEY_SAMESITE]]); - } elseif ($sameSite) { - $options = array_merge($options, [self::KEY_SAME_SITE => 'None']); - } - - $phpSetcookieSuccess = setcookie( - $name, - $value, - $options - ); - } else { - $path = $this->extractValue(CookieMetadata::KEY_PATH, $metadataArray, ''); - if (array_key_exists(ExtendPulicCookieMetadata::KEY_SAMESITE, $metadataArray)) { - $path .= '; SameSite=' . $metadataArray[ExtendPulicCookieMetadata::KEY_SAMESITE]; - } elseif ($sameSite && !preg_match('/SameSite/', $path)) { - $path .= '; SameSite=None'; - } - - $phpSetcookieSuccess = setcookie( - $name, - $value, - $expire, - $path, - $this->extractValue(CookieMetadata::KEY_DOMAIN, $metadataArray, ''), - $this->extractValue(CookieMetadata::KEY_SECURE, $metadataArray, true), - $this->extractValue(CookieMetadata::KEY_HTTP_ONLY, $metadataArray, false) - ); - } - - if (!$phpSetcookieSuccess) { - $params['name'] = $name; - if ($value == '') { - throw new FailureToSendException( - new Phrase('The cookie with "%name" cookieName couldn\'t be deleted.', $params) - ); - } else { - throw new FailureToSendException( - new Phrase('The cookie with "%name" cookieName couldn\'t be sent. Please try again later.', $params) - ); - } - } - } - - /** - * Retrieve the size of a cookie. - * The size of a cookie is determined by the length of 'name=value' portion of the cookie. - * - * @param string $name - * @param string $value - * @return int - */ - private function sizeOfCookie($name, $value) - { - // The constant '1' is the length of the equal sign in 'name=value'. - return strlen($name) + 1 + strlen($value); - } - - /** - * Determines whether or not it is possible to send the cookie, based on the number of cookies that already - * exist and the size of the cookie. - * - * @param string $name - * @param string|null $value - * @return void if it is possible to send the cookie - * @throws CookieSizeLimitReachedException Thrown when the cookie is too big to store any additional data. - * @throws InputException If the cookie name is empty or contains invalid characters. - */ - private function checkAbilityToSendCookie($name, $value) - { - if ($name == '' || preg_match("/[=,; \t\r\n\013\014]/", $name)) { - throw new InputException( - new Phrase( - 'Cookie name cannot be empty and cannot contain these characters: =,; \\t\\r\\n\\013\\014' - ) - ); - } - - $numCookies = count($_COOKIE); - - if (!isset($_COOKIE[$name])) { - $numCookies++; - } - - $sizeOfCookie = $this->sizeOfCookie($name, $value); - - if ($numCookies > static::MAX_NUM_COOKIES) { - $this->logger->warning( - new Phrase('Unable to send the cookie. Maximum number of cookies would be exceeded.'), - array_merge($_COOKIE, ['user-agent' => $this->httpHeader->getHttpUserAgent()]) - ); - } - - if ($sizeOfCookie > static::MAX_COOKIE_SIZE) { - throw new CookieSizeLimitReachedException( - new Phrase( - 'Unable to send the cookie. Size of \'%name\' is %size bytes.', - [ - 'name' => $name, - 'size' => $sizeOfCookie, - ] - ) - ); - } - } - - /** - * Determines the expiration time of a cookie. - * - * @param array $metadataArray - * @return int in seconds since the Unix epoch. - */ - private function computeExpirationTime(array $metadataArray) - { - if (isset($metadataArray[PhpCookieManager::KEY_EXPIRE_TIME]) - && $metadataArray[PhpCookieManager::KEY_EXPIRE_TIME] < time() - ) { - $expireTime = $metadataArray[PhpCookieManager::KEY_EXPIRE_TIME]; - } else { - if (isset($metadataArray[CookieMetadata::KEY_DURATION])) { - $expireTime = $metadataArray[CookieMetadata::KEY_DURATION] + time(); - } else { - $expireTime = PhpCookieManager::EXPIRE_AT_END_OF_SESSION_TIME; - } - } - - return $expireTime; - } - - /** - * Determines the value to be used as a $parameter. - * If $metadataArray[$parameter] is not set, returns the $defaultValue. - * - * @param string $parameter - * @param array $metadataArray - * @param string|boolean|int|null $defaultValue - * @return string|boolean|int|null - */ - private function extractValue($parameter, array $metadataArray, $defaultValue) - { - if (array_key_exists($parameter, $metadataArray)) { - return $metadataArray[$parameter]; - } else { - return $defaultValue; - } - } - - /** - * Retrieve a value from a cookie. - * - * @param string $name - * @param string|null $default The default value to return if no value could be found for the given $name. - * @return string|null - */ - public function getCookie($name, $default = null) - { - return $this->reader->getCookie($name, $default); - } - - /** - * Deletes a cookie with the given name. - * - * @param string $name - * @param CookieMetadata $metadata - * @return void - * @throws FailureToSendException If cookie couldn't be sent to the browser. - * If this exception isn't thrown, there is still no guarantee that the browser - * received and accepted the request to delete this cookie. - * @throws InputException If the cookie name is empty or contains invalid characters. - */ - public function deleteCookie($name, CookieMetadata $metadata = null) - { - $metadataArray = $this->scope->getCookieMetadata($metadata)->__toArray(); - - // explicitly set an expiration time in the metadataArray. - $metadataArray[PhpCookieManager::KEY_EXPIRE_TIME] = PhpCookieManager::EXPIRE_NOW_TIME; - - $this->checkAbilityToSendCookie($name, ''); - - // cookie value set to empty string to delete from the remote client - $this->setCookie($name, '', $metadataArray); - - // Remove the cookie - unset($_COOKIE[$name]); - } -} diff --git a/composer.json b/composer.json index a53fe46..1f9d647 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "AFL-3.0" ], "description":"Magento2 extension for Cookie SameSite attribute.", - "version":"2.4.0", + "version":"3.0.2", "authors":[ { "name":"Hirokazu Nishi", @@ -16,8 +16,8 @@ } ], "require": { - "php": "~7.2.0||~7.3.0||~7.4.0", - "magento/framework": "~102.0.0||~103.0.0", + "php": "~7.3.0||~7.4.0||~8.1.0", + "magento/framework": "~102.0.6-p1||~103.0.1", "lib-libxml": "*" }, "autoload": { @@ -28,4 +28,4 @@ "Veriteworks\\CookieFix\\": "" } } -} \ No newline at end of file +} diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml new file mode 100644 index 0000000..615321b --- /dev/null +++ b/etc/adminhtml/system.xml @@ -0,0 +1,14 @@ + + + + +
+ + + + Veriteworks\CookieFix\Model\Config\Source\SameSite + + +
+
+
diff --git a/etc/config.xml b/etc/config.xml new file mode 100644 index 0000000..e1fdb66 --- /dev/null +++ b/etc/config.xml @@ -0,0 +1,11 @@ + + + + + + Lax + PHPSESSID,form_key,private_content_version,X-Magento-Vary + + + + diff --git a/etc/di.xml b/etc/di.xml deleted file mode 100644 index d8ed26a..0000000 --- a/etc/di.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index 8dda2f2..c53c48c 100644 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -1,14 +1,9 @@ - - - - - + diff --git a/etc/webapi_rest/di.xml b/etc/webapi_rest/di.xml new file mode 100644 index 0000000..c53c48c --- /dev/null +++ b/etc/webapi_rest/di.xml @@ -0,0 +1,9 @@ + + + + + +