.../Core/Menu/DefaultMenuLinkTreeManipulators.php | 3 + core/lib/Drupal/Core/Menu/InaccessibleMenuLink.php | 86 ++++++++++++++++++++++ .../Menu/DefaultMenuLinkTreeManipulatorsTest.php | 7 ++ 3 files changed, 96 insertions(+) diff --git a/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php b/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php index a1bd4cb..9461683 100644 --- a/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php +++ b/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php @@ -101,6 +101,9 @@ public function checkAccess(array $tree) { } } else { + // Replace the link with an InaccessibleMenuLink object, so that if it + // is accidentally rendered, no sensitive information is divulged. + $tree[$key]->link = new InaccessibleMenuLink($tree[$key]->link); // Always keep top-level inaccessible links: their cacheability metadata // that indicates why they're not accessible by the current user must be // bubbled. Otherwise, those subtrees will not be varied by any cache diff --git a/core/lib/Drupal/Core/Menu/InaccessibleMenuLink.php b/core/lib/Drupal/Core/Menu/InaccessibleMenuLink.php new file mode 100644 index 0000000..0a5d4a4 --- /dev/null +++ b/core/lib/Drupal/Core/Menu/InaccessibleMenuLink.php @@ -0,0 +1,86 @@ +wrappedLink = $wrapped_link; + $plugin_definition = [ + 'route_name' => '', + 'route_parameters' => [], + 'url' => NULL, + ] + $this->wrappedLink->getPluginDefinition(); + parent::__construct([], $this->wrappedLink->getPluginId(), $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public function getTitle() { + return $this->t('Inaccessible'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return ''; + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + return $this->wrappedLink->getCacheContexts(); + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return $this->wrappedLink->getCacheTags(); + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return $this->wrappedLink->getCacheMaxAge(); + } + + + /** + * {@inheritdoc} + */ + public function updateLink(array $new_definition_values, $persist) { + throw new PluginException(SafeMarkup::format('Inaccessible menu link plugins do not support updating', ['@id' => $this->getPluginId()])); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php b/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php index 873893a..c20a99b 100644 --- a/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php +++ b/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php @@ -174,24 +174,29 @@ public function testCheckAccess() { // hence kept. $element = $tree[1]; $this->assertEquals(AccessResult::forbidden()->cachePerPermissions(), $element->access); + $this->assertInstanceOf('\Drupal\Core\Menu\InaccessibleMenuLink', $element->link); // Menu link 2: route with parameters, access granted. $element = $tree[2]; $this->assertEquals(AccessResult::allowed()->cachePerPermissions(), $element->access); + $this->assertNotInstanceOf('\Drupal\Core\Menu\InaccessibleMenuLink', $element->link); // Menu link 3: route with parameters, AccessResult::neutral(), top-level // inaccessible link, hence kept for its cacheability metadata. // Note that the permissions cache context is added automatically, because // we always check the "link to any page" permission. $element = $tree[2]->subtree[3]; $this->assertEquals(AccessResult::neutral()->cachePerPermissions(), $element->access); + $this->assertInstanceOf('\Drupal\Core\Menu\InaccessibleMenuLink', $element->link); // Menu link 4: child of menu link 3, which was AccessResult::neutral(), // hence menu link 3's subtree is removed, of which this menu link is one. $this->assertFalse(array_key_exists(4, $tree[2]->subtree[3]->subtree)); // Menu link 5: no route name, treated as external, hence access granted. $element = $tree[5]; $this->assertEquals(AccessResult::allowed()->cachePerPermissions(), $element->access); + $this->assertNotInstanceOf('\Drupal\Core\Menu\InaccessibleMenuLink', $element->link); // Menu link 6: external URL, hence access granted. $element = $tree[6]; $this->assertEquals(AccessResult::allowed()->cachePerPermissions(), $element->access); + $this->assertNotInstanceOf('\Drupal\Core\Menu\InaccessibleMenuLink', $element->link); // Menu link 7: 'access' already set: AccessResult::neutral(), top-level // inaccessible link, hence kept for its cacheability metadata. // Note that unlike for menu link 3, the permission cache context is absent, @@ -199,10 +204,12 @@ public function testCheckAccess() { // already set. $element = $tree[5]->subtree[7]; $this->assertEquals(AccessResult::neutral(), $element->access); + $this->assertInstanceOf('\Drupal\Core\Menu\InaccessibleMenuLink', $element->link); // Menu link 8: 'access' already set, note that 'per permissions' caching // is not added. $element = $tree[8]; $this->assertEquals(AccessResult::allowed()->cachePerUser(), $element->access); + $this->assertNotInstanceOf('\Drupal\Core\Menu\InaccessibleMenuLink', $element->link); } /**