diff --git a/docs/book/config.md b/docs/book/config.md index e544578f..b6568bc7 100644 --- a/docs/book/config.md +++ b/docs/book/config.md @@ -22,6 +22,7 @@ Option | Data Type | Description `cookie_httponly` | `boolean` | Marks the cookie as accessible only through the HTTP protocol. `cookie_lifetime` | `integer` | Specifies the lifetime of the cookie in seconds which is sent to the browser. `cookie_path` | `string` | Specifies path to set in the session cookie. +`cookie_samesite` | `string` | Specifies whether cookies should be sent along with cross-site requests. (Since 2.11.0) `cookie_secure` | `boolean` | Specifies whether cookies should only be sent over secure connections. `entropy_length` | `integer` | Specifies the number of bytes which will be read from the file specified in entropy_file. Removed in PHP 7.1.0. `entropy_file` | `string` | Defines a path to an external resource (file) which will be used as an additional entropy. Removed in PHP 7.1.0. diff --git a/src/Config/SameSiteCookieCapableInterface.php b/src/Config/SameSiteCookieCapableInterface.php new file mode 100644 index 00000000..2f5af695 --- /dev/null +++ b/src/Config/SameSiteCookieCapableInterface.php @@ -0,0 +1,15 @@ +cookieDomain; } + /** + * Set session.cookie_samesite + * + * @param string $cookieSameSite + * @return StandardConfig + */ + public function setCookieSameSite($cookieSameSite) + { + $this->cookieSameSite = (string) $cookieSameSite; + $this->setStorageOption('cookie_samesite', $this->cookieSameSite); + return $this; + } + + /** + * Get session.cookie_samesite + * + * @return string + */ + public function getCookieSameSite() + { + if (null === $this->cookieSameSite) { + $this->cookieSameSite = $this->getStorageOption('cookie_samesite'); + } + return $this->cookieSameSite; + } + /** * Set session.cookie_secure * @@ -912,6 +945,7 @@ public function toArray() 'cookie_httponly' => $this->getCookieHttpOnly(), 'cookie_lifetime' => $this->getCookieLifetime(), 'cookie_path' => $this->getCookiePath(), + 'cookie_samesite' => $this->getCookieSameSite(), 'cookie_secure' => $this->getCookieSecure(), 'name' => $this->getName(), 'remember_me_seconds' => $this->getRememberMeSeconds(), diff --git a/src/Service/SessionConfigFactory.php b/src/Service/SessionConfigFactory.php index 0253499c..e2d6b209 100644 --- a/src/Service/SessionConfigFactory.php +++ b/src/Service/SessionConfigFactory.php @@ -7,6 +7,7 @@ use Laminas\ServiceManager\FactoryInterface; use Laminas\ServiceManager\ServiceLocatorInterface; use Laminas\Session\Config\ConfigInterface; +use Laminas\Session\Config\SameSiteCookieCapableInterface; use Laminas\Session\Config\SessionConfig; use function class_exists; @@ -60,6 +61,18 @@ public function __invoke(ContainerInterface $container, $requestedName, ?array $ ConfigInterface::class )); } + + if ( + isset($config['cookie_samesite']) + && ! $sessionConfig instanceof SameSiteCookieCapableInterface + ) { + throw new ServiceNotCreatedException(sprintf( + 'Invalid configuration class "%s". When configuration option "cookie_samesite" is used,' + . ' the configuration class must implement %s', + $class, + SameSiteCookieCapableInterface::class + )); + } $sessionConfig->setOptions($config); return $sessionConfig; diff --git a/test/Config/SessionConfigTest.php b/test/Config/SessionConfigTest.php index bf1f697a..0a9d9d7e 100644 --- a/test/Config/SessionConfigTest.php +++ b/test/Config/SessionConfigTest.php @@ -395,6 +395,25 @@ public function testSettingInvalidCookieDomainRaisesException2(): void $this->config->setCookieDomain('D:\\WINDOWS\\System32\\drivers\\etc\\hosts'); } + // session.cookie_samesite + + public function testCookieSameSiteDefaultsToIniSettings() + { + $this->assertSame(ini_get('session.cookie_samesite'), $this->config->getCookieSameSite()); + } + + public function testCookieSameSiteIsMutable() + { + $this->config->setCookieSameSite('Strict'); + $this->assertEquals('Strict', $this->config->getCookieSameSite()); + } + + public function testCookieSameSiteAltersIniSetting() + { + $this->config->setCookieSameSite('Strict'); + $this->assertEquals('Strict', ini_get('session.cookie_samesite')); + } + // session.cookie_secure public function testCookieSecureDefaultsToIniSettings(): void @@ -901,6 +920,11 @@ public function optionsProvider(): array 'getCookieDomain', 'getlaminas.org', ], + [ + 'cookie_samesite', + 'getCookieSameSite', + 'Lax', + ], [ 'cookie_secure', 'getCookieSecure', diff --git a/test/Config/StandardConfigTest.php b/test/Config/StandardConfigTest.php index a7ffb4cb..401e5704 100644 --- a/test/Config/StandardConfigTest.php +++ b/test/Config/StandardConfigTest.php @@ -225,6 +225,14 @@ public function testSettingInvalidCookieDomainRaisesException2(): void $this->config->setCookieDomain('D:\\WINDOWS\\System32\\drivers\\etc\\hosts'); } + // session.cookie_samesite + + public function testCookieSameSiteIsMutable() + { + $this->config->setCookieSameSite('Strict'); + $this->assertEquals('Strict', $this->config->getCookieSameSite()); + } + // session.cookie_secure public function testCookieSecureIsMutable(): void @@ -524,6 +532,11 @@ public function optionsProvider(): array 'getCookieDomain', 'getlaminas.org', ], + [ + 'cookie_samesite', + 'getCookieSameSite', + 'Lax', + ], [ 'cookie_secure', 'getCookieSecure', diff --git a/test/Service/SessionConfigFactoryTest.php b/test/Service/SessionConfigFactoryTest.php index 4d15953c..3cfe912f 100644 --- a/test/Service/SessionConfigFactoryTest.php +++ b/test/Service/SessionConfigFactoryTest.php @@ -3,11 +3,13 @@ namespace LaminasTest\Session\Service; use Laminas\ServiceManager\Config; +use Laminas\ServiceManager\Exception\ServiceNotCreatedException; use Laminas\ServiceManager\ServiceManager; use Laminas\Session\Config\ConfigInterface; use Laminas\Session\Config\SessionConfig; use Laminas\Session\Config\StandardConfig; use Laminas\Session\Service\SessionConfigFactory; +use LaminasTest\Session\TestAsset\TestConfig; use PHPUnit\Framework\TestCase; /** @@ -73,4 +75,20 @@ public function testServiceReceivesConfiguration(): void $config = $this->services->get(ConfigInterface::class); self::assertEquals('laminas', $config->getName()); } + + public function testServiceNotCreatedWhenInvalidSamesiteConfig() + { + $this->services->setService( + 'config', + [ + 'session_config' => [ + 'config_class' => TestConfig::class, + 'cookie_samesite' => 'Lax', + ], + ] + ); + $this->expectException(ServiceNotCreatedException::class); + $this->expectExceptionMessage('"cookie_samesite"'); + $this->services->get(ConfigInterface::class); + } } diff --git a/test/TestAsset/TestConfig.php b/test/TestAsset/TestConfig.php new file mode 100644 index 00000000..82de07cc --- /dev/null +++ b/test/TestAsset/TestConfig.php @@ -0,0 +1,168 @@ +