diff --git a/composer.json b/composer.json index 3b07fef484df..5b219bf535c5 100644 --- a/composer.json +++ b/composer.json @@ -48,6 +48,7 @@ "swiftmailer/swiftmailer": "~5.4.5", "symfony/console": "^2.7 || ^3.0 || ^4.0", "symfony/finder": "^2.7 || ^3.0 || ^4.0", + "symfony/http-foundation": "^3.4 || ^4.2", "symfony/polyfill-mbstring": "^1.2", "symfony/yaml": "^2.7 || ^3.0 || ^4.0", "typo3/class-alias-loader": "^1.0", diff --git a/composer.lock b/composer.lock index 09c13a9c2bcb..5c91e6b3966e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "443f9b14d81e5a20ee0ca8badbadcc59", + "content-hash": "58891391ba9f5205e4572c948c4bdfb4", "packages": [ { "name": "algo26-matthias/idna-convert", @@ -813,6 +813,51 @@ ], "time": "2018-12-04T20:46:45+00:00" }, + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "time": "2018-07-02T15:55:56+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -1181,6 +1226,60 @@ "homepage": "https://symfony.com", "time": "2019-11-17T21:55:15+00:00" }, + { + "name": "symfony/http-foundation", + "version": "v3.4.37", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "f3abd07a56111ebe6a1ad6f1cbc23e4f8983f8f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f3abd07a56111ebe6a1ad6f1cbc23e4f8983f8f5", + "reference": "f3abd07a56111ebe6a1ad6f1cbc23e4f8983f8f5", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php70": "~1.6" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2020-01-04T12:05:51+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.13.1", @@ -1298,6 +1397,65 @@ ], "time": "2019-11-27T14:18:11+00:00" }, + { + "name": "symfony/polyfill-php70", + "version": "v1.13.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "af23c7bb26a73b850840823662dda371484926c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/af23c7bb26a73b850840823662dda371484926c4", + "reference": "af23c7bb26a73b850840823662dda371484926c4", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0|~9.99", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.13-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-11-27T13:56:44+00:00" + }, { "name": "symfony/yaml", "version": "v3.4.36", @@ -1989,6 +2147,7 @@ "selenium", "webdriver" ], + "abandoned": "php-webdriver/webdriver", "time": "2017-01-13T15:48:08+00:00" }, { @@ -2264,51 +2423,6 @@ ], "time": "2017-04-12T18:52:22+00:00" }, - { - "name": "paragonie/random_compat", - "version": "v9.99.99", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", - "shasum": "" - }, - "require": { - "php": "^7" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*", - "vimeo/psalm": "^1" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" - ], - "time": "2018-07-02T15:55:56+00:00" - }, { "name": "phar-io/manifest", "version": "1.0.1", @@ -4002,65 +4116,6 @@ ], "time": "2019-10-26T11:02:01+00:00" }, - { - "name": "symfony/polyfill-php70", - "version": "v1.13.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "af23c7bb26a73b850840823662dda371484926c4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/af23c7bb26a73b850840823662dda371484926c4", - "reference": "af23c7bb26a73b850840823662dda371484926c4", - "shasum": "" - }, - "require": { - "paragonie/random_compat": "~1.0|~2.0|~9.99", - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.13-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php70\\": "" - }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "time": "2019-11-27T13:56:44+00:00" - }, { "name": "symfony/polyfill-php72", "version": "v1.13.1", diff --git a/typo3/sysext/backend/Classes/Controller/LoginController.php b/typo3/sysext/backend/Classes/Controller/LoginController.php index fb682dad5f08..13adbfddf14b 100644 --- a/typo3/sysext/backend/Classes/Controller/LoginController.php +++ b/typo3/sysext/backend/Classes/Controller/LoginController.php @@ -16,6 +16,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Symfony\Component\HttpFoundation\Cookie; use TYPO3\CMS\Backend\Exception; use TYPO3\CMS\Backend\LoginProvider\LoginProviderInterface; use TYPO3\CMS\Backend\Utility\BackendUtility; @@ -91,8 +92,6 @@ public function __construct() { $this->validateAndSortLoginProviders(); - // We need a PHP session session for most login levels - session_start(); $this->redirectUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('redirect_url')); $this->loginProviderIdentifier = $this->detectLoginProvider(); @@ -492,7 +491,19 @@ protected function detectLoginProvider() } // Use the secure option when the current request is served by a secure connection: $cookieSecure = (bool)$GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieSecure'] && GeneralUtility::getIndpEnv('TYPO3_SSL'); - setcookie('be_lastLoginProvider', $loginProvider, $GLOBALS['EXEC_TIME'] + 7776000, null, null, $cookieSecure, true); // 90 days + $cookie = new Cookie( + 'be_lastLoginProvider', + (string)$loginProvider, + $GLOBALS['EXEC_TIME'] + 7776000, // 90 days + GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') . TYPO3_mainDir, + '', + $cookieSecure, + true, + false, + Cookie::SAMESITE_STRICT + ); + header('Set-Cookie: ' . $cookie->__toString(), false); + return $loginProvider; } diff --git a/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php b/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php index 2cdee16084de..95caade59b45 100644 --- a/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php +++ b/typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php @@ -14,6 +14,7 @@ * The TYPO3 project - inspiring people to share! */ +use Symfony\Component\HttpFoundation\Cookie; use TYPO3\CMS\Core\Crypto\Random; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; @@ -26,6 +27,7 @@ use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction; use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction; use TYPO3\CMS\Core\Exception; +use TYPO3\CMS\Core\Http\CookieHeaderTrait; use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException; use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface; use TYPO3\CMS\Core\Session\SessionManager; @@ -44,6 +46,8 @@ */ abstract class AbstractUserAuthentication { + use CookieHeaderTrait; + /** * Session/Cookie name * @var string @@ -483,9 +487,28 @@ protected function setSessionCookie() $cookieExpire = $isRefreshTimeBasedCookie ? $GLOBALS['EXEC_TIME'] + $this->lifetime : 0; // Use the secure option when the current request is served by a secure connection: $cookieSecure = (bool)$settings['cookieSecure'] && GeneralUtility::getIndpEnv('TYPO3_SSL'); + // Valid options are "strict", "lax" or "none", whereas "none" only works in HTTPS requests (default & fallback is "strict") + $cookieSameSite = $this->sanitizeSameSiteCookieValue( + strtolower($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieSameSite'] ?? Cookie::SAMESITE_STRICT) + ); + // SameSite "none" needs the secure option (only allowed on HTTPS) + if ($cookieSameSite === Cookie::SAMESITE_NONE) { + $cookieSecure = true; + } // Do not set cookie if cookieSecure is set to "1" (force HTTPS) and no secure channel is used: if ((int)$settings['cookieSecure'] !== 1 || GeneralUtility::getIndpEnv('TYPO3_SSL')) { - setcookie($this->name, $this->id, $cookieExpire, $cookiePath, $cookieDomain, $cookieSecure, true); + $cookie = new Cookie( + $this->name, + $this->id, + $cookieExpire, + $cookiePath, + $cookieDomain, + $cookieSecure, + true, + false, + $cookieSameSite + ); + header('Set-Cookie: ' . $cookie->__toString(), false); $this->cookieWasSetOnCurrentRequest = true; } else { throw new Exception('Cookie was not set since HTTPS was forced in $TYPO3_CONF_VARS[SYS][cookieSecure].', 1254325546); diff --git a/typo3/sysext/core/Classes/Http/CookieHeaderTrait.php b/typo3/sysext/core/Classes/Http/CookieHeaderTrait.php new file mode 100644 index 000000000000..da00e29d6799 --- /dev/null +++ b/typo3/sysext/core/Classes/Http/CookieHeaderTrait.php @@ -0,0 +1,75 @@ +='); + } + + /** + * Since PHP < 7.3 is not capable of sending the same-site cookie information, session_start() effectively + * sends the Set-Cookie header. This method fetches the set-cookie headers, parses it via Symfony's Cookie + * object, and resends the header. + * + * @param string[] $cookieNames + */ + private function resendCookieHeader(array $cookieNames = []) + { + $cookies = array_filter(headers_list(), function (string $header) { + return stripos($header, 'Set-Cookie:') === 0; + }); + $cookies = array_map(function (string $cookieHeader) use ($cookieNames) { + $payload = ltrim(substr($cookieHeader, 11)); + $cookie = Cookie::fromString($payload); + $sameSite = $cookie->getSameSite(); + // adjust SameSite flag only for given cookie names (applied to all if not declared) + if (empty($cookieNames) || in_array($cookie->getName(), $cookieNames, true)) { + $sameSite = $sameSite ?? Cookie::SAMESITE_STRICT; + } + $cookie = new Cookie( + $cookie->getName(), + $cookie->getValue(), + $cookie->getExpiresTime(), + $cookie->getPath(), + $cookie->getDomain(), + $cookie->isSecure(), + $cookie->isHttpOnly(), + $cookie->isRaw(), + $sameSite + ); + return (string)$cookie; + }, $cookies); + if (!empty($cookies)) { + header_remove('Set-Cookie'); + foreach ($cookies as $cookie) { + header('Set-Cookie: ' . $cookie, false); + } + } + } + + private function sanitizeSameSiteCookieValue(string $cookieSameSite): string + { + if (!in_array($cookieSameSite, [Cookie::SAMESITE_STRICT, Cookie::SAMESITE_LAX, Cookie::SAMESITE_NONE], true)) { + $cookieSameSite = Cookie::SAMESITE_STRICT; + } + return $cookieSameSite; + } +} diff --git a/typo3/sysext/core/Configuration/DefaultConfiguration.php b/typo3/sysext/core/Configuration/DefaultConfiguration.php index ad8898ab2f73..4d3d98a7b0c7 100644 --- a/typo3/sysext/core/Configuration/DefaultConfiguration.php +++ b/typo3/sysext/core/Configuration/DefaultConfiguration.php @@ -867,6 +867,7 @@ 'enabledBeUserIPLock' => true, 'cookieDomain' => '', 'cookieName' => 'be_typo_user', + 'cookieSameSite' => 'strict', 'loginSecurityLevel' => '', 'showRefreshLoginPopup' => false, 'adminOnly' => 0, @@ -996,6 +997,7 @@ 'permalogin' => 0, 'cookieDomain' => '', 'cookieName' => 'fe_typo_user', + 'cookieSameSite' => 'lax', 'defaultUserTSconfig' => '', 'defaultTypoScript_constants' => '', 'defaultTypoScript_constants.' => [], // Lines of TS to include after a static template with the uid = the index in the array (Constants) diff --git a/typo3/sysext/core/Configuration/DefaultConfigurationDescription.php b/typo3/sysext/core/Configuration/DefaultConfigurationDescription.php index fc3c6d9d51a3..19b4b8a69605 100644 --- a/typo3/sysext/core/Configuration/DefaultConfigurationDescription.php +++ b/typo3/sysext/core/Configuration/DefaultConfigurationDescription.php @@ -113,6 +113,7 @@ 'enabledBeUserIPLock' => 'Boolean: If set, the User/Group TSconfig option option.lockToIP is enabled.', 'cookieDomain' => 'Same as $TYPO3_CONF_VARS[\'SYS\'][\'cookieDomain\'] but only for BE cookies. If empty, $TYPO3_CONF_VARS[\'SYS\'][\'cookieDomain\'] value will be used.', 'cookieName' => 'String: Set the name for the cookie used for the back-end user session', + 'cookieSameSite' => 'Indicates that the cookie should send proper information where the cookie can be shared (first-party cookies vs. third-party cookies) in TYPO3 Backend. Allowed values are "strict", "lax" or "none"', 'loginSecurityLevel' => 'String: Keywords that determines the security level of login to the backend. "normal" means the password from the login form is sent in clear-text, "rsa" uses RSA password encryption (only if the rsaauth extension is installed).', 'showRefreshLoginPopup' => 'Boolean: If set, the Ajax relogin will show a real popup window for relogin after the count down. Some auth services need this as they add custom validation to the login form. If it\'s not set, the Ajax relogin will show an inline relogin window.', 'adminOnly' => '

