diff --git a/core/modules/field_ui/field_ui.routing.yml b/core/modules/field_ui/field_ui.routing.yml index 98db1e0..096845e 100644 --- a/core/modules/field_ui/field_ui.routing.yml +++ b/core/modules/field_ui/field_ui.routing.yml @@ -13,6 +13,7 @@ field_ui.display_mode: _title: 'Display modes' requirements: _permission: 'administer display modes' + _access_admin_menu_block_page: 'TRUE' entity.entity_view_mode.collection: path: '/admin/structure/display-modes/view' diff --git a/core/modules/system/src/Access/SystemAdminMenuBlockAccessCheck.php b/core/modules/system/src/Access/SystemAdminMenuBlockAccessCheck.php new file mode 100644 index 0000000..c751c65 --- /dev/null +++ b/core/modules/system/src/Access/SystemAdminMenuBlockAccessCheck.php @@ -0,0 +1,98 @@ +accessManager = $access_manager; + $this->menuLinkTree = $menu_link_tree; + } + + /** + * Checks access. + * + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The cron key. + * @param \Drupal\Core\Session\AccountInterface $account + * The current user. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public function access(RouteMatchInterface $route_match, AccountInterface $account): AccessResultInterface { + return $this->hasAccessToChildRoutes($route_match->getRouteName(), $account)->cachePerPermissions(); + } + + /** + * Check that the given route has access to one of it's child routes. + * + * @param string $route_id + * The route ID. + * @param \Drupal\Core\Session\AccountInterface $account + * The account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public function hasAccessToChildRoutes(string $route_id, AccountInterface $account): AccessResultInterface { + $parameters = new MenuTreeParameters(); + $parameters->setRoot($route_id) + ->excludeRoot() + ->setTopLevelOnly() + ->onlyEnabledLinks(); + + $tree = $this->menuLinkTree->load(NULL, $parameters); + + if (empty($tree)) { + return AccessResult::allowed(); + } + + foreach ($tree as $menu_link_route_id => $element) { + $url = $element->link->getUrlObject(); + if (!$this->accessManager->checkNamedRoute($url->getRouteName(), $url->getRouteParameters(), $account)) { + continue; + } + + // Check if it's again route with inaccessible children. + return AccessResult::allowedIf($this->hasAccessToChildRoutes($menu_link_route_id, $account)->isAllowed()); + } + + return AccessResult::neutral(); + } + +} diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml index 00eddce..c6d95f8 100644 --- a/core/modules/system/system.routing.yml +++ b/core/modules/system/system.routing.yml @@ -37,6 +37,7 @@ system.admin: _title: 'Administration' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' system.admin_structure: path: '/admin/structure' @@ -45,6 +46,7 @@ system.admin_structure: _title: 'Structure' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' system.admin_reports: path: '/admin/reports' @@ -53,6 +55,7 @@ system.admin_reports: _title: 'Reports' requirements: _permission: 'access site reports' + _access_admin_menu_block_page: 'TRUE' system.admin_config_media: path: '/admin/config/media' @@ -61,6 +64,7 @@ system.admin_config_media: _title: 'Media' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' system.admin_config_services: path: '/admin/config/services' @@ -69,6 +73,7 @@ system.admin_config_services: _title: 'Web services' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' system.admin_config_development: path: '/admin/config/development' @@ -77,6 +82,7 @@ system.admin_config_development: _title: 'Development' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' system.admin_config_regional: path: '/admin/config/regional' @@ -85,6 +91,7 @@ system.admin_config_regional: _title: 'Regional and language' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' system.admin_config_search: path: '/admin/config/search' @@ -93,6 +100,7 @@ system.admin_config_search: _title: 'Search and metadata' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' system.admin_config_system: path: '/admin/config/system' @@ -101,6 +109,7 @@ system.admin_config_system: _title: 'System' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' system.admin_config_ui: path: '/admin/config/user-interface' @@ -109,6 +118,7 @@ system.admin_config_ui: _title: 'User interface' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' system.admin_config_workflow: path: '/admin/config/workflow' @@ -117,6 +127,7 @@ system.admin_config_workflow: _title: 'Workflow' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' system.admin_config_content: path: '/admin/config/content' @@ -125,6 +136,7 @@ system.admin_config_content: _title: 'Content authoring' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' system.cron: path: '/cron/{key}' @@ -331,6 +343,7 @@ system.admin_index: _title: 'Administration' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' system.files: path: '/system/files/{scheme}' @@ -464,6 +477,7 @@ system.admin_config: _title: 'Configuration' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' system.batch_page.html: path: '/batch' @@ -506,6 +520,7 @@ system.admin_content: _title: 'Content' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' system.entity_autocomplete: path: '/entity_reference_autocomplete/{target_type}/{selection_handler}/{selection_settings_key}' diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml index 31abbfe..2ed9299 100644 --- a/core/modules/system/system.services.yml +++ b/core/modules/system/system.services.yml @@ -7,6 +7,11 @@ services: class: Drupal\system\Access\DbUpdateAccessCheck tags: - { name: access_check, applies_to: _access_system_update } + access_check.admin_menu_block_page: + class: Drupal\system\Access\SystemAdminMenuBlockAccessCheck + arguments: ['@access_manager', '@menu.link_tree'] + tags: + - { name: access_check, applies_to: _access_admin_menu_block_page } system.manager: class: Drupal\system\SystemManager arguments: ['@module_handler', '@request_stack', '@menu.link_tree', '@menu.active_trail'] diff --git a/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php b/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php index ac85028..3d747ca 100644 --- a/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php +++ b/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php @@ -17,7 +17,7 @@ class MenuAccessTest extends BrowserTestBase { * * @var array */ - protected static $modules = ['block', 'menu_test']; + protected static $modules = ['block', 'filter', 'menu_test']; /** * {@inheritdoc} @@ -71,4 +71,60 @@ public function testMenuBlockLinksAccessCheck() { $this->assertSession()->linkByHrefNotExists('foo/asdf/c'); } + /** + * Test routes implementing _access_admin_menu_block_page. + */ + public function testSystemAdminMenuBlockAccessCheck() { + // Create an admin user. + $adminUser = $this->drupalCreateUser([], NULL, TRUE); + + // Create a user with 'administer menu' permission. + $menuAdmin = $this->drupalCreateUser([ + 'access administration pages', + 'administer menu', + ]); + + // Create a user with 'administer filters' permission. + $filterAdmin = $this->drupalCreateUser([ + 'access administration pages', + 'administer filters', + ]); + + // Create a user with 'access administration pages' permission. + $webUser = $this->drupalCreateUser([ + 'access administration pages', + ]); + + // An admin user has access to all parent pages. + $this->drupalLogin($adminUser); + $this->drupalGet('admin/structure'); + $this->assertSession()->statusCodeEquals(200); + $this->drupalGet('admin/people'); + $this->assertSession()->statusCodeEquals(200); + + // This user has access to administer menus so the structure parent page + // should be accessible. + $this->drupalLogin($menuAdmin); + $this->drupalGet('admin/structure'); + $this->assertSession()->statusCodeEquals(200); + $this->drupalGet('admin/people'); + $this->assertSession()->statusCodeEquals(403); + + // This user has access to administer filters so the config parent page + // should be accessible. + $this->drupalLogin($filterAdmin); + $this->drupalGet('admin/config'); + $this->assertSession()->statusCodeEquals(200); + $this->drupalGet('admin/people'); + $this->assertSession()->statusCodeEquals(403); + + // This user doesn't have access to any of the child pages, so the parent + // pages should not be accessible. + $this->drupalLogin($webUser); + $this->drupalGet('admin/structure'); + $this->assertSession()->statusCodeEquals(403); + $this->drupalGet('admin/people'); + $this->assertSession()->statusCodeEquals(403); + } + } diff --git a/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php b/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php index 974b6eb..4de3a52 100644 --- a/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php +++ b/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php @@ -518,7 +518,7 @@ public function testBrokenThenFixedUpdates() { \Drupal::keyValueExpirable('update_available_releases')->deleteAll(); // This cron run should retrieve fixed updates. $this->cronRun(); - $this->drupalGet('admin/structure'); + $this->drupalGet('admin/config'); $this->assertSession()->statusCodeEquals(200); $this->assertSession()->pageTextContains('There is a security update available for your version of Drupal.'); } diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml index 04a094a..f27163a 100644 --- a/core/modules/user/user.routing.yml +++ b/core/modules/user/user.routing.yml @@ -20,6 +20,7 @@ user.admin_index: _title: 'People' requirements: _permission: 'access administration pages' + _access_admin_menu_block_page: 'TRUE' entity.user.admin_form: path: '/admin/config/people/accounts'