/** * Tests the matchRequest() function for access denied. * * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ public function testMatchRequestDenied() { $this->setupRouter(); $request = new Request(); $this->accessManager->expects($this->once())->method('checkRequest')->with($request)->will($this->returnValue(FALSE)); $this->router->matchRequest($request); }
/** * {@inheritdoc} */ protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { switch ($operation) { case 'view': // There is no direct viewing of a menu link, but still for purposes of // content_translation we need a generic way to check access. return AccessResult::allowedIfHasPermission($account, 'administer menu'); case 'update': if (!$account->hasPermission('administer menu')) { return AccessResult::neutral()->cachePerPermissions(); } else { // If there is a URL, this is an external link so always accessible. $access = AccessResult::allowed()->cachePerPermissions()->addCacheableDependency($entity); /** @var \Drupal\menu_link_content\MenuLinkContentInterface $entity */ // We allow access, but only if the link is accessible as well. if (($url_object = $entity->getUrlObject()) && $url_object->isRouted()) { $link_access = $this->accessManager->checkNamedRoute($url_object->getRouteName(), $url_object->getRouteParameters(), $account, TRUE); $access = $access->andIf($link_access); } return $access; } case 'delete': return AccessResult::allowedIf(!$entity->isNew() && $account->hasPermission('administer menu'))->cachePerPermissions()->addCacheableDependency($entity); } }
/** * {@inheritdoc} */ public function render($empty = FALSE) { $account = \Drupal::currentUser(); if (!$empty || !empty($this->options['empty'])) { $element = array('#theme' => 'links', '#links' => array(array('url' => Url::fromRoute('node.add_page'), 'title' => $this->t('Add content'))), '#access' => $this->accessManager->checkNamedRoute('node.add_page', array(), $account)); return $element; } return array(); }
/** * {@inheritdoc} */ public function render($empty = FALSE) { if (!$empty || !empty($this->options['empty'])) { /** @var \Drupal\Core\Access\AccessResultInterface|\Drupal\Core\Cache\CacheableDependencyInterface $access_result */ $access_result = $this->accessManager->checkNamedRoute('block_content.add_page', array(), $this->currentUser, TRUE); $element = array('#markup' => $this->t('Add a <a href=":url">custom block</a>.', array(':url' => Url::fromRoute('block_content.add_page')->toString())), '#access' => $access_result->isAllowed(), '#cache' => ['contexts' => $access_result->getCacheContexts(), 'tags' => $access_result->getCacheTags(), 'max-age' => $access_result->getCacheMaxAge()]); return $element; } return array(); }
/** * Tests the matchRequest() function for access denied. * * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ public function testMatchRequestDenied() { $this->setupRouter(); $request = new Request(); $access_result = AccessResult::forbidden(); $this->accessManager->expects($this->once())->method('checkRequest')->with($request)->willReturn($access_result); $parameters = $this->router->matchRequest($request); $expected = [AccessAwareRouterInterface::ACCESS_RESULT => $access_result]; $this->assertSame($expected, $request->attributes->all()); $this->assertSame($expected, $parameters); }
/** * {@inheritdoc} */ protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { switch ($operation) { case 'view': // There is no direct view. return FALSE; case 'update': // If there is a URL, this is an external link so always accessible. return $account->hasPermission('administer menu') && ($entity->getUrl() || $this->accessManager->checkNamedRoute($entity->getRouteName(), $entity->getRouteParameters(), $account)); case 'delete': return !$entity->isNew() && $account->hasPermission('administer menu'); } }
protected function setupFactoryAndLocalTaskPlugins(array $definitions, $active_plugin_id) { $map = []; $access_manager_map = []; foreach ($definitions as $plugin_id => $info) { $info += ['access' => AccessResult::allowed()]; $mock = $this->prophesize(LocalTaskInterface::class); $mock->willImplement(CacheableDependencyInterface::class); $mock->getRouteName()->willReturn($info['route_name']); $mock->getTitle()->willReturn($info['title']); $mock->getRouteParameters(Argument::cetera())->willReturn([]); $mock->getOptions(Argument::cetera())->willReturn([]); $mock->getActive()->willReturn($plugin_id === $active_plugin_id); $mock->getWeight()->willReturn(isset($info['weight']) ? $info['weight'] : 0); $mock->getCacheContexts()->willReturn(isset($info['cache_contexts']) ? $info['cache_contexts'] : []); $mock->getCacheTags()->willReturn(isset($info['cache_tags']) ? $info['cache_tags'] : []); $mock->getCacheMaxAge()->willReturn(isset($info['cache_max_age']) ? $info['cache_max_age'] : Cache::PERMANENT); $access_manager_map[] = [$info['route_name'], [], $this->account, TRUE, $info['access']]; $map[] = [$info['id'], [], $mock->reveal()]; } $this->accessManager->expects($this->any()) ->method('checkNamedRoute') ->willReturnMap($access_manager_map); $this->factory->expects($this->any()) ->method('createInstance') ->will($this->returnValueMap($map)); }
/** * Tests the checkAccess() tree manipulator. * * @covers ::checkAccess */ public function testCheckAccess() { // Those menu links that are non-external will have their access checks // performed. 8 routes, but 1 is external, 2 already have their 'access' // property set, and 1 is a child if an inaccessible menu link, so only 4 // calls will be made. $this->accessManager->expects($this->exactly(4))->method('checkNamedRoute')->will($this->returnValueMap(array(array('example1', array(), $this->currentUser, NULL, FALSE), array('example2', array('foo' => 'bar'), $this->currentUser, NULL, TRUE), array('example3', array('baz' => 'qux'), $this->currentUser, NULL, FALSE), array('example5', array(), $this->currentUser, NULL, TRUE)))); $this->mockTree(); $this->originalTree[5]->subtree[7]->access = TRUE; $this->originalTree[8]->access = FALSE; $tree = $this->defaultMenuTreeManipulators->checkAccess($this->originalTree); // Menu link 1: route without parameters, access forbidden, hence removed. $this->assertFalse(array_key_exists(1, $tree)); // Menu link 2: route with parameters, access granted. $element = $tree[2]; $this->assertTrue($element->access); // Menu link 3: route with parameters, access forbidden, hence removed, // including its children. $this->assertFalse(array_key_exists(3, $tree[2]->subtree)); // Menu link 4: child of menu link 3, which already is removed. $this->assertSame(array(), $tree[2]->subtree); // Menu link 5: no route name, treated as external, hence access granted. $element = $tree[5]; $this->assertTrue($element->access); // Menu link 6: external URL, hence access granted. $element = $tree[6]; $this->assertTrue($element->access); // Menu link 7: 'access' already set. $element = $tree[5]->subtree[7]; $this->assertTrue($element->access); // Menu link 8: 'access' already set, to FALSE, hence removed. $this->assertFalse(array_key_exists(8, $tree)); }
/** * Language translations overview page for a configuration name. * * @param \Symfony\Component\HttpFoundation\Request $request * Page request object. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The route match. * @param string $plugin_id * The plugin ID of the mapper. * * @return array * Page render array. */ public function itemPage(Request $request, RouteMatchInterface $route_match, $plugin_id) { /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */ $mapper = $this->configMapperManager->createInstance($plugin_id); $mapper->populateFromRequest($request); $page = array(); $page['#title'] = $this->t('Translations for %label', array('%label' => $mapper->getTitle())); $languages = $this->languageManager->getLanguages(); if (count($languages) == 1) { drupal_set_message($this->t('In order to translate configuration, the website must have at least two <a href="@url">languages</a>.', array('@url' => $this->url('entity.configurable_language.collection'))), 'warning'); } $original_langcode = $mapper->getLangcode(); if (!isset($languages[$original_langcode])) { // If the language is not configured on the site, create a dummy language // object for this listing only to ensure the user gets useful info. $language_name = $this->languageManager->getLanguageName($original_langcode); $languages[$original_langcode] = new Language(array('id' => $original_langcode, 'name' => $language_name)); } // We create a fake request object to pass into // ConfigMapperInterface::populateFromRequest() for the different languages. // Creating a separate request for each language and route is neither easily // possible nor performant. $fake_request = $request->duplicate(); $page['languages'] = array('#type' => 'table', '#header' => array($this->t('Language'), $this->t('Operations'))); foreach ($languages as $language) { $langcode = $language->getId(); // This is needed because // ConfigMapperInterface::getAddRouteParameters(), for example, // needs to return the correct language code for each table row. $fake_request->attributes->set('langcode', $langcode); $mapper->populateFromRequest($fake_request); // Prepare the language name and the operations depending on whether this // is the original language or not. if ($langcode == $original_langcode) { $language_name = '<strong>' . $this->t('@language (original)', array('@language' => $language->getName())) . '</strong>'; // Check access for the path/route for editing, so we can decide to // include a link to edit or not. $edit_access = $this->accessManager->checkNamedRoute($mapper->getBaseRouteName(), $route_match->getRawParameters()->all(), $this->account); // Build list of operations. $operations = array(); if ($edit_access) { $operations['edit'] = array('title' => $this->t('Edit'), 'url' => Url::fromRoute($mapper->getBaseRouteName(), $mapper->getBaseRouteParameters(), ['query' => ['destination' => $mapper->getOverviewPath()]])); } } else { $language_name = $language->getName(); $operations = array(); // If no translation exists for this language, link to add one. if (!$mapper->hasTranslation($language)) { $operations['add'] = array('title' => $this->t('Add'), 'url' => Url::fromRoute($mapper->getAddRouteName(), $mapper->getAddRouteParameters())); } else { // Otherwise, link to edit the existing translation. $operations['edit'] = array('title' => $this->t('Edit'), 'url' => Url::fromRoute($mapper->getEditRouteName(), $mapper->getEditRouteParameters())); $operations['delete'] = array('title' => $this->t('Delete'), 'url' => Url::fromRoute($mapper->getDeleteRouteName(), $mapper->getDeleteRouteParameters())); } } $page['languages'][$langcode]['language'] = array('#markup' => $language_name); $page['languages'][$langcode]['operations'] = array('#type' => 'operations', '#links' => $operations); } return $page; }
/** * {@inheritdoc} */ public function getTasksBuild($current_route_name, RefinableCacheableDependencyInterface &$cacheability) { $tree = $this->getLocalTasksForRoute($current_route_name); $build = array(); // Collect all route names. $route_names = array(); foreach ($tree as $instances) { foreach ($instances as $child) { $route_names[] = $child->getRouteName(); } } // Pre-fetch all routes involved in the tree. This reduces the number // of SQL queries that would otherwise be triggered by the access manager. $routes = $route_names ? $this->routeProvider->getRoutesByNames($route_names) : array(); foreach ($tree as $level => $instances) { /** @var $instances \Drupal\Core\Menu\LocalTaskInterface[] */ foreach ($instances as $plugin_id => $child) { $route_name = $child->getRouteName(); $route_parameters = $child->getRouteParameters($this->routeMatch); // Given that the active flag depends on the route we have to add the // route cache context. $cacheability->addCacheContexts(['route']); $active = $this->isRouteActive($current_route_name, $route_name, $route_parameters); // The plugin may have been set active in getLocalTasksForRoute() if // one of its child tabs is the active tab. $active = $active || $child->getActive(); // @todo It might make sense to use link render elements instead. $link = ['title' => $this->getTitle($child), 'url' => Url::fromRoute($route_name, $route_parameters), 'localized_options' => $child->getOptions($this->routeMatch)]; $access = $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account, TRUE); $build[$level][$plugin_id] = ['#theme' => 'menu_local_task', '#link' => $link, '#active' => $active, '#weight' => $child->getWeight(), '#access' => $access]; $cacheability->addCacheableDependency($access)->addCacheableDependency($child); } } return $build; }
/** * {@inheritdoc} */ public function getTasksBuild($current_route_name) { $tree = $this->getLocalTasksForRoute($current_route_name); $build = array(); // Collect all route names. $route_names = array(); foreach ($tree as $instances) { foreach ($instances as $child) { $route_names[] = $child->getRouteName(); } } // Pre-fetch all routes involved in the tree. This reduces the number // of SQL queries that would otherwise be triggered by the access manager. $routes = $route_names ? $this->routeProvider->getRoutesByNames($route_names) : array(); foreach ($tree as $level => $instances) { /** @var $instances \Drupal\Core\Menu\LocalTaskInterface[] */ foreach ($instances as $plugin_id => $child) { $route_name = $child->getRouteName(); $route_parameters = $child->getRouteParameters($this->routeMatch); // Find out whether the user has access to the task. $access = $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account); if ($access) { $active = $this->isRouteActive($current_route_name, $route_name, $route_parameters); // The plugin may have been set active in getLocalTasksForRoute() if // one of its child tabs is the active tab. $active = $active || $child->getActive(); // @todo It might make sense to use link render elements instead. $link = array('title' => $this->getTitle($child), 'url' => Url::fromRoute($route_name, $route_parameters), 'localized_options' => $child->getOptions($this->routeMatch)); $build[$level][$plugin_id] = array('#theme' => 'menu_local_task', '#link' => $link, '#active' => $active, '#weight' => $child->getWeight(), '#access' => $access); } } } return $build; }
/** * {@inheritdoc} */ public function getActionsForRoute($route_appears) { if (!isset($this->instances[$route_appears])) { $route_names = array(); $this->instances[$route_appears] = array(); // @todo - optimize this lookup by compiling or caching. foreach ($this->getDefinitions() as $plugin_id => $action_info) { if (in_array($route_appears, $action_info['appears_on'])) { $plugin = $this->createInstance($plugin_id); $route_names[] = $plugin->getRouteName(); $this->instances[$route_appears][$plugin_id] = $plugin; } } // Pre-fetch all the action route objects. This reduces the number of SQL // queries that would otherwise be triggered by the access manager. if (!empty($route_names)) { $this->routeProvider->getRoutesByNames($route_names); } } $links = array(); /** @var $plugin \Drupal\Core\Menu\LocalActionInterface */ foreach ($this->instances[$route_appears] as $plugin_id => $plugin) { $route_name = $plugin->getRouteName(); $route_parameters = $plugin->getRouteParameters($this->routeMatch); $links[$plugin_id] = array('#theme' => 'menu_local_action', '#link' => array('title' => $this->getTitle($plugin), 'url' => Url::fromRoute($route_name, $route_parameters), 'localized_options' => $plugin->getOptions($this->routeMatch)), '#access' => $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account), '#weight' => $plugin->getWeight()); } return $links; }
/** * {@inheritdoc} */ public function isValid($path) { // External URLs and the front page are always valid. if ($path == '<front>' || UrlHelper::isExternal($path)) { return TRUE; } // Check the routing system. $collection = $this->routeProvider->getRoutesByPattern('/' . $path); if ($collection->count() == 0) { return FALSE; } $request = RequestHelper::duplicate($this->requestStack->getCurrentRequest(), '/' . $path); $request->attributes->set('_system_path', $path); // We indicate that a menu administrator is running the menu access check. $request->attributes->set('_menu_admin', TRUE); // Attempt to match this path to provide a fully built request to the // access checker. try { $request->attributes->add($this->requestMatcher->matchRequest($request)); } catch (ParamNotConvertedException $e) { return FALSE; } // Consult the access manager. $routes = $collection->all(); $route = reset($routes); return $this->accessManager->check($route, $request, $this->account); }
/** * Tests the optimized node access checking. * * @covers ::checkNodeAccess * @covers ::collectNodeLinks * @covers ::checkAccess */ public function testCheckNodeAccess() { $links = array(1 => MenuLinkMock::create(array('id' => 'node.1', 'route_name' => 'entity.node.canonical', 'title' => 'foo', 'parent' => '', 'route_parameters' => array('node' => 1))), 2 => MenuLinkMock::create(array('id' => 'node.2', 'route_name' => 'entity.node.canonical', 'title' => 'bar', 'parent' => '', 'route_parameters' => array('node' => 2))), 3 => MenuLinkMock::create(array('id' => 'node.3', 'route_name' => 'entity.node.canonical', 'title' => 'baz', 'parent' => 'node.2', 'route_parameters' => array('node' => 3))), 4 => MenuLinkMock::create(array('id' => 'node.4', 'route_name' => 'entity.node.canonical', 'title' => 'qux', 'parent' => 'node.3', 'route_parameters' => array('node' => 4))), 5 => MenuLinkMock::create(array('id' => 'test.1', 'route_name' => 'test_route', 'title' => 'qux', 'parent' => '')), 6 => MenuLinkMock::create(array('id' => 'test.2', 'route_name' => 'test_route', 'title' => 'qux', 'parent' => 'test.1'))); $tree = array(); $tree[1] = new MenuLinkTreeElement($links[1], FALSE, 1, FALSE, array()); $tree[2] = new MenuLinkTreeElement($links[2], TRUE, 1, FALSE, array(3 => new MenuLinkTreeElement($links[3], TRUE, 2, FALSE, array(4 => new MenuLinkTreeElement($links[4], FALSE, 3, FALSE, array()))))); $tree[5] = new MenuLinkTreeElement($links[5], TRUE, 1, FALSE, array(6 => new MenuLinkTreeElement($links[6], FALSE, 2, FALSE, array()))); $query = $this->getMock('Drupal\\Core\\Entity\\Query\\QueryInterface'); $query->expects($this->at(0))->method('condition')->with('nid', array(1, 2, 3, 4)); $query->expects($this->at(1))->method('condition')->with('status', NODE_PUBLISHED); $query->expects($this->once())->method('execute')->willReturn(array(1, 2, 4)); $this->queryFactory->expects($this->once())->method('get')->with('node')->willReturn($query); $node_access_result = AccessResult::allowed()->cachePerPermissions()->addCacheContexts(['user.node_grants:view']); $tree = $this->defaultMenuTreeManipulators->checkNodeAccess($tree); $this->assertEquals($node_access_result, $tree[1]->access); $this->assertEquals($node_access_result, $tree[2]->access); // Ensure that access denied is set. $this->assertEquals(AccessResult::neutral(), $tree[2]->subtree[3]->access); $this->assertEquals($node_access_result, $tree[2]->subtree[3]->subtree[4]->access); // Ensure that other routes than entity.node.canonical are set as well. $this->assertNull($tree[5]->access); $this->assertNull($tree[5]->subtree[6]->access); // On top of the node access checking now run the ordinary route based // access checkers. // Ensure that the access manager is just called for the non-node routes. $this->accessManager->expects($this->at(0))->method('checkNamedRoute')->with('test_route', [], $this->currentUser, TRUE)->willReturn(AccessResult::allowed()); $this->accessManager->expects($this->at(1))->method('checkNamedRoute')->with('test_route', [], $this->currentUser, TRUE)->willReturn(AccessResult::neutral()); $tree = $this->defaultMenuTreeManipulators->checkAccess($tree); $this->assertEquals($node_access_result, $tree[1]->access); $this->assertEquals($node_access_result, $tree[2]->access); $this->assertEquals(AccessResult::neutral(), $tree[2]->subtree[3]->access); $this->assertEquals(AccessResult::allowed()->cachePerPermissions(), $tree[5]->access); $this->assertEquals(AccessResult::neutral()->cachePerPermissions(), $tree[5]->subtree[6]->access); }
/** * Tests the matchRequest() function for access denied with reason message. */ public function testCheckAccessResultWithReason() { $this->setupRouter(); $request = new Request(); $reason = $this->getRandomGenerator()->string(); $access_result = AccessResult::forbidden($reason); $this->accessManager->expects($this->once())->method('checkRequest')->with($request)->willReturn($access_result); $this->setExpectedException(AccessDeniedHttpException::class, $reason); $this->router->matchRequest($request); }
/** * Tests that if access is granted, AccessSubscriber will not throw an exception. */ public function testAccessSubscriberDoesNotAlterRequestIfAccessManagerGrantsAccess() { $this->parameterBag->expects($this->once())->method('has')->with(RouteObjectInterface::ROUTE_OBJECT)->will($this->returnValue(TRUE)); $this->parameterBag->expects($this->once())->method('get')->with(RouteObjectInterface::ROUTE_OBJECT)->will($this->returnValue($this->route)); $this->accessManager->expects($this->once())->method('check')->with($this->equalTo($this->route))->will($this->returnValue(TRUE)); $subscriber = new AccessSubscriber($this->accessManager, $this->currentUser); // We're testing that no exception is thrown in this case. There is no // return. $subscriber->onKernelRequestAccessCheck($this->event); }
/** * {@inheritdoc} */ protected function setUp() { $this->configFactory = $this->getConfigFactoryStub(['system.site' => ['page.403' => '/access-denied-page', 'page.404' => '/not-found-page']]); $this->kernel = $this->getMock('Symfony\\Component\\HttpKernel\\HttpKernelInterface'); $this->logger = $this->getMock('Psr\\Log\\LoggerInterface'); $this->redirectDestination = $this->getMock('\\Drupal\\Core\\Routing\\RedirectDestinationInterface'); $this->redirectDestination->expects($this->any())->method('getAsArray')->willReturn(['destination' => 'test']); $this->accessUnawareRouter = $this->getMock('Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface'); $this->accessUnawareRouter->expects($this->any())->method('match')->willReturn(['_controller' => 'mocked']); $this->accessManager = $this->getMock('Drupal\\Core\\Access\\AccessManagerInterface'); $this->accessManager->expects($this->any())->method('checkNamedRoute')->willReturn(AccessResult::allowed()->addCacheTags(['foo', 'bar'])); $this->customPageSubscriber = new CustomPageExceptionHtmlSubscriber($this->configFactory, $this->kernel, $this->logger, $this->redirectDestination, $this->accessUnawareRouter, $this->accessManager); $path_validator = $this->getMock('Drupal\\Core\\Path\\PathValidatorInterface'); $path_validator->expects($this->any())->method('getUrlIfValidWithoutAccessCheck')->willReturn(Url::fromRoute('foo', ['foo' => 'bar'])); $container = new ContainerBuilder(); $container->set('path.validator', $path_validator); \Drupal::setContainer($container); // You can't create an exception in PHP without throwing it. Store the // current error_log, and disable it temporarily. $this->errorLog = ini_set('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); }
/** * Checks access for one menu link instance. * * @param \Drupal\Core\Menu\MenuLinkInterface $instance * The menu link instance. * * @return bool * TRUE if the current user can access the link, FALSE otherwise. */ protected function menuLinkCheckAccess(MenuLinkInterface $instance) { // Use the definition here since that's a lot faster than creating a Url // object that we don't need. $definition = $instance->getPluginDefinition(); // 'url' should only be populated for external links. if (!empty($definition['url']) && empty($definition['route_name'])) { $access = TRUE; } else { $access = $this->accessManager->checkNamedRoute($definition['route_name'], $definition['route_parameters'], $this->account); } return $access; }
/** * Apply access check service to the route and parameters in the request. * * @param \Symfony\Component\HttpFoundation\Request $request * The request to access check. */ protected function checkAccess(Request $request) { // The cacheability (if any) of this request's access check result must be // applied to the response. $access_result = $this->accessManager->checkRequest($request, $this->account, TRUE); // Allow a master request to set the access result for a subrequest: if an // access result attribute is already set, don't overwrite it. if (!$request->attributes->has(AccessAwareRouterInterface::ACCESS_RESULT)) { $request->attributes->set(AccessAwareRouterInterface::ACCESS_RESULT, $access_result); } if (!$access_result->isAllowed()) { throw new AccessDeniedHttpException($access_result instanceof AccessResultReasonInterface ? $access_result->getReason() : NULL); } }
/** * {@inheritdoc} */ protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { switch ($operation) { case 'view': // There is no direct view. return AccessResult::neutral(); case 'update': if (!$account->hasPermission('administer menu')) { return AccessResult::neutral()->cachePerRole(); } else { // If there is a URL, this is an external link so always accessible. $access = AccessResult::allowed()->cachePerRole()->cacheUntilEntityChanges($entity); if (!$entity->getUrl()) { // We allow access, but only if the link is accessible as well. $link_access = $this->accessManager->checkNamedRoute($entity->getRouteName(), $entity->getRouteParameters(), $account, TRUE); return $access->andIf($link_access); } return $access; } case 'delete': return AccessResult::allowedIf(!$entity->isNew() && $account->hasPermission('administer menu'))->cachePerRole()->cacheUntilEntityChanges($entity); } }
/** * {@inheritdoc} */ protected function setUp() { $this->controllerResolver = $this->getMock('Drupal\\Core\\Controller\\ControllerResolverInterface'); $this->request = $this->getMock('Symfony\\Component\\HttpFoundation\\Request'); $this->routeProvider = $this->getMock('Drupal\\Core\\Routing\\RouteProviderInterface'); $this->moduleHandler = $this->getMock('Drupal\\Core\\Extension\\ModuleHandlerInterface'); $this->cacheBackend = $this->getMock('Drupal\\Core\\Cache\\CacheBackendInterface'); $this->accessManager = $this->getMock('Drupal\\Core\\Access\\AccessManagerInterface'); $this->accessManager->expects($this->any())->method('checkNamedRoute')->will($this->returnValue(FALSE)); $this->account = $this->getMock('Drupal\\Core\\Session\\AccountInterface'); $this->discovery = $this->getMock('Drupal\\Component\\Plugin\\Discovery\\DiscoveryInterface'); $this->factory = $this->getMock('Drupal\\Component\\Plugin\\Factory\\FactoryInterface'); $this->localActionManager = new TestLocalActionManager($this->controllerResolver, $this->request, $this->routeProvider, $this->moduleHandler, $this->cacheBackend, $this->accessManager, $this->account, $this->discovery, $this->factory); }
/** * Verifies that the current user can access the requested path. * * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event * The Event to process. * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException * Thrown when the access got denied. */ public function onKernelRequestAccessCheck(GetResponseEvent $event) { $request = $event->getRequest(); // The controller is being handled by the HTTP kernel, so add an attribute // to tell us this is the controller request. $request->attributes->set('_controller_request', TRUE); if (!$request->attributes->has(RouteObjectInterface::ROUTE_OBJECT)) { // If no Route is available it is likely a static resource and access is // handled elsewhere. return; } // Wrap this in a try/catch to ensure the '_controller_request' attribute // can always be removed. try { $access = $this->accessManager->check($request->attributes->get(RouteObjectInterface::ROUTE_OBJECT), $request, $this->currentUser); } catch (\Exception $e) { $request->attributes->remove('_controller_request'); throw $e; } $request->attributes->remove('_controller_request'); if (!$access) { throw new AccessDeniedHttpException(); } }
/** * Checks access for one menu link instance. * * @param \Drupal\Core\Menu\MenuLinkInterface $instance * The menu link instance. * * @return \Drupal\Core\Access\AccessResultInterface * The access result. */ protected function menuLinkCheckAccess(MenuLinkInterface $instance) { $access_result = NULL; if ($this->account->hasPermission('link to any page')) { $access_result = AccessResult::allowed(); } else { $url = $instance->getUrlObject(); // When no route name is specified, this must be an external link. if (!$url->isRouted()) { $access_result = AccessResult::allowed(); } else { $access_result = $this->accessManager->checkNamedRoute($url->getRouteName(), $url->getRouteParameters(), $this->account, TRUE); } } return $access_result->cachePerPermissions(); }
/** * {@inheritdoc} */ public function build(RouteMatchInterface $route_match) { $breadcrumb = new Breadcrumb(); $links = array(); // General path-based breadcrumbs. Use the actual request path, prior to // resolving path aliases, so the breadcrumb can be defined by simply // creating a hierarchy of path aliases. $path = trim($this->context->getPathInfo(), '/'); $path_elements = explode('/', $path); $exclude = array(); // Don't show a link to the front-page path. $front = $this->config->get('page.front'); $exclude[$front] = TRUE; // /user is just a redirect, so skip it. // @todo Find a better way to deal with /user. $exclude['/user'] = TRUE; // Because this breadcrumb builder is entirely path-based, vary by the // 'url.path' cache context. $breadcrumb->addCacheContexts(['url.path']); while (count($path_elements) > 1) { array_pop($path_elements); // Copy the path elements for up-casting. $route_request = $this->getRequestForPath('/' . implode('/', $path_elements), $exclude); if ($route_request) { $route_match = RouteMatch::createFromRequest($route_request); $access = $this->accessManager->check($route_match, $this->currentUser, NULL, TRUE); // The set of breadcrumb links depends on the access result, so merge // the access result's cacheability metadata. $breadcrumb = $breadcrumb->addCacheableDependency($access); if ($access->isAllowed()) { $title = $this->titleResolver->getTitle($route_request, $route_match->getRouteObject()); if (!isset($title)) { // Fallback to using the raw path component as the title if the // route is missing a _title or _title_callback attribute. $title = str_replace(array('-', '_'), ' ', Unicode::ucfirst(end($path_elements))); } $url = Url::fromRouteMatch($route_match); $links[] = new Link($title, $url); } } } if ($path && '/' . $path != $front) { // Add the Home link, except for the front page. $links[] = Link::createFromRoute($this->t('Home'), '<front>'); } return $breadcrumb->setLinks(array_reverse($links)); }
/** * {@inheritdoc} */ public function getContextualLinksArrayByGroup($group_name, array $route_parameters, array $metadata = array()) { $links = array(); $request = $this->requestStack->getCurrentRequest(); foreach ($this->getContextualLinkPluginsByGroup($group_name) as $plugin_id => $plugin_definition) { /** @var $plugin \Drupal\Core\Menu\ContextualLinkInterface */ $plugin = $this->createInstance($plugin_id); $route_name = $plugin->getRouteName(); // Check access. if (!$this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account)) { continue; } $links[$plugin_id] = array('route_name' => $route_name, 'route_parameters' => $route_parameters, 'title' => $plugin->getTitle($request), 'weight' => $plugin->getWeight(), 'localized_options' => $plugin->getOptions(), 'metadata' => $metadata); } $this->moduleHandler->alter('contextual_links', $links, $group_name, $route_parameters); return $links; }
/** * {@inheritdoc} */ public function build(RouteMatchInterface $route_match) { $links = array(); // General path-based breadcrumbs. Use the actual request path, prior to // resolving path aliases, so the breadcrumb can be defined by simply // creating a hierarchy of path aliases. $path = trim($this->context->getPathInfo(), '/'); $path_elements = explode('/', $path); $exclude = array(); // Don't show a link to the front-page path. $front = $this->config->get('page.front'); $exclude[$front] = TRUE; // /user is just a redirect, so skip it. // @todo Find a better way to deal with /user. $exclude['user'] = TRUE; while (count($path_elements) > 1) { array_pop($path_elements); // Copy the path elements for up-casting. $route_request = $this->getRequestForPath(implode('/', $path_elements), $exclude); if ($route_request) { $route_name = $route_request->attributes->get(RouteObjectInterface::ROUTE_NAME); // Note that the parameters don't really matter here since we're // passing in the request which already has the upcast attributes. $parameters = array(); $access = $this->accessManager->checkNamedRoute($route_name, $parameters, $this->currentUser, $route_request); if ($access) { $title = $this->titleResolver->getTitle($route_request, $route_request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)); } if ($access) { if (!isset($title)) { // Fallback to using the raw path component as the title if the // route is missing a _title or _title_callback attribute. $title = str_replace(array('-', '_'), ' ', Unicode::ucfirst(end($path_elements))); } // @todo Replace with a #type => link render element so that the alter // hook can work with the actual data. $links[] = $this->l($title, $route_request->attributes->get(RouteObjectInterface::ROUTE_NAME), $route_request->attributes->get('_raw_variables')->all(), array('html' => TRUE)); } } } if ($path && $path != $front) { // Add the Home link, except for the front page. $links[] = $this->l($this->t('Home'), '<front>'); } return array_reverse($links); }
/** * Checks access for one menu link instance. * * @param \Drupal\Core\Menu\MenuLinkInterface $instance * The menu link instance. * * @return \Drupal\Core\Access\AccessResultInterface * The access result. */ protected function menuLinkCheckAccess(MenuLinkInterface $instance) { $access_result = NULL; if ($this->account->hasPermission('link to any page')) { $access_result = AccessResult::allowed(); } else { // Use the definition here since that's a lot faster than creating a Url // object that we don't need. $definition = $instance->getPluginDefinition(); // 'url' should only be populated for external links. if (!empty($definition['url']) && empty($definition['route_name'])) { $access_result = AccessResult::allowed(); } else { $access_result = $this->accessManager->checkNamedRoute($definition['route_name'], $definition['route_parameters'], $this->account, TRUE); } } return $access_result->cachePerPermissions(); }
/** * {@inheritdoc} */ protected function renderLink(EntityInterface $entity, ResultRow $values) { if (empty($entity)) { return; } // Check access when we pull up the user account so we know // if the user has made the contact page available. if (!$this->accessManager->checkNamedRoute('entity.user.contact_form', array('user' => $entity->id()), $this->currentUser())) { return; } $this->options['alter']['make_link'] = TRUE; $this->options['alter']['path'] = "user/{$entity->id()}/contact"; $title = $this->t('Contact %user', array('%user' => $entity->name->value)); $this->options['alter']['attributes'] = array('title' => $title); if (!empty($this->options['text'])) { return $this->options['text']; } else { return $title; } }
/** * {@inheritdoc} */ public function getActionsForRoute($route_appears) { if (!isset($this->instances[$route_appears])) { $route_names = array(); $this->instances[$route_appears] = array(); // @todo - optimize this lookup by compiling or caching. foreach ($this->getDefinitions() as $plugin_id => $action_info) { if (in_array($route_appears, $action_info['appears_on'])) { $plugin = $this->createInstance($plugin_id); $route_names[] = $plugin->getRouteName(); $this->instances[$route_appears][$plugin_id] = $plugin; } } // Pre-fetch all the action route objects. This reduces the number of SQL // queries that would otherwise be triggered by the access manager. if (!empty($route_names)) { $this->routeProvider->getRoutesByNames($route_names); } } $links = array(); /** @var $plugin \Drupal\Core\Menu\LocalActionInterface */ foreach ($this->instances[$route_appears] as $plugin_id => $plugin) { $cacheability = new CacheableMetadata(); $route_name = $plugin->getRouteName(); $route_parameters = $plugin->getRouteParameters($this->routeMatch); $access = $this->accessManager->checkNamedRoute($route_name, $route_parameters, $this->account, TRUE); $links[$plugin_id] = array('#theme' => 'menu_local_action', '#link' => array('title' => $this->getTitle($plugin), 'url' => Url::fromRoute($route_name, $route_parameters), 'localized_options' => $plugin->getOptions($this->routeMatch)), '#access' => $access, '#weight' => $plugin->getWeight()); $cacheability->addCacheableDependency($access); // For backward compatibility in 8.0.x, plugins that do not implement // the \Drupal\Core\Cache\CacheableDependencyInterface are assumed // to be cacheable forever. if ($plugin instanceof CacheableDependencyInterface) { $cacheability->addCacheableDependency($plugin); } else { $cacheability->setCacheMaxAge(Cache::PERMANENT); } $cacheability->applyTo($links[$plugin_id]); } $links['#cache']['contexts'][] = 'route'; return $links; }
/** * Tests the access checking of the getContextualLinksArrayByGroup method. * * @see \Drupal\Core\Menu\ContextualLinkManager::getContextualLinksArrayByGroup() */ public function testGetContextualLinksArrayByGroupAccessCheck() { $definitions = array('test_plugin1' => array('id' => 'test_plugin1', 'class' => '\\Drupal\\Core\\Menu\\ContextualLinkDefault', 'title' => 'Plugin 1', 'weight' => 0, 'group' => 'group1', 'route_name' => 'test_route', 'options' => array()), 'test_plugin2' => array('id' => 'test_plugin2', 'class' => '\\Drupal\\Core\\Menu\\ContextualLinkDefault', 'title' => 'Plugin 2', 'weight' => 2, 'group' => 'group1', 'route_name' => 'test_route2', 'options' => array('key' => 'value'))); $this->pluginDiscovery->expects($this->once())->method('getDefinitions')->will($this->returnValue($definitions)); $this->accessManager->expects($this->any())->method('checkNamedRoute')->will($this->returnValueMap(array(array('test_route', array('key' => 'value'), $this->account, FALSE, TRUE), array('test_route2', array('key' => 'value'), $this->account, FALSE, FALSE)))); // Set up mocking of the plugin factory. $map = array(); foreach ($definitions as $plugin_id => $definition) { $plugin = $this->getMock('Drupal\\Core\\Menu\\ContextualLinkInterface'); $plugin->expects($this->any())->method('getRouteName')->will($this->returnValue($definition['route_name'])); $plugin->expects($this->any())->method('getTitle')->will($this->returnValue($definition['title'])); $plugin->expects($this->any())->method('getWeight')->will($this->returnValue($definition['weight'])); $plugin->expects($this->any())->method('getOptions')->will($this->returnValue($definition['options'])); $map[] = array($plugin_id, array(), $plugin); } $this->factory->expects($this->any())->method('createInstance')->will($this->returnValueMap($map)); $result = $this->contextualLinkManager->getContextualLinksArrayByGroup('group1', array('key' => 'value')); // Ensure that access checking was respected. $this->assertTrue(isset($result['test_plugin1'])); $this->assertFalse(isset($result['test_plugin2'])); }