Integer (-1, 0, 1, 2)

-1
total shutdown for maintenance purposes
0
normal operation, everyone can login (default)
1
only admins can login (no CLI)
2
only admins and regular CLI users can login
', @@ -152,6 +153,7 @@ 'permalogin' => '
-1
Permanent login for FE users is disabled.
0
By default permalogin is disabled for FE users but can be enabled by a form control in the login form.
1
Permanent login is by default enabled but can be disabled by a form control in the login form.
2
Permanent login is forced to be enabled.
In any case, permanent login is only possible if [FE][lifetime] lifetime is > 0.', 'cookieDomain' => 'Same as $TYPO3_CONF_VARS[\'SYS\'][\'cookieDomain\'] but only for FE cookies. If empty, $TYPO3_CONF_VARS[\'SYS\'][\'cookieDomain\'] value will be used.', 'cookieName' => 'String: Set the name for the cookie used for the front-end user session', + 'cookieSameSite' => 'Indicates that the cookie should send proper information where the cookie can be shared (first-party cookies vs. third-party cookies) in TYPO3 Frontend Sessions. Allowed values are "strict", "lax" or "none"', 'defaultUserTSconfig' => 'String (textarea). Enter lines of default frontend user/group TSconfig.', 'defaultTypoScript_constants' => 'String (textarea). Enter lines of default TypoScript, constants-field.', 'defaultTypoScript_setup' => 'String (textarea). Enter lines of default TypoScript, setup-field.', diff --git a/typo3/sysext/core/Documentation/Changelog/8.7.x/Feature-90351-ConfigureTYPO3-shippedCookiesWithSameSiteFlag.rst b/typo3/sysext/core/Documentation/Changelog/8.7.x/Feature-90351-ConfigureTYPO3-shippedCookiesWithSameSiteFlag.rst new file mode 100644 index 000000000000..0608816662e6 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/8.7.x/Feature-90351-ConfigureTYPO3-shippedCookiesWithSameSiteFlag.rst @@ -0,0 +1,60 @@ +.. include:: ../../Includes.txt + +==================================================================== +Feature: #90351 - Configure TYPO3-shipped cookies with SameSite flag +==================================================================== + +See :issue:`90351` + +Description +=========== + +TYPO3 Core sends four cookies set by PHP to the browser when a session is requested: + +- fe_typo_user - used to identify a session ID when logged-in to the TYPO3 Frontend +- be_typo_user - used to identify a backend session when a Backend User logged in to TYPO3 Backend or Frontend +- Typo3InstallTool - used to validate a session for the System Maintenance Area / "Install Tool" +- be_lastLoginProvider - stores information about the last login provider when logging into TYPO3 Backend + +All modern wide-spread browsers (Mozilla Firefox, Chromium-based Browsers such as Google Chrome, Safari, Microsoft Edge) support sending cookies with an additional flag called "SameSite" which +defines the visibility of a cookie when used in other scripts or +iframes such as a YouTube video embedded into a site. The same site +flag defines whether to send such information to these "third-party +sites". + +Starting with Google Chrome 80 (expected in February 2020), the browser treats any cookie without having the SameSite flag sent to +be the same as "lax". + +TYPO3 now supports the configuration of this cookie for Frontend- +and Backend users. For the install Tool and lastLoginProvider +the cookies are now always sent with the "strict" flag set. + +SameSite enhances privacy for every visitor or editor of your +TYPO3 installation. + +Read more about SameSite cookies on: https://web.dev/samesite-cookies-explained/ + + +Impact +====== + +All cookies sent by TYPO3 Core now send the SameSite flag by default, whereas TYPO3 Frontend sends the SameSite flag "lax", +and all other cookies are sent via "strict". + +The cookies for Frontend User Sessions can be configured via +`$GLOBALS[TYPO3_CONF_VARS][FE][cookieSameSite]` to be either +"strict", "lax" or "none". + +The cookies for Backend User Sessions can be configured via +`$GLOBALS[TYPO3_CONF_VARS][BE][cookieSameSite]` to be either +"strict", "lax" or "none". + +Please note that "none" only works when running the site via HTTPS. + +Older browsers without SameSite support do not consider evaluating +the SameSite flag will behave as before. + +Both settings can be configured in the Install Tool / Maintenance +Area Settings module. + +.. index:: LocalConfiguration, ext:core \ No newline at end of file diff --git a/typo3/sysext/core/composer.json b/typo3/sysext/core/composer.json index 315246009d60..005ab32f90d9 100644 --- a/typo3/sysext/core/composer.json +++ b/typo3/sysext/core/composer.json @@ -30,6 +30,7 @@ "swiftmailer/swiftmailer": "~5.4.5", "symfony/console": "^2.7 || ^3.0 || ^4.0", "symfony/finder": "^2.7 || ^3.0 || ^4.0", + "symfony/http-foundation": "^3.4 || ^4.2", "symfony/polyfill-mbstring": "^1.2", "symfony/yaml": "^2.7 || ^3.0 || ^4.0", "typo3/class-alias-loader": "^1.0", diff --git a/typo3/sysext/install/Classes/Service/SessionService.php b/typo3/sysext/install/Classes/Service/SessionService.php index d8f2d5c7ae15..66c2f6ab4cde 100644 --- a/typo3/sysext/install/Classes/Service/SessionService.php +++ b/typo3/sysext/install/Classes/Service/SessionService.php @@ -14,6 +14,8 @@ * The TYPO3 project - inspiring people to share! */ +use Symfony\Component\HttpFoundation\Cookie; +use TYPO3\CMS\Core\Http\CookieHeaderTrait; use TYPO3\CMS\Core\Utility\GeneralUtility; /** @@ -21,6 +23,8 @@ */ class SessionService implements \TYPO3\CMS\Core\SingletonInterface { + use CookieHeaderTrait; + /** * The path to our typo3temp/var/ (where we can write our sessions). Set in the * constructor. @@ -73,6 +77,9 @@ public function __construct() session_save_path($sessionSavePath); session_name($this->cookieName); ini_set('session.cookie_httponly', true); + if ($this->hasSameSiteCookieSupport()) { + ini_set('session.cookie_samesite', Cookie::SAMESITE_STRICT); + } ini_set('session.cookie_path', GeneralUtility::getIndpEnv('TYPO3_SITE_PATH')); // Always call the garbage collector to clean up stale session files ini_set('session.gc_probability', 100); @@ -90,6 +97,9 @@ public function __construct() throw new \TYPO3\CMS\Install\Exception($sessionCreationError, 1294587486); } session_start(); + if (!$this->hasSameSiteCookieSupport()) { + $this->resendCookieHeader([$this->cookieName]); + } } /** @@ -190,6 +200,9 @@ public function resetSession() private function renewSession() { session_regenerate_id(); + if (!$this->hasSameSiteCookieSupport()) { + $this->resendCookieHeader([$this->cookieName]); + } return session_id(); } diff --git a/typo3/sysext/rsaauth/Classes/Storage/SplitStorage.php b/typo3/sysext/rsaauth/Classes/Storage/SplitStorage.php index 1fe0306ec7eb..78975785cabb 100644 --- a/typo3/sysext/rsaauth/Classes/Storage/SplitStorage.php +++ b/typo3/sysext/rsaauth/Classes/Storage/SplitStorage.php @@ -14,7 +14,9 @@ * The TYPO3 project - inspiring people to share! */ +use Symfony\Component\HttpFoundation\Cookie; use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Http\CookieHeaderTrait; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; @@ -24,6 +26,8 @@ */ class SplitStorage extends AbstractStorage { + use CookieHeaderTrait; + /** * Creates an instance of this class. It checks and initializes PHP * sessions if necessary. @@ -31,7 +35,17 @@ class SplitStorage extends AbstractStorage public function __construct() { if (session_id() === '') { - session_start(); + $options = [ + 'cookie_httponly' => true, + 'cookie_secure' => GeneralUtility::getIndpEnv('TYPO3_SSL'), + ]; + if ($this->hasSameSiteCookieSupport()) { + $options['cookie_samesite'] = Cookie::SAMESITE_STRICT; + } + session_start($options); + if (!$this->hasSameSiteCookieSupport()) { + $this->resendCookieHeader([session_name()]); + } } } @@ -88,7 +102,7 @@ public function put($key) setcookie( $sessionName, false, - $sessionCookie['lifetime'], + -1, $sessionCookie['path'], $sessionCookie['domain'], $sessionCookie['secure'] diff --git a/typo3/sysext/workspaces/Classes/Hook/PreviewHook.php b/typo3/sysext/workspaces/Classes/Hook/PreviewHook.php index 17d94cca4ed2..5128a644b304 100644 --- a/typo3/sysext/workspaces/Classes/Hook/PreviewHook.php +++ b/typo3/sysext/workspaces/Classes/Hook/PreviewHook.php @@ -14,10 +14,12 @@ * The TYPO3 project - inspiring people to share! */ +use Symfony\Component\HttpFoundation\Cookie; use TYPO3\CMS\Backend\FrontendBackendUserAuthentication; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction; +use TYPO3\CMS\Core\Http\CookieHeaderTrait; use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; @@ -28,6 +30,8 @@ */ class PreviewHook implements \TYPO3\CMS\Core\SingletonInterface { + use CookieHeaderTrait; + /** * the GET parameter to be used * @@ -302,7 +306,24 @@ public function getPreviewConfiguration() if (GeneralUtility::_GP($this->previewKey)) { // Lifetime is 1 hour, does it matter much? // Requires the user to click the link from their email again if it expires. - setcookie($this->previewKey, GeneralUtility::_GP($this->previewKey), 0, GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'), null, null, true); + $cookieSameSite = $this->sanitizeSameSiteCookieValue( + strtolower($GLOBALS['TYPO3_CONF_VARS']['BE']['cookieSameSite'] ?? Cookie::SAMESITE_STRICT) + ); + // None needs the secure option (only allowed on HTTPS) + $cookieSecure = $cookieSameSite === Cookie::SAMESITE_NONE || GeneralUtility::getIndpEnv('TYPO3_SSL'); + + $cookie = new Cookie( + $this->previewKey, + GeneralUtility::_GP($this->previewKey), + 0, + GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'), + null, + $cookieSecure, + true, + false, + $cookieSameSite + ); + header('Set-Cookie: ' . $cookie->__toString(), false); } return $previewConfig; }