From 61a79e24e037e5ecb2a9345481d0d95eeafef1cb Mon Sep 17 00:00:00 2001 From: Pierre Rudloff <contact@rudloff.pro> Date: Thu, 27 Feb 2025 21:07:31 +0100 Subject: [PATCH 1/9] Add failing tests --- .../menu_link_access_test.info.yml | 4 + .../src/Hook/MenuLinkAccessTestHooks.php | 37 ++++++++ .../src/Functional/MenuUiNodeAccessTest.php | 86 +++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 core/modules/menu_ui/tests/modules/menu_link_access_test/menu_link_access_test.info.yml create mode 100644 core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php create mode 100644 core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php diff --git a/core/modules/menu_ui/tests/modules/menu_link_access_test/menu_link_access_test.info.yml b/core/modules/menu_ui/tests/modules/menu_link_access_test/menu_link_access_test.info.yml new file mode 100644 index 000000000000..36ba82e66771 --- /dev/null +++ b/core/modules/menu_ui/tests/modules/menu_link_access_test/menu_link_access_test.info.yml @@ -0,0 +1,4 @@ +name: 'Tests menu link access' +type: module +package: Testing +version: VERSION diff --git a/core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php b/core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php new file mode 100644 index 000000000000..0ae635625145 --- /dev/null +++ b/core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\menu_link_access_test\Hook; + +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Hook\Attribute\Hook; + +/** + * Hook implementations for menu_link_access_test. + */ +class MenuLinkAccessTestHooks { + + /** + * Implements hook_ENTITY_TYPE_access(). + */ + #[Hook('menu_link_content_access')] + public function entityTestAccess(EntityInterface $entity, $operation, AccountInterface $account): AccessResultInterface { + if (in_array($operation, ['update', 'delete'])) { + return AccessResult::forbidden(); + } + + return AccessResult::neutral(); + } + + /** + * Implements hook_ENTITY_TYPE_create_access(). + */ + #[Hook('menu_link_content_create_access')] + public function entityTestCreateAccess(AccountInterface $account, $context, $entity_bundle): AccessResultInterface { + return AccessResult::forbidden(); + } + +} diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php new file mode 100644 index 000000000000..2a7e855322bd --- /dev/null +++ b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php @@ -0,0 +1,86 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\menu_ui\Functional; + +use Drupal\menu_link_content\Entity\MenuLinkContent; +use Drupal\node\Entity\Node; +use Drupal\Tests\BrowserTestBase; + +/** + * Edit a node when you don't have permission to add or edit menu links. + */ +class MenuUiNodeAccessTest extends BrowserTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'menu_ui', + 'test_page_test', + 'node', + 'menu_link_access_test', + ]; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']); + } + + /** + * Tests menu link create access is enforced. + */ + public function testMenuLinkCreateAccess(): void { + $this->drupalLogin($this->drupalCreateUser([ + 'administer menu', + 'edit any page content', + ])); + $node = Node::create([ + 'type' => 'page', + 'title' => $this->randomMachineName(), + 'uid' => $this->rootUser->id(), + 'status' => 1, + ]); + $node->save(); + + $this->drupalGet('node/' . $node->id() . '/edit'); + $this->assertSession()->elementNotExists('css', 'input[name="menu[title]"]'); + } + + /** + * Tests menu link edit/delete access is enforced. + */ + public function testMenuLinkEditAccess(): void { + $this->drupalLogin($this->drupalCreateUser([ + 'administer menu', + 'edit any page content', + ])); + $mainLinkTitle = $this->randomMachineName(); + $node = Node::create([ + 'type' => 'page', + 'title' => $this->randomMachineName(), + 'uid' => $this->rootUser->id(), + 'status' => 1, + ]); + $node->save(); + MenuLinkContent::create([ + 'link' => [['uri' => 'entity:node/' . $node->id()]], + 'title' => $mainLinkTitle, + 'menu_name' => 'main', + ])->save(); + + $this->drupalGet('node/' . $node->id() . '/edit'); + $this->assertSession()->elementNotExists('css', 'input[name="menu[title]"]'); + } + +} -- GitLab From 5b8d3e46e50cac1044d15f65cdf0d35d6a10b59b Mon Sep 17 00:00:00 2001 From: Pierre Rudloff <contact@rudloff.pro> Date: Thu, 27 Feb 2025 21:14:00 +0100 Subject: [PATCH 2/9] Add fix --- core/modules/menu_ui/src/Hook/MenuUiHooks.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/core/modules/menu_ui/src/Hook/MenuUiHooks.php b/core/modules/menu_ui/src/Hook/MenuUiHooks.php index ac7c7602f3be..74c7b78fca85 100644 --- a/core/modules/menu_ui/src/Hook/MenuUiHooks.php +++ b/core/modules/menu_ui/src/Hook/MenuUiHooks.php @@ -16,6 +16,7 @@ use Drupal\Core\Url; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Hook\Attribute\Hook; +use Drupal\menu_link_content\Entity\MenuLinkContent; /** * Hook implementations for menu_ui. @@ -86,6 +87,22 @@ public function blockViewSystemMenuBlockAlter(array &$build, BlockPluginInterfac } } + /** + * Check if user is allowed to use the menu link subform. + */ + private function getMenuLinkContentAccess(array $defaults) { + if (!empty($defaults['entity_id'])) { + $entity = MenuLinkContent::load($defaults['entity_id']); + + // The form can be used to edit or delete the menu link. + return $entity->access('update', NULL, TRUE)->andIf($entity->access('delete', NULL, TRUE)); + } + else { + // If the node has no corresponding menu link, users needs to permission to create one. + return \Drupal::entityTypeManager()->getAccessControlHandler('menu_link_content')->createAccess(NULL, NULL, [], TRUE); + } + } + /** * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm. * @@ -128,7 +145,7 @@ public function formNodeFormAlter(&$form, FormStateInterface $form_state) : void $form['menu'] = [ '#type' => 'details', '#title' => $this->t('Menu settings'), - '#access' => \Drupal::currentUser()->hasPermission('administer menu'), + '#access' => $this->getMenuLinkContentAccess($defaults), '#open' => (bool) $defaults['id'], '#group' => 'advanced', '#attached' => [ -- GitLab From 1638a359cfc0f3544c8fc644309f716f899aebd9 Mon Sep 17 00:00:00 2001 From: Pierre Rudloff <contact@rudloff.pro> Date: Thu, 27 Feb 2025 21:24:44 +0100 Subject: [PATCH 3/9] Fix types --- core/modules/menu_ui/src/Hook/MenuUiHooks.php | 3 ++- .../menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/modules/menu_ui/src/Hook/MenuUiHooks.php b/core/modules/menu_ui/src/Hook/MenuUiHooks.php index 74c7b78fca85..fab544f24ce9 100644 --- a/core/modules/menu_ui/src/Hook/MenuUiHooks.php +++ b/core/modules/menu_ui/src/Hook/MenuUiHooks.php @@ -17,6 +17,7 @@ use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Hook\Attribute\Hook; use Drupal\menu_link_content\Entity\MenuLinkContent; +use Drupal\Core\Access\AccessResultInterface; /** * Hook implementations for menu_ui. @@ -90,7 +91,7 @@ public function blockViewSystemMenuBlockAlter(array &$build, BlockPluginInterfac /** * Check if user is allowed to use the menu link subform. */ - private function getMenuLinkContentAccess(array $defaults) { + private function getMenuLinkContentAccess(array $defaults): AccessResultInterface { if (!empty($defaults['entity_id'])) { $entity = MenuLinkContent::load($defaults['entity_id']); diff --git a/core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php b/core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php index 0ae635625145..586d2a793691 100644 --- a/core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php +++ b/core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php @@ -5,6 +5,7 @@ namespace Drupal\menu_link_access_test\Hook; use Drupal\Core\Access\AccessResult; +use Drupal\Core\Access\AccessResultInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Hook\Attribute\Hook; -- GitLab From 09d533ab39c4ced1026c9c7de04840be3325f4b1 Mon Sep 17 00:00:00 2001 From: Pierre Rudloff <contact@rudloff.pro> Date: Thu, 27 Feb 2025 21:44:34 +0100 Subject: [PATCH 4/9] Users that can't see unpublished nodes can't edit their menu link So fix test accordingly Submitting menu[enabled] now fails --- core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php index 69b11f376d03..0025ae08718c 100644 --- a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php +++ b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php @@ -191,7 +191,9 @@ public function testMenuNodeFormWidget(): void { $this->drupalGet('test-page'); $this->assertSession()->linkNotExists($node_title, 'Found no menu link with the node unpublished'); // Assert that the link exists if published. - $edit['status[value]'] = TRUE; + $edit = [ + 'status[value]' => TRUE, + ]; $this->drupalGet('node/' . $node->id() . '/edit'); $this->submitForm($edit, 'Save'); $this->drupalGet('test-page'); -- GitLab From 09480daa5f47b614975bf3c9bcb0f44ec915b1b9 Mon Sep 17 00:00:00 2001 From: Pierre Rudloff <contact@rudloff.pro> Date: Thu, 27 Feb 2025 21:55:36 +0100 Subject: [PATCH 5/9] Missing @group annotation --- .../menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php index 2a7e855322bd..93acdc88754f 100644 --- a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php +++ b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php @@ -10,6 +10,8 @@ /** * Edit a node when you don't have permission to add or edit menu links. + * + * @group menu_ui */ class MenuUiNodeAccessTest extends BrowserTestBase { -- GitLab From 0f20ad4c6b0bf71182081439ee220ddc360111ca Mon Sep 17 00:00:00 2001 From: Pierre Rudloff <contact@rudloff.pro> Date: Fri, 28 Feb 2025 10:48:41 +0100 Subject: [PATCH 6/9] Make method protected --- core/modules/menu_ui/src/Hook/MenuUiHooks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/menu_ui/src/Hook/MenuUiHooks.php b/core/modules/menu_ui/src/Hook/MenuUiHooks.php index fab544f24ce9..82669191754b 100644 --- a/core/modules/menu_ui/src/Hook/MenuUiHooks.php +++ b/core/modules/menu_ui/src/Hook/MenuUiHooks.php @@ -91,7 +91,7 @@ public function blockViewSystemMenuBlockAlter(array &$build, BlockPluginInterfac /** * Check if user is allowed to use the menu link subform. */ - private function getMenuLinkContentAccess(array $defaults): AccessResultInterface { + protected function getMenuLinkContentAccess(array $defaults): AccessResultInterface { if (!empty($defaults['entity_id'])) { $entity = MenuLinkContent::load($defaults['entity_id']); -- GitLab From 34f662df962569b25d8f4c04528d29370d009e60 Mon Sep 17 00:00:00 2001 From: Pierre Rudloff <contact@rudloff.pro> Date: Fri, 28 Feb 2025 10:52:05 +0100 Subject: [PATCH 7/9] Inject entity type manager service --- core/modules/menu_ui/src/Hook/MenuUiHooks.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/modules/menu_ui/src/Hook/MenuUiHooks.php b/core/modules/menu_ui/src/Hook/MenuUiHooks.php index 82669191754b..32a4ab93813b 100644 --- a/core/modules/menu_ui/src/Hook/MenuUiHooks.php +++ b/core/modules/menu_ui/src/Hook/MenuUiHooks.php @@ -18,6 +18,7 @@ use Drupal\Core\Hook\Attribute\Hook; use Drupal\menu_link_content\Entity\MenuLinkContent; use Drupal\Core\Access\AccessResultInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; /** * Hook implementations for menu_ui. @@ -26,6 +27,10 @@ class MenuUiHooks { use StringTranslationTrait; + public function __construct( + protected EntityTypeManagerInterface $entityTypeManager, + ) {} + /** * Implements hook_help(). */ @@ -100,7 +105,7 @@ protected function getMenuLinkContentAccess(array $defaults): AccessResultInterf } else { // If the node has no corresponding menu link, users needs to permission to create one. - return \Drupal::entityTypeManager()->getAccessControlHandler('menu_link_content')->createAccess(NULL, NULL, [], TRUE); + return $this->entityTypeManager->getAccessControlHandler('menu_link_content')->createAccess(NULL, NULL, [], TRUE); } } -- GitLab From 879fa6e993654f7420ce2e0516d510464460e781 Mon Sep 17 00:00:00 2001 From: Pierre Rudloff <contact@rudloff.pro> Date: Fri, 28 Feb 2025 10:59:23 +0100 Subject: [PATCH 8/9] Fix related test --- core/modules/menu_ui/tests/src/Kernel/MenuBlockTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/menu_ui/tests/src/Kernel/MenuBlockTest.php b/core/modules/menu_ui/tests/src/Kernel/MenuBlockTest.php index f87a2639b877..f3f6d51e512e 100644 --- a/core/modules/menu_ui/tests/src/Kernel/MenuBlockTest.php +++ b/core/modules/menu_ui/tests/src/Kernel/MenuBlockTest.php @@ -71,7 +71,7 @@ public function testOperationLinks(): void { ]); // Test when user does have "administer menu" permission. - $menuUiEntityOperation = new MenuUiHooks(); + $menuUiEntityOperation = new MenuUiHooks(\Drupal::entityTypeManager()); $this->assertEquals([ 'menu-edit' => [ 'title' => 'Edit menu', -- GitLab From 964f455afcd29fd729087f5e0a2ab64ca1303a15 Mon Sep 17 00:00:00 2001 From: Pierre Rudloff <contact@rudloff.pro> Date: Fri, 28 Feb 2025 19:15:21 +0100 Subject: [PATCH 9/9] Add missing params to docblocks --- core/modules/menu_ui/src/Hook/MenuUiHooks.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/modules/menu_ui/src/Hook/MenuUiHooks.php b/core/modules/menu_ui/src/Hook/MenuUiHooks.php index 32a4ab93813b..9b32ffa18247 100644 --- a/core/modules/menu_ui/src/Hook/MenuUiHooks.php +++ b/core/modules/menu_ui/src/Hook/MenuUiHooks.php @@ -27,6 +27,12 @@ class MenuUiHooks { use StringTranslationTrait; + /** + * Constructs a new MenuUiHooks object. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager + * The entity type manager. + */ public function __construct( protected EntityTypeManagerInterface $entityTypeManager, ) {} @@ -95,6 +101,11 @@ public function blockViewSystemMenuBlockAlter(array &$build, BlockPluginInterfac /** * Check if user is allowed to use the menu link subform. + * + * @param array $defaults + * An array that contains default values for the menu link form. + * + * @see menu_ui_get_menu_link_defaults() */ protected function getMenuLinkContentAccess(array $defaults): AccessResultInterface { if (!empty($defaults['entity_id'])) { -- GitLab