diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index b25a0d935e79ef29b80a4a75500462e9a4570f8a..49744524a2ac99bdd659e71adcb1a0443e1cde05 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -20,6 +20,7 @@ use Drupal\Core\DependencyInjection\YamlFileLoader; use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\File\MimeType\MimeTypeGuesser; +use Drupal\Core\Http\TrustedHostsRequestFactory; use Drupal\Core\Language\Language; use Drupal\Core\PageCache\RequestPolicyInterface; use Drupal\Core\PhpStorage\PhpStorageFactory; @@ -1297,13 +1298,24 @@ public static function validateHostname(Request $request) { * TRUE if the Host header is trusted, FALSE otherwise. * * @see https://www.drupal.org/node/1992030 + * @see \Drupal\Core\Http\TrustedHostsRequestFactory */ protected static function setupTrustedHosts(Request $request, $host_patterns) { $request->setTrustedHosts($host_patterns); // Get the host, which will validate the current request. try { - $request->getHost(); + $host = $request->getHost(); + + // Fake requests created through Request::create() without passing in the + // server variables from the main request have a default host of + // 'localhost'. If 'localhost' does not match any of the trusted host + // patterns these fake requests would fail the host verification. Instead, + // TrustedHostsRequestFactory makes sure to pass in the server variables + // from the main request. + $request_factory = new TrustedHostsRequestFactory($host); + Request::setFactory([$request_factory, 'createRequest']); + } catch (\UnexpectedValueException $e) { return FALSE; diff --git a/core/lib/Drupal/Core/Http/TrustedHostsRequestFactory.php b/core/lib/Drupal/Core/Http/TrustedHostsRequestFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..bc518d7a07df5984c7e13dc8a123ea6d0a384bca --- /dev/null +++ b/core/lib/Drupal/Core/Http/TrustedHostsRequestFactory.php @@ -0,0 +1,72 @@ +<?php + +/** + * @file + * Contains Drupal\Core\Http\TrustedHostsRequestFactory. + */ + +namespace Drupal\Core\Http; + +use Symfony\Component\HttpFoundation\Request; + +/** + * Provides a request factory for requests using host verification. + * + * Because the trusted host patterns for requests are stored statically, they + * are consulted even for fake request created with Request::create(), whose + * host is 'localhost' by default. Such requests would fail host verification + * unless 'localhost' matches one of the trusted host patterns. To circumvent + * this problem, this factory injects the server variables from the main request + * into each newly created request, so that the host is correctly set even for + * fake requests and they properly pass host verification. + * + * @see \Drupal\Core\DrupalKernel::setupTrustedHosts() + */ +class TrustedHostsRequestFactory { + + /** + * The host of the main request. + * + * @var string + */ + protected $host; + + /** + * Creates a new TrustedHostsRequestFactory. + * + * @param string $host + * The host of the main request. + */ + public function __construct($host) { + $this->host = (string) $host; + } + + /** + * Creates a new request object. + * + * @param array $query + * (optional) The query (GET) or request (POST) parameters. + * @param array $request + * (optional) An array of request variables. + * @param array $attributes + * (optioanl) An array of attributes. + * @param array $cookies + * (optional) The request cookies ($_COOKIE). + * @param array $files + * (optional) The request files ($_FILES). + * @param array $server + * (optional) The server parameters ($_SERVER). + * @param string $content + * (optional) The raw body data. + * + * @return \Symfony\Component\HttpFoundation\Request + * A new request object. + **/ + public function createRequest(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) { + if (empty($server['HTTP_HOST']) || ($server['HTTP_HOST'] === 'localhost' && $this->host !== 'localhost')) { + $server['HTTP_HOST'] = $this->host; + } + return new Request($query, $request, $attributes, $cookies, $files, $server, $content); + } + +} diff --git a/core/modules/system/src/Tests/System/TrustedHostsTest.php b/core/modules/system/src/Tests/System/TrustedHostsTest.php index 2be2c8b70fe58b5844357e732662920e38694bf6..3719bd1ebf684eb06b04ff29adb73cb6764bdb52 100644 --- a/core/modules/system/src/Tests/System/TrustedHostsTest.php +++ b/core/modules/system/src/Tests/System/TrustedHostsTest.php @@ -8,7 +8,10 @@ namespace Drupal\system\Tests\System; use Drupal\Core\Site\Settings; +use Drupal\Core\Url; use Drupal\simpletest\WebTestBase; +use Drupal\user\Entity\User; +use Symfony\Component\HttpFoundation\Request; /** * Tests output on the status overview page. @@ -59,4 +62,55 @@ public function testStatusPageWithConfiguration() { $this->assertRaw(t('The trusted_host_patterns setting is set to allow')); } + /** + * Tests that fake requests have the proper host configured. + * + * @see \Drupal\Core\Http\TrustedHostsRequestFactory + */ + public function testFakeRequests() { + $this->container->get('module_installer')->install(['trusted_hosts_test']); + + $host = $this->container->get('request_stack')->getCurrentRequest()->getHost(); + $settings['settings']['trusted_host_patterns'] = (object) array( + 'value' => array('^' . preg_quote($host) . '$'), + 'required' => TRUE, + ); + + $this->writeSettings($settings); + + $this->drupalGet('trusted-hosts-test/fake-request'); + $this->assertText('Host: ' . $host); + } + + /** + * Tests that shortcut module works together with host verification. + */ + public function testShortcut() { + $this->container->get('module_installer')->install(['block', 'shortcut']); + $this->rebuildContainer(); + + /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */ + $entity_manager = $this->container->get('entity.manager'); + $shortcut_storage = $entity_manager->getStorage('shortcut'); + + $shortcut = $shortcut_storage->create([ + 'title' => $this->randomString(), + 'link' => 'internal:/admin/reports/status', + 'shortcut_set' => 'default', + ]); + $shortcut_storage->save($shortcut); + + // Grant the current user access to see the shortcuts. + $role_storage = $entity_manager->getStorage('user_role'); + $roles = $this->loggedInUser->getRoles(TRUE); + /** @var \Drupal\user\RoleInterface $role */ + $role = $role_storage->load(reset($roles)); + $role->grantPermission('access shortcuts')->save(); + + $this->drupalPlaceBlock('shortcuts'); + + $this->drupalGet(''); + $this->assertLink($shortcut->label()); + } + } diff --git a/core/modules/system/tests/modules/trusted_hosts_test/src/Controller/TrustedHostsTestController.php b/core/modules/system/tests/modules/trusted_hosts_test/src/Controller/TrustedHostsTestController.php new file mode 100644 index 0000000000000000000000000000000000000000..b97c45c543e0c07c3d5cb37954a9b7f46148b9c4 --- /dev/null +++ b/core/modules/system/tests/modules/trusted_hosts_test/src/Controller/TrustedHostsTestController.php @@ -0,0 +1,25 @@ +<?php + +/** + * @file + * Contains Drupal\trusted_hosts_test\Controller\TrustedHostsTestController. + */ + +namespace Drupal\trusted_hosts_test\Controller; +use Symfony\Component\HttpFoundation\Request; + +/** + * Provides a test controller for testing the trusted hosts setting. + */ +class TrustedHostsTestController { + + /** + * Creates a fake request and prints out its host. + */ + public function fakeRequestHost() { + $request = Request::create('/'); + return ['#markup' => 'Host: ' . $request->getHost()]; + } + +} + diff --git a/core/modules/system/tests/modules/trusted_hosts_test/trusted_hosts_test.info.yml b/core/modules/system/tests/modules/trusted_hosts_test/trusted_hosts_test.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..20f41bcb84a01235e0a22058e9da0c4bc4f8d8c3 --- /dev/null +++ b/core/modules/system/tests/modules/trusted_hosts_test/trusted_hosts_test.info.yml @@ -0,0 +1,5 @@ +type: module +name: 'Trusted hosts test module' +core: 8.x +package: Testing +version: VERSION diff --git a/core/modules/system/tests/modules/trusted_hosts_test/trusted_hosts_test.routing.yml b/core/modules/system/tests/modules/trusted_hosts_test/trusted_hosts_test.routing.yml new file mode 100644 index 0000000000000000000000000000000000000000..834aeace01e8844e3a972945151dff4242d33097 --- /dev/null +++ b/core/modules/system/tests/modules/trusted_hosts_test/trusted_hosts_test.routing.yml @@ -0,0 +1,6 @@ +trusted_hosts_test.fake_request: + path: '/trusted-hosts-test/fake-request' + defaults: + _controller: 'Drupal\trusted_hosts_test\Controller\TrustedHostsTestController::fakeRequestHost' + requirements: + _access: 'TRUE' diff --git a/core/tests/Drupal/Tests/Core/Http/TrustedHostsRequestFactoryTest.php b/core/tests/Drupal/Tests/Core/Http/TrustedHostsRequestFactoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..643b821b1f2fa1b3ab27804e6492f2ff3f776527 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Http/TrustedHostsRequestFactoryTest.php @@ -0,0 +1,60 @@ +<?php + +/** + * @file + * Contains Drupal\Tests\Core\Http\TrustedHostsRequestFactoryTest. + */ + +namespace Drupal\Tests\Core\Http; +use Drupal\Core\Http\TrustedHostsRequestFactory; +use Drupal\Tests\UnitTestCase; + +/** + * Tests the trusted hosts request factory. + * + * @coversDefaultClass \Drupal\Core\Http\TrustedHostsRequestFactory + * @group Http + */ +class TrustedHostsRequestFactoryTest extends UnitTestCase { + + /** + * Tests TrustedHostsRequestFactory::createRequest(). + * + * @param string $host + * The host to pass into TrustedHostsRequestFactory. + * @param array $server + * The server array to pass into + * TrustedHostsRequestFactory::createRequest(). + * @param string $expected + * The expected host of the created request. + * + * @covers ::createRequest + * @dataProvider providerTestCreateRequest + */ + public function testCreateRequest($host, $server, $expected) { + $request_factory = new TrustedHostsRequestFactory($host); + $request = $request_factory->createRequest([], [], [], [], [], $server, []); + $this->assertEquals($expected, $request->getHost()); + } + + /** + * Provides data for testCreateRequest(). + * + * @return array + * An array of test cases, where each test case is an array with the + * following values: + * - A string containing the host to pass into TrustedHostsRequestFactory. + * - An array containing the server array to pass into + * TrustedHostsRequestFactory::createRequest(). + * - A string containing the expected host of the created request. + */ + public function providerTestCreateRequest() { + $tests = []; + $tests[] = ['example.com', [], 'example.com']; + $tests[] = ['localhost', [], 'localhost']; + $tests[] = ['localhost', ['HTTP_HOST' => 'localhost'], 'localhost']; + $tests[] = ['example.com', ['HTTP_HOST' => 'localhost'], 'example.com']; + return $tests; + } + +}