diff --git a/composer.lock b/composer.lock index 81ca12d579dba9d9c0b2231b9928385028a39634..44d2f9ab7913b67dde905ef0418656da33761f88 100644 --- a/composer.lock +++ b/composer.lock @@ -3642,16 +3642,16 @@ }, { "name": "phpunit/phpunit", - "version": "4.8.27", + "version": "4.8.28", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90" + "reference": "558a3a0d28b4cb7e4a593a4fbd2220e787076225" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c062dddcb68e44b563f66ee319ddae2b5a322a90", - "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/558a3a0d28b4cb7e4a593a4fbd2220e787076225", + "reference": "558a3a0d28b4cb7e4a593a4fbd2220e787076225", "shasum": "" }, "require": { @@ -3710,7 +3710,7 @@ "testing", "xunit" ], - "time": "2016-07-21T06:48:14+00:00" + "time": "2016-11-14T06:25:28+00:00" }, { "name": "phpunit/phpunit-mock-objects", diff --git a/core/composer.json b/core/composer.json index 503704cb7d2a324d11b9bec0318d07313ff5c0f7..d2a41c0ae35dd26248ac4404f8839fb9d197373f 100644 --- a/core/composer.json +++ b/core/composer.json @@ -42,7 +42,7 @@ "jcalderonzumba/gastonjs": "~1.0.2", "jcalderonzumba/mink-phantomjs-driver": "~0.3.1", "mikey179/vfsStream": "~1.2", - "phpunit/phpunit": "~4.8", + "phpunit/phpunit": ">=4.8.28 <5", "symfony/css-selector": "~2.8" }, "replace": { diff --git a/core/modules/block/block.routing.yml b/core/modules/block/block.routing.yml index e12cd8b1af827a679d87ef72797ee110e1366439..83648b94830309967b2630f7b1895638a84c1bcf 100644 --- a/core/modules/block/block.routing.yml +++ b/core/modules/block/block.routing.yml @@ -32,6 +32,7 @@ entity.block.enable: op: enable requirements: _entity_access: 'block.enable' + _csrf_token: 'TRUE' entity.block.disable: path: '/admin/structure/block/manage/{block}/disable' @@ -40,6 +41,7 @@ entity.block.disable: op: disable requirements: _entity_access: 'block.disable' + _csrf_token: 'TRUE' block.admin_display: path: '/admin/structure/block' diff --git a/core/modules/block/src/Tests/BlockUiTest.php b/core/modules/block/src/Tests/BlockUiTest.php index 899f9bba87617162b7baaf0f9a9fd49291e2660e..d4ebda4d9b7c71868aa6669e5408d5bdb923a30f 100644 --- a/core/modules/block/src/Tests/BlockUiTest.php +++ b/core/modules/block/src/Tests/BlockUiTest.php @@ -312,4 +312,18 @@ public function testBlockValidateErrors() { $this->assertTrue($error_class, 'Plugin error class found in parent form.'); } + /** + * Tests that the enable/disable routes are protected from CSRF. + */ + public function testRouteProtection() { + // Get the first block generated in our setUp method. + /** @var \Drupal\block\BlockInterface $block */ + $block = reset($this->blocks); + // Ensure that the enable and disable routes are protected. + $this->drupalGet('admin/structure/block/manage/' . $block->id() . '/disable'); + $this->assertResponse(403); + $this->drupalGet('admin/structure/block/manage/' . $block->id() . '/enable'); + $this->assertResponse(403); + } + } diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module index 04e4cd5a3c13db7dfa121cd70498f54fbc938e34..dccf8239b5f0860405a5963bba3786dbf33b66a7 100644 --- a/core/modules/editor/editor.module +++ b/core/modules/editor/editor.module @@ -520,8 +520,8 @@ function editor_file_download($uri) { if ($file->isPermanent()) { $referencing_entity_is_accessible = FALSE; $references = empty($usage_list['editor']) ? [] : $usage_list['editor']; - foreach ($references as $entity_type => $entity_ids) { - $referencing_entities = entity_load_multiple($entity_type, $entity_ids); + foreach ($references as $entity_type => $entity_ids_usage_count) { + $referencing_entities = entity_load_multiple($entity_type, array_keys($entity_ids_usage_count)); /** @var \Drupal\Core\Entity\EntityInterface $referencing_entity */ foreach ($referencing_entities as $referencing_entity) { if ($referencing_entity->access('view', NULL, TRUE)->isAllowed()) { diff --git a/core/modules/editor/tests/src/Functional/EditorPrivateFileReferenceFilterTest.php b/core/modules/editor/tests/src/Functional/EditorPrivateFileReferenceFilterTest.php index 3e3f3d187c2ebbcf9159f036a3202f0801a43ffe..816de5d6e4f7b19b0520e175e5f319f9287a4f6e 100644 --- a/core/modules/editor/tests/src/Functional/EditorPrivateFileReferenceFilterTest.php +++ b/core/modules/editor/tests/src/Functional/EditorPrivateFileReferenceFilterTest.php @@ -68,9 +68,18 @@ public function testEditorPrivateFileReferenceFilter() { $file->setPermanent(); $file->save(); + // Create some nodes to ensure file usage count does not match the ID's + // of the nodes we are going to check. + for ($i = 0; $i < 5; $i++) { + $this->drupalCreateNode([ + 'type' => 'page', + 'uid' => $author->id(), + ]); + } + // Create a node with its body field properly pointing to the just-created // file. - $node = $this->drupalCreateNode([ + $published_node = $this->drupalCreateNode([ 'type' => 'page', 'body' => [ 'value' => 'alt', @@ -79,19 +88,44 @@ public function testEditorPrivateFileReferenceFilter() { 'uid' => $author->id(), ]); + // Create an unpublished node with its body field properly pointing to the + // just-created file. + $unpublished_node = $this->drupalCreateNode([ + 'type' => 'page', + 'status' => NODE_NOT_PUBLISHED, + 'body' => [ + 'value' => 'alt', + 'format' => 'private_images', + ], + 'uid' => $author->id(), + ]); + // Do the actual test. The image should be visible for anonymous users, - // because they can view the referencing entity. - $this->drupalGet($node->toUrl()); + // because they can view the published node. Even though they can't view + // the unpublished node. + $this->drupalGet($published_node->toUrl()); $this->assertSession()->statusCodeEquals(200); + $this->drupalGet($unpublished_node->toUrl()); + $this->assertSession()->statusCodeEquals(403); $this->drupalGet($src); $this->assertSession()->statusCodeEquals(200); + // When the published node is also unpublished, the image should also + // become inaccessible to anonymous users. + $published_node->setPublished(FALSE)->save(); + + $this->drupalGet($published_node->toUrl()); + $this->assertSession()->statusCodeEquals(403); + $this->drupalGet($src); + $this->assertSession()->statusCodeEquals(403); + // Disallow anonymous users to view the entity, which then should also // disallow them to view the image. + $published_node->setPublished(TRUE)->save(); Role::load(RoleInterface::ANONYMOUS_ID) ->revokePermission('access content') ->save(); - $this->drupalGet($node->toUrl()); + $this->drupalGet($published_node->toUrl()); $this->assertSession()->statusCodeEquals(403); $this->drupalGet($src); $this->assertSession()->statusCodeEquals(403); diff --git a/core/modules/search/search.routing.yml b/core/modules/search/search.routing.yml index ec273f166882e00e6f159d77c33482b74ecc4b5d..98969a6e21c7fa4bc0e71531560aceba79de3f7f 100644 --- a/core/modules/search/search.routing.yml +++ b/core/modules/search/search.routing.yml @@ -37,6 +37,7 @@ entity.search_page.enable: op: 'enable' requirements: _entity_access: 'search_page.update' + _csrf_token: 'TRUE' entity.search_page.disable: path: '/admin/config/search/pages/manage/{search_page}/disable' @@ -45,6 +46,7 @@ entity.search_page.disable: op: 'disable' requirements: _entity_access: 'search_page.disable' + _csrf_token: 'TRUE' entity.search_page.set_default: path: '/admin/config/search/pages/manage/{search_page}/set-default' @@ -52,6 +54,7 @@ entity.search_page.set_default: _controller: '\Drupal\search\Controller\SearchController::setAsDefault' requirements: _entity_access: 'search_page.update' + _csrf_token: 'TRUE' entity.search_page.delete_form: path: '/admin/config/search/pages/manage/{search_page}/delete' diff --git a/core/modules/search/src/Form/SearchBlockForm.php b/core/modules/search/src/Form/SearchBlockForm.php index 5d77b7c3bfb281ad3c59d8840dfb8d13ec07b60f..652bf732d12fec99bfc28b922cca6695f6478b2f 100644 --- a/core/modules/search/src/Form/SearchBlockForm.php +++ b/core/modules/search/src/Form/SearchBlockForm.php @@ -75,6 +75,16 @@ public function getFormId() { public function buildForm(array $form, FormStateInterface $form_state) { // Set up the form to submit using GET to the correct search page. $entity_id = $this->searchPageRepository->getDefaultSearchPage(); + + $form = []; + + // SearchPageRepository::getDefaultSearchPage() depends on search.settings. + // The dependency needs to be added before the conditional return, otherwise + // the block would get cached without the necessary cacheablity metadata in + // case there is no default search page and would not be invalidated if that + // changes. + $this->renderer->addCacheableDependency($form, $this->configFactory->get('search.settings')); + if (!$entity_id) { $form['message'] = [ '#markup' => $this->t('Search is currently disabled'), @@ -103,9 +113,6 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#name' => '', ]; - // SearchPageRepository::getDefaultSearchPage() depends on search.settings. - $this->renderer->addCacheableDependency($form, $this->configFactory->get('search.settings')); - return $form; } diff --git a/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php b/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php index b5021d9ec5cb2af51941c734637a75b4fa7c660b..191e0cf0d577358c1e3cc156312a27360465c91a 100644 --- a/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php +++ b/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php @@ -154,8 +154,7 @@ public function testSearchModuleDisabling() { // Test each plugin if it's enabled as the only search plugin. foreach ($entities as $entity_id => $entity) { - // Set this as default. - $this->drupalGet("admin/config/search/pages/manage/$entity_id/set-default"); + $this->setDefaultThroughUi($entity_id); // Run a search from the correct search URL. $info = $plugin_info[$entity_id]; @@ -187,13 +186,16 @@ public function testSearchModuleDisabling() { $entity->disable()->save(); } + // Set the node search as default. + $this->setDefaultThroughUi('node_search'); + // Test with all search plugins enabled. When you go to the search // page or run search, all plugins should be shown. foreach ($entities as $entity) { $entity->enable()->save(); } - // Set the node search as default. - $this->drupalGet('admin/config/search/pages/manage/node_search/set-default'); + + \Drupal::service('router.builder')->rebuild(); $paths = [ ['path' => 'search/node', 'options' => ['query' => ['keys' => 'pizza']]], @@ -316,6 +318,19 @@ public function testMultipleSearchPages() { $this->verifySearchPageOperations($first_id, FALSE, FALSE, FALSE, FALSE); } + /** + * Tests that the enable/disable/default routes are protected from CSRF. + */ + public function testRouteProtection() { + // Ensure that the enable and disable routes are protected. + $this->drupalGet('admin/config/search/pages/manage/node_search/enable'); + $this->assertResponse(403); + $this->drupalGet('admin/config/search/pages/manage/node_search/disable'); + $this->assertResponse(403); + $this->drupalGet('admin/config/search/pages/manage/node_search/set-default'); + $this->assertResponse(403); + } + /** * Checks that the search page operations match expectations. * @@ -373,4 +388,17 @@ protected function assertDefaultSearch($expected, $message = '', $group = 'Other $this->assertIdentical($search_page_repository->getDefaultSearchPage(), $expected, $message, $group); } + /** + * Sets a search page as the default in the UI. + * + * @param string $entity_id + * The search page entity ID to enable. + */ + protected function setDefaultThroughUi($entity_id) { + $this->drupalGet('admin/config/search/pages'); + preg_match('|href="([^"]+' . $entity_id . '/set-default[^"]+)"|', $this->getRawContent(), $matches); + + $this->drupalGet($this->getAbsoluteUrl($matches[1])); + } + } diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php index 986410d1979969cbbc9d2a5c7391b004f53d85e8..2491bc84b30f3f9a8383b14b326fef833a8b9ad6 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -901,7 +901,7 @@ protected function drupalGet($path, array $options = [], array $headers = []) { } if ($path instanceof Url) { - $path = $path->toString(); + $path = $path->setAbsolute()->toString(TRUE)->getGeneratedUrl(); } $verbose = 'GET request to: ' . $path . @@ -2127,7 +2127,7 @@ protected function buildUrl($path, array $options = []) { $url_options = $path->getOptions(); $options = $url_options + $options; $path->setOptions($options); - return $path->setAbsolute()->toString(); + return $path->setAbsolute()->toString(TRUE)->getGeneratedUrl(); } // The URL generator service is not necessarily available yet; e.g., in // interactive installer tests.