diff --git a/core/core.services.yml b/core/core.services.yml index 8b9d1119c30006e599b5b086aede0ce4f207c456..de97bd6308c05efc1977f0f517f89e27df7538ba 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1608,7 +1608,7 @@ services: Drupal\Core\Session\AccountProxyInterface: '@current_user' session_configuration: class: Drupal\Core\Session\SessionConfiguration - arguments: ['%session.storage.options%'] + arguments: ['%session.storage.options%', '@settings'] Drupal\Core\Session\SessionConfigurationInterface: '@session_configuration' session: class: Symfony\Component\HttpFoundation\Session\Session diff --git a/core/lib/Drupal/Core/Session/SessionConfiguration.php b/core/lib/Drupal/Core/Session/SessionConfiguration.php index 541b94ab4c9678282299903ce86449e13a6c5702..b93a5ef988a14b47b93e7bd64343cfa65644ad27 100644 --- a/core/lib/Drupal/Core/Session/SessionConfiguration.php +++ b/core/lib/Drupal/Core/Session/SessionConfiguration.php @@ -2,6 +2,7 @@ namespace Drupal\Core\Session; +use Drupal\Core\Site\Settings; use Symfony\Component\HttpFoundation\Request; /** @@ -11,6 +12,8 @@ class SessionConfiguration implements SessionConfigurationInterface { /** * An associative array of session ini settings. + * + * @var array */ protected $options; @@ -19,12 +22,14 @@ class SessionConfiguration implements SessionConfigurationInterface { * * @param array $options * An associative array of session ini settings. + * @param \Drupal\Core\Site\Settings $settings + * The settings instance. * * @see \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage::__construct() * @see http://php.net/manual/session.configuration.php * @see https://www.php.net/manual/session.security.ini.php */ - public function __construct($options = []) { + public function __construct($options = [], protected ?Settings $settings = NULL) { // Provide sensible defaults for sid_length, sid_bits_per_character and // name_suffix. // @see core/assets/scaffold/files/default.services.yml @@ -33,6 +38,10 @@ public function __construct($options = []) { 'sid_bits_per_character' => 6, 'name_suffix' => '', ]; + if (!isset($this->settings)) { + @trigger_error('Calling ' . __METHOD__ . '() without the $settings argument is deprecated in drupal:11.1.0 and will be required in drupal:12.0.0. See https://www.drupal.org/node/3462570', E_USER_DEPRECATED); + $this->settings = \Drupal::service('settings'); + } } /** @@ -105,6 +114,9 @@ protected function getUnprefixedName(Request $request) { // Replace "core" out of session_name so core scripts redirect properly, // specifically install.php. $session_name = preg_replace('#/core$#', '', $session_name); + // Create unique session name for different sites served on the same + // host name and base path. + return substr(hash_hmac('sha256', $session_name, $this->settings->getHashSalt()), 0, 32); } return substr(hash('sha256', $session_name), 0, 32); diff --git a/core/tests/Drupal/Tests/Core/Session/SessionConfigurationTest.php b/core/tests/Drupal/Tests/Core/Session/SessionConfigurationTest.php index aa03d6b5dc3c0726b3fc0400a16ca21c6e127734..ea32f2489fbae92b20d41a852505dcb2012e51fd 100644 --- a/core/tests/Drupal/Tests/Core/Session/SessionConfigurationTest.php +++ b/core/tests/Drupal/Tests/Core/Session/SessionConfigurationTest.php @@ -4,12 +4,14 @@ namespace Drupal\Tests\Core\Session; +use Drupal\Core\Site\Settings; use Drupal\Tests\UnitTestCase; use Symfony\Component\HttpFoundation\Request; /** * @coversDefaultClass \Drupal\Core\Session\SessionConfiguration * @group Session + * @runTestsInSeparateProcesses */ class SessionConfigurationTest extends UnitTestCase { @@ -18,10 +20,11 @@ class SessionConfigurationTest extends UnitTestCase { * * @return \Drupal\Core\Session\SessionConfiguration|\PHPUnit\Framework\MockObject\MockObject */ - protected function createSessionConfiguration($options = []) { + protected function createSessionConfiguration($options = [], string $hash_salt = 'some_salt') { + $settings = new Settings(['hash_salt' => $hash_salt]); return $this->getMockBuilder('Drupal\Core\Session\SessionConfiguration') ->onlyMethods(['drupalValidTestUa']) - ->setConstructorArgs([$options]) + ->setConstructorArgs([$options, $settings]) ->getMock(); } @@ -170,8 +173,8 @@ public static function providerTestCookieSecure() { * * @dataProvider providerTestGeneratedSessionName */ - public function testGeneratedSessionName($uri, $expected_name): void { - $config = $this->createSessionConfiguration(); + public function testGeneratedSessionName(string $uri, string $hash_salt, string $expected_name): void { + $config = $this->createSessionConfiguration([], $hash_salt); $request = Request::create($uri); $options = $config->getOptions($request); @@ -187,27 +190,27 @@ public function testGeneratedSessionName($uri, $expected_name): void { */ public static function providerTestGeneratedSessionName() { $data = [ - ['http://example.com/path/index.php', 'SESS', 'example.com'], - ['http://www.example.com/path/index.php', 'SESS', 'www.example.com'], - ['http://subdomain.example.com/path/index.php', 'SESS', 'subdomain.example.com'], - ['http://example.com:8080/path/index.php', 'SESS', 'example.com'], - ['https://example.com/path/index.php', 'SSESS', 'example.com'], - ['http://example.com/path/core/install.php', 'SESS', 'example.com'], - ['http://localhost/path/index.php', 'SESS', 'localhost'], - ['http://127.0.0.1/path/index.php', 'SESS', '127.0.0.1'], - ['http://127.0.0.1:8888/path/index.php', 'SESS', '127.0.0.1'], - ['https://127.0.0.1/path/index.php', 'SSESS', '127.0.0.1'], - ['https://127.0.0.1:8443/path/index.php', 'SSESS', '127.0.0.1'], - ['http://1.1.1.1/path/index.php', 'SESS', '1.1.1.1'], - ['https://1.1.1.1/path/index.php', 'SSESS', '1.1.1.1'], - ['http://[::1]/path/index.php', 'SESS', '[::1]'], - ['http://[::1]:8888/path/index.php', 'SESS', '[::1]'], - ['https://[::1]/path/index.php', 'SSESS', '[::1]'], - ['https://[::1]:8443/path/index.php', 'SSESS', '[::1]'], + ['http://example.com/path/index.php', 'example_hash_salt', 'SESS', 'example.com'], + ['http://www.example.com/path/index.php', 'example_hash_salt', 'SESS', 'www.example.com'], + ['http://subdomain.example.com/path/index.php', 'example_hash_salt', 'SESS', 'subdomain.example.com'], + ['http://example.com:8080/path/index.php', 'example_hash_salt', 'SESS', 'example.com'], + ['https://example.com/path/index.php', 'example_hash_salt', 'SSESS', 'example.com'], + ['http://example.com/path/core/install.php', 'example_hash_salt', 'SESS', 'example.com'], + ['http://localhost/path/index.php', 'example_hash_salt', 'SESS', 'localhost'], + ['http://127.0.0.1/path/index.php', 'example_hash_salt', 'SESS', '127.0.0.1'], + ['http://127.0.0.1:8888/path/index.php', 'example_hash_salt', 'SESS', '127.0.0.1'], + ['https://127.0.0.1/path/index.php', 'example_hash_salt', 'SSESS', '127.0.0.1'], + ['https://127.0.0.1:8443/path/index.php', 'example_hash_salt', 'SSESS', '127.0.0.1'], + ['http://1.1.1.1/path/index.php', 'example_hash_salt', 'SESS', '1.1.1.1'], + ['https://1.1.1.1/path/index.php', 'example_hash_salt', 'SSESS', '1.1.1.1'], + ['http://[::1]/path/index.php', 'example_hash_salt', 'SESS', '[::1]'], + ['http://[::1]:8888/path/index.php', 'example_hash_salt', 'SESS', '[::1]'], + ['https://[::1]/path/index.php', 'example_hash_salt', 'SSESS', '[::1]'], + ['https://[::1]:8443/path/index.php', 'example_hash_salt', 'SSESS', '[::1]'], ]; return array_map(function ($record) { - return [$record[0], $record[1] . substr(hash('sha256', $record[2]), 0, 32)]; + return [$record[0], $record[1], $record[2] . substr(hash_hmac('sha256', $record[3], $record[1]), 0, 32)]; }, $data); } @@ -259,6 +262,24 @@ public static function providerTestEnforcedSessionName() { }, $data); } + /** + * Test if session name depends on hash_salt. + * + * @covers ::getOptions + */ + public function testSessionNameDependsOnHashSalt() { + $uri = "http://example.com/path/index.php"; + $request = Request::create($uri); + + $config_1 = $this->createSessionConfiguration([], 'hash_salt_1'); + $options_1 = $config_1->getOptions($request); + + $config_2 = $this->createSessionConfiguration([], 'hash_salt_2'); + $options_2 = $config_2->getOptions($request); + + $this->assertNotEquals($options_1['name'], $options_2['name']); + } + /** * Tests constructor's default settings. *