Issue #2323721 by dawehner, chx: Fixed [sechole] Link field item and menu link information leakage.

8.0.x
webchick 2014-08-28 23:13:46 -07:00
parent aca1ec38eb
commit 68f576fe81
28 changed files with 723 additions and 283 deletions

View File

@ -506,7 +506,7 @@ services:
arguments: ['@config.factory']
path.validator:
class: Drupal\Core\Path\PathValidator
arguments: ['@router', '@router.route_provider', '@request_stack']
arguments: ['@router', '@router.no_access_checks', '@current_user', '@path_processor_manager']
# The argument to the hashing service defined in services.yml, to the
# constructor of PhpassHashedPassword is the log2 number of iterations for

View File

@ -666,4 +666,13 @@ class Drupal {
return static::$container->get('menu.link_tree');
}
/**
* Returns the path validator.
*
* @return \Drupal\Core\Path\PathValidatorInterface
*/
public static function pathValidator() {
return static::$container->get('path.validator');
}
}

View File

@ -151,7 +151,12 @@ class UrlHelper {
if (strpos($url, '://') !== FALSE) {
// Split off everything before the query string into 'path'.
$parts = explode('?', $url);
$options['path'] = $parts[0];
// Don't support URLs without a path, like 'http://'.
list(, $path) = explode('://', $parts[0], 2);
if ($path != '') {
$options['path'] = $parts[0];
}
// If there is a query string, transform it into keyed query parameters.
if (isset($parts[1])) {
$query_parts = explode('#', $parts[1]);

View File

@ -8,6 +8,7 @@
namespace Drupal\Core\Menu;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Path\PathValidator;
use Drupal\Core\Session\AccountInterface;
/**
@ -93,6 +94,9 @@ class DefaultMenuLinkTreeManipulators {
* TRUE if the current user can access the link, FALSE otherwise.
*/
protected function menuLinkCheckAccess(MenuLinkInterface $instance) {
if ($this->account->hasPermission('link to any page')) {
return TRUE;
}
// Use the definition here since that's a lot faster than creating a Url
// object that we don't need.
$definition = $instance->getPluginDefinition();

View File

@ -2,18 +2,22 @@
/**
* @file
* Contains Drupal\Core\Path\PathValidator
* Contains \Drupal\Core\Path\PathValidator
*/
namespace Drupal\Core\Path;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Core\Routing\RequestHelper;
use Drupal\Core\Routing\RouteProviderInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\Routing\AccessAwareRouterInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
/**
* Provides a default path validator and access checker.
@ -21,66 +25,122 @@ use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
class PathValidator implements PathValidatorInterface {
/**
* The request matcher.
* The access aware router.
*
* @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface
* @var \Drupal\Core\Routing\AccessAwareRouterInterface
*/
protected $requestMatcher;
protected $accessAwareRouter;
/**
* The route provider.
* A router implementation which does not check access.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
* @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface
*/
protected $routeProvider;
protected $accessUnawareRouter;
/**
* The request stack.
* The current user.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
* @var \Drupal\Core\Session\AccountInterface
*/
protected $requestStack;
protected $account;
/**
* The path processor.
*
* @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
*/
protected $pathProcessor;
/**
* Creates a new PathValidator.
*
* @param \Symfony\Component\Routing\Matcher\RequestMatcherInterface $request_matcher
* The request matcher.
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
* The route provider.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Routing\AccessAwareRouterInterface $access_aware_router
* The access aware router.
* @param \Symfony\Component\Routing\Matcher\UrlMatcherInterface $access_unaware_router
* A router implementation which does not check access.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
* @param \Drupal\Core\PathProcessor\InboundPathProcessorInterface $path_processor
* The path processor;
*/
public function __construct(RequestMatcherInterface $request_matcher, RouteProviderInterface $route_provider, RequestStack $request_stack) {
$this->requestMatcher = $request_matcher;
$this->routeProvider = $route_provider;
$this->requestStack = $request_stack;
public function __construct(AccessAwareRouterInterface $access_aware_router, UrlMatcherInterface $access_unaware_router, AccountInterface $account, InboundPathProcessorInterface $path_processor) {
$this->accessAwareRouter = $access_aware_router;
$this->accessUnawareRouter = $access_unaware_router;
$this->account = $account;
$this->pathProcessor = $path_processor;
}
/**
* {@inheritdoc}
*/
public function isValid($path) {
// External URLs and the front page are always valid.
if ($path == '<front>' || UrlHelper::isExternal($path)) {
return TRUE;
return (bool) $this->getUrlIfValid($path);
}
/**
* {@inheritdoc}
*/
public function getUrlIfValid($path) {
$parsed_url = UrlHelper::parse($path);
$options = [];
if (!empty($parsed_url['query'])) {
$options['query'] = $parsed_url['query'];
}
if (!empty($parsed_url['fragment'])) {
$options['fragment'] = $parsed_url['fragment'];
}
// Check the routing system.
$collection = $this->routeProvider->getRoutesByPattern('/' . $path);
if ($collection->count() == 0) {
if ($parsed_url['path'] == '<front>') {
return new Url('<front>', [], $options);
}
elseif (UrlHelper::isExternal($path) && UrlHelper::isValid($path)) {
if (empty($parsed_url['path'])) {
return FALSE;
}
return Url::createFromPath($path);
}
$request = Request::create('/' . $path);
$attributes = $this->getPathAttributes($path, $request);
if (!$attributes) {
return FALSE;
}
// We can not use $this->requestMatcher->match() because we need to set
// the _menu_admin attribute to indicate a menu administrator is running
// the menu access check.
$request = RequestHelper::duplicate($this->requestStack->getCurrentRequest(), '/' . $path);
$request->attributes->set('_system_path', $path);
$request->attributes->set('_menu_admin', TRUE);
$route_name = $attributes[RouteObjectInterface::ROUTE_NAME];
$route_parameters = $attributes['_raw_variables']->all();
return new Url($route_name, $route_parameters, $options + ['query' => $request->query->all()]);
}
/**
* Gets the matched attributes for a given path.
*
* @param string $path
* The path to check.
* @param \Symfony\Component\HttpFoundation\Request $request
* A request object with the given path.
*
* @return array|bool
* An array of request attributes of FALSE if an exception was thrown.
*/
protected function getPathAttributes($path, Request $request) {
if ($this->account->hasPermission('link to any page')) {
$router = $this->accessUnawareRouter;
}
else {
$router = $this->accessAwareRouter;
}
$path = $this->pathProcessor->processInbound($path, $request);
try {
$this->requestMatcher->matchRequest($request);
return $router->match('/' . $path);
}
catch (ResourceNotFoundException $e) {
return FALSE;
}
catch (ParamNotConvertedException $e) {
return FALSE;
@ -88,7 +148,6 @@ class PathValidator implements PathValidatorInterface {
catch (AccessDeniedHttpException $e) {
return FALSE;
}
return TRUE;
}
}

View File

@ -2,7 +2,7 @@
/**
* @file
* Contains Drupal\Core\Path\PathValidatorInterface
* Contains \Drupal\Core\Path\PathValidatorInterface
*/
namespace Drupal\Core\Path;
@ -12,6 +12,17 @@ namespace Drupal\Core\Path;
*/
interface PathValidatorInterface {
/**
* Returns an URL object, if the path is valid and accessible.
*
* @param string $path
* The path to check.
*
* @return \Drupal\Core\Url|false
* The url object, or FALSE if the path is not valid.
*/
public function getUrlIfValid($path);
/**
* Checks if the URL path is valid and accessible by the current user.
*

View File

@ -22,6 +22,9 @@ interface InboundPathProcessorInterface {
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The HttpRequest object representing the current request.
*
* @return string
* The processed path.
*/
public function processInbound($path, Request $request);

View File

@ -9,11 +9,9 @@ namespace Drupal\Core;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Routing\MatchingRouteNotFoundException;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
/**
* Defines an object that holds information about a URL.
@ -97,7 +95,15 @@ class Url {
}
/**
* Returns the Url object matching a path.
* Returns the Url object matching a path. READ THE FOLLOWING SECURITY NOTE.
*
* SECURITY NOTE: The path is not checked to be valid and accessible by the
* current user to allow storing and reusing Url objects by different users.
* The 'path.validator' service getUrlIfValid() method should be used instead
* of this one if validation and access check is desired. Otherwise,
* 'access_manager' service checkNamedRoute() method should be used on the
* router name and parameters stored in the Url object returned by this
* method.
*
* @param string $path
* A path (e.g. 'node/1', 'http://drupal.org').
@ -118,27 +124,15 @@ class Url {
// Special case the front page route.
if ($path == '<front>') {
$route_name = $path;
$route_parameters = array();
return new static($path);
}
else {
// Look up the route name and parameters used for the given path.
try {
// We use the router without access checks because URL objects might be
// created and stored for different users.
$result = \Drupal::service('router.no_access_checks')->match('/' . $path);
}
catch (ResourceNotFoundException $e) {
throw new MatchingRouteNotFoundException(sprintf('No matching route could be found for the path "%s"', $path), 0, $e);
}
$route_name = $result[RouteObjectInterface::ROUTE_NAME];
$route_parameters = $result['_raw_variables']->all();
return static::createFromRequest(Request::create("/$path"));
}
return new static($route_name, $route_parameters);
}
/**
* Returns the Url object matching a request.
* Returns the Url object matching a request. READ THE SECURITY NOTE ON createFromPath().
*
* @param \Symfony\Component\HttpFoundation\Request $request
* A request object.
@ -152,14 +146,9 @@ class Url {
* Thrown when the request cannot be matched.
*/
public static function createFromRequest(Request $request) {
try {
// We use the router without access checks because URL objects might be
// created and stored for different users.
$result = \Drupal::service('router.no_access_checks')->matchRequest($request);
}
catch (ResourceNotFoundException $e) {
throw new MatchingRouteNotFoundException(sprintf('No matching route could be found for the request: %s', $request), 0, $e);
}
// We use the router without access checks because URL objects might be
// created and stored for different users.
$result = \Drupal::service('router.no_access_checks')->matchRequest($request);
$route_name = $result[RouteObjectInterface::ROUTE_NAME];
$route_parameters = $result['_raw_variables']->all();
return new static($route_name, $route_parameters);

View File

@ -7,15 +7,10 @@
namespace Drupal\link\Plugin\Field\FieldWidget;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Core\Routing\MatchingRouteNotFoundException;
use Drupal\Core\Url;
use Drupal\link\LinkItemInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Plugin implementation of the 'link' widget.
@ -47,9 +42,10 @@ class LinkWidget extends WidgetBase {
$default_url_value = NULL;
if (isset($items[$delta]->url)) {
$url = Url::createFromPath($items[$delta]->url);
$url->setOptions($items[$delta]->options);
$default_url_value = ltrim($url->toString(), '/');
if ($url = \Drupal::pathValidator()->getUrlIfValid($items[$delta]->url)) {
$url->setOptions($items[$delta]->options);
$default_url_value = ltrim($url->toString(), '/');
}
}
$element['url'] = array(
'#type' => 'url',
@ -204,32 +200,16 @@ class LinkWidget extends WidgetBase {
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
foreach ($values as &$value) {
if (!empty($value['url'])) {
try {
$parsed_url = UrlHelper::parse($value['url']);
// If internal links are supported, look up whether the given value is
// a path alias and store the system path instead.
if ($this->supportsInternalLinks() && !UrlHelper::isExternal($value['url'])) {
$parsed_url['path'] = \Drupal::service('path.alias_manager')->getPathByAlias($parsed_url['path']);
}
$url = Url::createFromPath($parsed_url['path']);
$url->setOption('query', $parsed_url['query']);
$url->setOption('fragment', $parsed_url['fragment']);
$url->setOption('attributes', $value['attributes']);
$value += $url->toArray();
// Reset the URL value to contain only the path.
$value['url'] = $parsed_url['path'];
$url = \Drupal::pathValidator()->getUrlIfValid($value['url']);
if (!$url) {
return $values;
}
catch (NotFoundHttpException $e) {
// Nothing to do here, LinkTypeConstraintValidator emits errors.
}
catch (MatchingRouteNotFoundException $e) {
// Nothing to do here, LinkTypeConstraintValidator emits errors.
}
catch (ParamNotConvertedException $e) {
// Nothing to do here, LinkTypeConstraintValidator emits errors.
$value += $url->toArray();
// Reset the URL value to contain only the path.
if (!$url->isExternal() && $this->supportsInternalLinks()) {
$value['url'] = substr($url->toString(), strlen(\Drupal::request()->getBasePath() . '/'));
}
}
}

View File

@ -8,14 +8,9 @@
namespace Drupal\link\Plugin\Validation\Constraint;
use Drupal\link\LinkItemInterface;
use Drupal\Core\Url;
use Drupal\Core\Routing\MatchingRouteNotFoundException;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Component\Utility\UrlHelper;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\ExecutionContextInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Validation constraint for links receiving data allowed by its settings.
@ -53,35 +48,19 @@ class LinkTypeConstraint extends Constraint implements ConstraintValidatorInterf
*/
public function validate($value, Constraint $constraint) {
if (isset($value)) {
$url_is_valid = TRUE;
$url_is_valid = FALSE;
/** @var $link_item \Drupal\link\LinkItemInterface */
$link_item = $value;
$link_type = $link_item->getFieldDefinition()->getSetting('link_type');
$url_string = $link_item->url;
// Validate the url property.
if ($url_string !== '') {
try {
// @todo This shouldn't be needed, but massageFormValues() may not
// run.
$parsed_url = UrlHelper::parse($url_string);
if ($url = \Drupal::pathValidator()->getUrlIfValid($url_string)) {
$url_is_valid = (bool) $url;
$url = Url::createFromPath($parsed_url['path']);
if ($url->isExternal() && !UrlHelper::isValid($url_string, TRUE)) {
if ($url->isExternal() && !($link_type & LinkItemInterface::LINK_EXTERNAL)) {
$url_is_valid = FALSE;
}
elseif ($url->isExternal() && !($link_type & LinkItemInterface::LINK_EXTERNAL)) {
$url_is_valid = FALSE;
}
}
catch (NotFoundHttpException $e) {
$url_is_valid = FALSE;
}
catch (MatchingRouteNotFoundException $e) {
$url_is_valid = FALSE;
}
catch (ParamNotConvertedException $e) {
$url_is_valid = FALSE;
}
}
if (!$url_is_valid) {

View File

@ -52,6 +52,7 @@ class LinkFieldTest extends WebTestBase {
$this->web_user = $this->drupalCreateUser(array(
'view test entity',
'administer entity_test content',
'link to any page',
));
$this->drupalLogin($this->web_user);
}

View File

@ -18,11 +18,9 @@ use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Menu\Form\MenuLinkFormInterface;
use Drupal\Core\Menu\MenuLinkInterface;
use Drupal\Core\Menu\MenuParentFormSelectorInterface;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Core\Path\AliasManagerInterface;
use Drupal\Core\Routing\MatchingRouteNotFoundException;
use Drupal\Core\Path\PathValidatorInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\RequestContext;
@ -77,6 +75,13 @@ class MenuLinkContentForm extends ContentEntityForm implements MenuLinkFormInter
*/
protected $account;
/**
* The path validator.
*
* @var \Drupal\Core\Path\PathValidatorInterface
*/
protected $pathValidator;
/**
* Constructs a MenuLinkContentForm object.
*
@ -96,8 +101,10 @@ class MenuLinkContentForm extends ContentEntityForm implements MenuLinkFormInter
* The access manager.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
* @param \Drupal\Core\Path\PathValidatorInterface $path_validator
* The path validator.
*/
public function __construct(EntityManagerInterface $entity_manager, MenuParentFormSelectorInterface $menu_parent_selector, AliasManagerInterface $alias_manager, ModuleHandlerInterface $module_handler, RequestContext $request_context, LanguageManagerInterface $language_manager, AccessManagerInterface $access_manager, AccountInterface $account) {
public function __construct(EntityManagerInterface $entity_manager, MenuParentFormSelectorInterface $menu_parent_selector, AliasManagerInterface $alias_manager, ModuleHandlerInterface $module_handler, RequestContext $request_context, LanguageManagerInterface $language_manager, AccessManagerInterface $access_manager, AccountInterface $account, PathValidatorInterface $path_validator) {
parent::__construct($entity_manager, $language_manager);
$this->menuParentSelector = $menu_parent_selector;
$this->pathAliasManager = $alias_manager;
@ -106,6 +113,7 @@ class MenuLinkContentForm extends ContentEntityForm implements MenuLinkFormInter
$this->languageManager = $language_manager;
$this->accessManager = $access_manager;
$this->account = $account;
$this->pathValidator = $path_validator;
}
/**
@ -120,7 +128,8 @@ class MenuLinkContentForm extends ContentEntityForm implements MenuLinkFormInter
$container->get('router.request_context'),
$container->get('language_manager'),
$container->get('access_manager'),
$container->get('current_user')
$container->get('current_user'),
$container->get('path.validator')
);
}
@ -167,46 +176,6 @@ class MenuLinkContentForm extends ContentEntityForm implements MenuLinkFormInter
$this->save($form, $form_state);
}
/**
* Breaks up a user-entered URL or path into all the relevant parts.
*
* @param string $url
* The user-entered URL or path.
*
* @return array
* The extracted parts.
*/
protected function extractUrl($url) {
$extracted = UrlHelper::parse($url);
$external = UrlHelper::isExternal($url);
if ($external) {
$extracted['url'] = $extracted['path'];
$extracted['route_name'] = NULL;
$extracted['route_parameters'] = array();
}
else {
$extracted['url'] = '';
// If the path doesn't match a Drupal path, the route should end up empty.
$extracted['route_name'] = NULL;
$extracted['route_parameters'] = array();
try {
// Find the route_name.
$normal_path = $this->pathAliasManager->getPathByAlias($extracted['path']);
$url_obj = Url::createFromPath($normal_path);
$extracted['route_name'] = $url_obj->getRouteName();
$extracted['route_parameters'] = $url_obj->getRouteParameters();
}
catch (MatchingRouteNotFoundException $e) {
// The path doesn't match a Drupal path.
}
catch (ParamNotConvertedException $e) {
// A path like node/99 matched a route, but the route parameter was
// invalid (e.g. node with ID 99 does not exist).
}
}
return $extracted;
}
/**
* {@inheritdoc}
*/
@ -220,17 +189,24 @@ class MenuLinkContentForm extends ContentEntityForm implements MenuLinkFormInter
}
$new_definition['parent'] = isset($parent) ? $parent : '';
$extracted = $this->extractUrl($form_state->getValue('url'));
$new_definition['url'] = $extracted['url'];
$new_definition['route_name'] = $extracted['route_name'];
$new_definition['route_parameters'] = $extracted['route_parameters'];
$new_definition['options'] = array();
if ($extracted['query']) {
$new_definition['options']['query'] = $extracted['query'];
}
if ($extracted['fragment']) {
$new_definition['options']['fragment'] = $extracted['fragment'];
$new_definition['url'] = NULL;
$new_definition['route_name'] = NULL;
$new_definition['route_parameters'] = [];
$new_definition['options'] = [];
$extracted = $this->pathValidator->getUrlIfValid($form_state->getValue('url'));
if ($extracted) {
if ($extracted->isExternal()) {
$new_definition['url'] = $extracted->getPath();
}
else {
$new_definition['route_name'] = $extracted->getRouteName();
$new_definition['route_parameters'] = $extracted->getRouteParameters();
$new_definition['options'] = $extracted->getOptions();
}
}
$new_definition['title'] = $form_state->getValue(array('title', 0, 'value'));
$new_definition['description'] = $form_state->getValue(array('description', 0, 'value'));
$new_definition['weight'] = (int) $form_state->getValue(array('weight', 0, 'value'));
@ -380,31 +356,11 @@ class MenuLinkContentForm extends ContentEntityForm implements MenuLinkFormInter
* The current state of the form.
*/
protected function doValidate(array $form, FormStateInterface $form_state) {
$extracted = $this->extractUrl($form_state->getValue('url'));
$extracted = $this->pathValidator->getUrlIfValid($form_state->getValue('url'));
// If both URL and route_name are empty, the entered value is not valid.
$valid = FALSE;
if ($extracted['url']) {
// This is an external link.
$valid = TRUE;
}
elseif ($extracted['route_name']) {
// Users are not allowed to add a link to a page they cannot access.
$valid = $this->accessManager->checkNamedRoute($extracted['route_name'], $extracted['route_parameters'], $this->account);
}
if (!$valid) {
if (!$extracted) {
$form_state->setErrorByName('url', $this->t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $form_state->getValue('url'))));
}
elseif ($extracted['route_name']) {
// The user entered a Drupal path.
$normal_path = $this->pathAliasManager->getPathByAlias($extracted['path']);
if ($extracted['path'] != $normal_path) {
drupal_set_message($this->t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array(
'%link_path' => $extracted['path'],
'%normal_path' => $normal_path,
)));
}
}
}
}

View File

@ -258,29 +258,6 @@ function shortcut_set_title_exists($title) {
return FALSE;
}
/**
* Determines if a path corresponds to a valid shortcut link.
*
* @param string $path
* The path to the link.
*
* @return bool
* TRUE if the shortcut link is valid, FALSE otherwise. Valid links are ones
* that correspond to actual paths on the site.
*
* @see menu_edit_item_validate()
*/
function shortcut_valid_link($path) {
// Do not use URL aliases.
$normal_path = \Drupal::service('path.alias_manager')->getPathByAlias($path);
if ($path != $normal_path) {
$path = $normal_path;
}
// An empty path is valid too and will be converted to <front>.
return (!UrlHelper::isExternal($path) && (\Drupal::service('router.route_provider')->getRoutesByPattern('/' . $path)->count() > 0)) || empty($path) || $path == '<front>';
}
/**
* Returns an array of shortcut links, suitable for rendering.
*

View File

@ -8,6 +8,7 @@
namespace Drupal\shortcut\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Path\PathValidatorInterface;
use Drupal\shortcut\ShortcutSetInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
@ -19,6 +20,30 @@ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
*/
class ShortcutSetController extends ControllerBase {
/**
* The path validator.
*
* @var \Drupal\Core\Path\PathValidatorInterface
*/
protected $pathValidator;
/**
* Creates a new ShortcutSetController instance.
*
* @param \Drupal\Core\Path\PathValidatorInterface $path_validator
* The path validator.
*/
public function __construct(PathValidatorInterface $path_validator) {
$this->pathValidator = $path_validator;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('path.validator'));
}
/**
* Creates a new link in the provided shortcut set.
*
@ -35,7 +60,7 @@ class ShortcutSetController extends ControllerBase {
public function addShortcutLinkInline(ShortcutSetInterface $shortcut_set, Request $request) {
$link = $request->query->get('link');
$name = $request->query->get('name');
if (shortcut_valid_link($link)) {
if ($this->pathValidator->isValid($link)) {
$shortcut = $this->entityManager()->getStorage('shortcut')->create(array(
'title' => $name,
'shortcut_set' => $shortcut_set->id(),

View File

@ -81,6 +81,13 @@ class Shortcut extends ContentEntityBase implements ShortcutInterface {
return $this;
}
/**
* {@inheritdoc}
*/
public function getUrl() {
return new Url($this->getRouteName(), $this->getRouteParams());
}
/**
* {@inheritdoc}
*/

View File

@ -8,8 +8,11 @@
namespace Drupal\shortcut;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Path\PathValidatorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for the shortcut entity forms.
@ -23,6 +26,34 @@ class ShortcutForm extends ContentEntityForm {
*/
protected $entity;
/**
* The path validator.
*
* @var \Drupal\Core\Path\PathValidatorInterface
*/
protected $pathValidator;
/**
* Constructs a new ShortcutForm instance.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager
* @param \Drupal\Core\Path\PathValidatorInterface $path_validator
* The path validator.
*/
public function __construct(EntityManagerInterface $entity_manager, PathValidatorInterface $path_validator) {
parent::__construct($entity_manager);
$this->pathValidator = $path_validator;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('entity.manager'), $container->get('path.validator'));
}
/**
* {@inheritdoc}
*/
@ -65,7 +96,7 @@ class ShortcutForm extends ContentEntityForm {
* {@inheritdoc}
*/
public function validate(array $form, FormStateInterface $form_state) {
if (!shortcut_valid_link($form_state->getValue('path'))) {
if (!$this->pathValidator->isValid($form_state->getValue('path'))) {
$form_state->setErrorByName('path', $this->t('The shortcut must correspond to a valid path on the site.'));
}

View File

@ -52,6 +52,14 @@ interface ShortcutInterface extends ContentEntityInterface {
*/
public function setWeight($weight);
/**
* Returns the URL object pointing to the configured route.
*
* @return \Drupal\Core\Url
* The URL object.
*/
public function getUrl();
/**
* Returns the route name associated with this shortcut, if any.
*

View File

@ -38,13 +38,13 @@ class ShortcutLinksTest extends ShortcutTestBase {
// Create some paths to test.
$test_cases = array(
array('path' => ''),
array('path' => 'admin'),
array('path' => 'admin/config/system/site-information'),
array('path' => 'node/' . $this->node->id() . '/edit'),
array('path' => $path['alias']),
array('path' => 'router_test/test2'),
array('path' => 'router_test/test3/value'),
array('path' => '<front>', 'route_name' => '<front>'),
array('path' => 'admin', 'route_name' => 'system.admin'),
array('path' => 'admin/config/system/site-information', 'route_name' => 'system.site_information_settings'),
array('path' => 'node/' . $this->node->id() . '/edit', 'route_name' => 'entity.node.edit_form'),
array('path' => $path['alias'], 'route_name' => 'entity.node.canonical'),
array('path' => 'router_test/test2', 'route_name' => 'router_test.2'),
array('path' => 'router_test/test3/value', 'route_name' => 'router_test.3'),
);
// Check that each new shortcut links where it should.
@ -57,8 +57,8 @@ class ShortcutLinksTest extends ShortcutTestBase {
$this->drupalPostForm('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link', $form_data, t('Save'));
$this->assertResponse(200);
$saved_set = ShortcutSet::load($set->id());
$paths = $this->getShortcutInformation($saved_set, 'path');
$this->assertTrue(in_array($this->container->get('path.alias_manager')->getPathByAlias($test['path']), $paths), 'Shortcut created: ' . $test['path']);
$routes = $this->getShortcutInformation($saved_set, 'route_name');
$this->assertTrue(in_array($test['route_name'], $routes), 'Shortcut created: ' . $test['path']);
$this->assertLink($title, 0, 'Shortcut link found on the page.');
}
$saved_set = ShortcutSet::load($set->id());
@ -73,6 +73,25 @@ class ShortcutLinksTest extends ShortcutTestBase {
$this->assertEqual($entity->get('route_parameters')->first()->getValue(), $loaded->get('route_parameters')->first()->getValue());
}
}
// Login as non admin user, to check that access is checked when creating
// shortcuts.
$this->drupalLogin($this->shortcut_user);
$title = $this->randomMachineName();
$form_data = [
'title[0][value]' => $title,
'path' => 'admin',
];
$this->drupalPostForm('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link', $form_data, t('Save'));
$this->assertResponse(200);
$this->assertRaw(t('The shortcut must correspond to a valid path on the site.'));
$form_data = [
'title[0][value]' => $title,
'path' => 'node',
];
$this->drupalPostForm('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link', $form_data, t('Save'));
$this->assertLink($title, 0, 'Shortcut link found on the page.');
}
/**
@ -137,8 +156,8 @@ class ShortcutLinksTest extends ShortcutTestBase {
$shortcut = reset($shortcuts);
$this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $shortcut->id(), array('title[0][value]' => $shortcut->getTitle(), 'path' => $new_link_path), t('Save'));
$saved_set = ShortcutSet::load($set->id());
$paths = $this->getShortcutInformation($saved_set, 'path');
$this->assertTrue(in_array($new_link_path, $paths), 'Shortcut path changed: ' . $new_link_path);
$routes = $this->getShortcutInformation($saved_set, 'route_name');
$this->assertTrue(in_array('system.admin_config', $routes), 'Shortcut path changed: ' . $new_link_path);
$this->assertLinkByHref($new_link_path, 0, 'Shortcut with new path appears on the page.');
}

View File

@ -73,8 +73,8 @@ abstract class ShortcutTestBase extends WebTestBase {
}
// Create users.
$this->admin_user = $this->drupalCreateUser(array('access toolbar', 'administer shortcuts', 'view the administration theme', 'create article content', 'create page content', 'access content overview', 'administer users'));
$this->shortcut_user = $this->drupalCreateUser(array('customize shortcut links', 'switch shortcut sets'));
$this->admin_user = $this->drupalCreateUser(array('access toolbar', 'administer shortcuts', 'view the administration theme', 'create article content', 'create page content', 'access content overview', 'administer users', 'link to any page'));
$this->shortcut_user = $this->drupalCreateUser(array('customize shortcut links', 'switch shortcut sets', 'access shortcuts', 'access content'));
// Create a node.
$this->node = $this->drupalCreateNode(array('type' => 'article'));

View File

@ -249,6 +249,11 @@ function system_permission() {
'title' => t('View site reports'),
'restrict access' => TRUE,
),
'link to any page' => [
'title' => t('Link to any page'),
'description' => t('This allows to bypass access checking when linking to internal paths.'),
'restrict access' => TRUE,
],
);
}

View File

@ -9,7 +9,6 @@ namespace Drupal\user\Access;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Determines access to routes based on login status of current user.
@ -19,16 +18,14 @@ class LoginStatusCheck implements AccessInterface {
/**
* Checks access.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
*
* @return string
* A \Drupal\Core\Access\AccessInterface constant value.
*/
public function access(Request $request, AccountInterface $account) {
return ($request->attributes->get('_menu_admin') || $account->isAuthenticated()) ? static::ALLOW : static::DENY;
public function access(AccountInterface $account) {
return $account->isAuthenticated() ? static::ALLOW : static::DENY;
}
}

View File

@ -9,7 +9,6 @@ namespace Drupal\user\Access;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Access check for user registration routes.
@ -19,15 +18,13 @@ class RegisterAccessCheck implements AccessInterface {
/**
* Checks access.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
*
* @return string
* A \Drupal\Core\Access\AccessInterface constant value.
*/
public function access(Request $request, AccountInterface $account) {
return ($request->attributes->get('_menu_admin') || $account->isAnonymous()) && (\Drupal::config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY) ? static::ALLOW : static::DENY;
public function access(AccountInterface $account) {
return ($account->isAnonymous()) && (\Drupal::config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY) ? static::ALLOW : static::DENY;
}
}

View File

@ -272,6 +272,22 @@ class UrlHelperTest extends UnitTestCase {
'fragment' => 'footer',
),
),
array(
'http://',
array(
'path' => '',
'query' => array(),
'fragment' => '',
),
),
array(
'https://',
array(
'path' => '',
'query' => array(),
'fragment' => '',
),
),
array(
'/my/path?destination=home#footer',
array(

View File

@ -319,6 +319,22 @@ class DrupalTest extends UnitTestCase {
$this->assertNotNull(\Drupal::formBuilder());
}
/**
* Tests the menuTree() method.
*/
public function testMenuTree() {
$this->setMockContainerService('menu.link_tree');
$this->assertNotNull(\Drupal::menuTree());
}
/**
* Tests the pathValidator() method.
*/
public function testPathValidator() {
$this->setMockContainerService('path.validator');
$this->assertNotNull(\Drupal::pathValidator());
}
/**
* Sets up a mock expectation for the container get() method.
*

View File

@ -76,15 +76,10 @@ class ExternalUrlTest extends UnitTestCase {
*
* @covers ::createFromRequest()
*
* @expectedException \Drupal\Core\Routing\MatchingRouteNotFoundException
* @expectedExceptionMessage No matching route could be found for the request: request_as_a_string
* @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
*/
public function testCreateFromRequest() {
// Mock the request in order to override the __toString() method.
$request = $this->getMock('Symfony\Component\HttpFoundation\Request');
$request->expects($this->once())
->method('__toString')
->will($this->returnValue('request_as_a_string'));
$request = Request::create('/test-path');
$this->router->expects($this->once())
->method('matchRequest')

View File

@ -44,7 +44,7 @@ class DefaultMenuLinkTreeManipulatorsTest extends UnitTestCase {
/**
* The original menu tree build in mockTree().
*
* @var \Drupal\Tests\Core\Menu\MenuLinkMock[]
* @var \Drupal\Core\Menu\MenuLinkTreeElement[]
*/
protected $originalTree = array();
@ -134,6 +134,7 @@ class DefaultMenuLinkTreeManipulatorsTest extends UnitTestCase {
* Tests the checkAccess() tree manipulator.
*
* @covers ::checkAccess
* @covers ::menuLinkCheckAccess
*/
public function testCheckAccess() {
// Those menu links that are non-external will have their access checks
@ -178,6 +179,31 @@ class DefaultMenuLinkTreeManipulatorsTest extends UnitTestCase {
$this->assertFalse(array_key_exists(8, $tree));
}
/**
* Tests checkAccess() tree manipulator with 'link to any page' permission.
*
* @covers ::checkAccess
* @covers ::menuLinkCheckAccess
*/
public function testCheckAccessWithLinkToAnyPagePermission() {
$this->mockTree();
$this->currentUser->expects($this->exactly(8))
->method('hasPermission')
->with('link to any page')
->willReturn(TRUE);
$this->mockTree();
$this->defaultMenuTreeManipulators->checkAccess($this->originalTree);
$this->assertTrue($this->originalTree[1]->access);
$this->assertTrue($this->originalTree[2]->access);
$this->assertTrue($this->originalTree[2]->subtree[3]->access);
$this->assertTrue($this->originalTree[2]->subtree[3]->subtree[4]->access);
$this->assertTrue($this->originalTree[5]->subtree[7]->access);
$this->assertTrue($this->originalTree[6]->access);
$this->assertTrue($this->originalTree[8]->access);
}
/**
* Tests the flatten() tree manipulator.
*

View File

@ -0,0 +1,326 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Path\PathValidatorTest.
*/
namespace Drupal\Tests\Core\Path;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Core\Path\PathValidator;
use Drupal\Tests\UnitTestCase;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
/**
* @coversDefaultClass \Drupal\Core\Path\PathValidator
* @group Routing
*/
class PathValidatorTest extends UnitTestCase {
/**
* The mocked access aware router.
*
* @var \Drupal\Core\Routing\AccessAwareRouterInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $accessAwareRouter;
/**
* The mocked access unaware router.
* @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $accessUnawareRouter;
/**
* The mocked account.
*
* @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $account;
/**
* The path processor.
*
* @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $pathProcessor;
/**
* The tested path validator.
*
* @var \Drupal\Core\Path\PathValidator
*/
protected $pathValidator;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->accessAwareRouter = $this->getMock('Drupal\Core\Routing\AccessAwareRouterInterface');
$this->accessUnawareRouter = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface');
$this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
$this->pathProcessor = $this->getMock('Drupal\Core\PathProcessor\InboundPathProcessorInterface');
$this->pathValidator = new PathValidator($this->accessAwareRouter, $this->accessUnawareRouter, $this->account, $this->pathProcessor);
}
/**
* Tests the isValid() method for the frontpage.
*
* @covers ::isValid
*/
public function testIsValidWithFrontpage() {
$this->accessAwareRouter->expects($this->never())
->method('match');
$this->assertTrue($this->pathValidator->isValid('<front>'));
}
/**
* Tests the isValid() method for an external URL.
*
* @covers ::isValid
*/
public function testIsValidWithExternalUrl() {
$this->accessAwareRouter->expects($this->never())
->method('match');
$this->assertTrue($this->pathValidator->isValid('https://drupal.org'));
}
/**
* Tests the isValid() method with an invalid external URL.
*/
public function testIsValidWithInvalidExternalUrl() {
$this->accessAwareRouter->expects($this->never())
->method('match');
$this->assertFalse($this->pathValidator->isValid('http://'));
}
/**
* Tests the isValid() method with a 'link to any page' permission.
*
* @covers ::isValid
*/
public function testIsValidWithLinkToAnyPageAccount() {
$this->account->expects($this->once())
->method('hasPermission')
->with('link to any page')
->willReturn(TRUE);
$this->accessAwareRouter->expects($this->never())
->method('match');
$this->accessUnawareRouter->expects($this->once())
->method('match')
->with('/test-path')
->willReturn([RouteObjectInterface::ROUTE_NAME => 'test_route', '_raw_variables' => new ParameterBag(['key' => 'value'])]);
$this->pathProcessor->expects($this->once())
->method('processInbound')
->willReturnArgument(0);
$this->assertTrue($this->pathValidator->isValid('test-path'));
}
/**
* Tests the isValid() method without the 'link to any page' permission.
*
* @covers ::isValid
*/
public function testIsValidWithoutLinkToAnyPageAccount() {
$this->account->expects($this->once())
->method('hasPermission')
->with('link to any page')
->willReturn(FALSE);
$this->accessUnawareRouter->expects($this->never())
->method('match');
$this->accessAwareRouter->expects($this->once())
->method('match')
->with('/test-path')
->willReturn([RouteObjectInterface::ROUTE_NAME => 'test_route', '_raw_variables' => new ParameterBag(['key' => 'value'])]);
$this->pathProcessor->expects($this->once())
->method('processInbound')
->willReturnArgument(0);
$this->assertTrue($this->pathValidator->isValid('test-path'));
}
/**
* Tests the isValid() method with a path alias.
*
* @covers ::isValid
*/
public function testIsValidWithPathAlias() {
$this->account->expects($this->once())
->method('hasPermission')
->with('link to any page')
->willReturn(FALSE);
$this->accessUnawareRouter->expects($this->never())
->method('match');
$this->accessAwareRouter->expects($this->once())
->method('match')
->with('/test-path')
->willReturn([RouteObjectInterface::ROUTE_NAME => 'test_route', '_raw_variables' => new ParameterBag(['key' => 'value'])]);
$this->pathProcessor->expects($this->once())
->method('processInbound')
->with('path-alias', $this->anything())
->willReturn('test-path');
$this->assertTrue($this->pathValidator->isValid('path-alias'));
}
/**
* Tests the isValid() method with a user without access to the path.
*
* @covers ::isValid
*/
public function testIsValidWithAccessDenied() {
$this->account->expects($this->once())
->method('hasPermission')
->with('link to any page')
->willReturn(FALSE);
$this->accessUnawareRouter->expects($this->never())
->method('match');
$this->accessAwareRouter->expects($this->once())
->method('match')
->with('/test-path')
->willThrowException(new AccessDeniedHttpException());
$this->pathProcessor->expects($this->once())
->method('processInbound')
->willReturnArgument(0);
$this->assertFalse($this->pathValidator->isValid('test-path'));
}
/**
* Tests the isValid() method with a not working param converting.
*
* @covers ::isValid
*/
public function testIsValidWithFailingParameterConverting() {
$this->account->expects($this->once())
->method('hasPermission')
->with('link to any page')
->willReturn(FALSE);
$this->accessUnawareRouter->expects($this->never())
->method('match');
$this->accessAwareRouter->expects($this->once())
->method('match')
->with('/entity-test/1')
->willThrowException(new ParamNotConvertedException());
$this->pathProcessor->expects($this->once())
->method('processInbound')
->willReturnArgument(0);
$this->assertFalse($this->pathValidator->isValid('entity-test/1'));
}
/**
* Tests the isValid() method with a not existing path.
*
* @covers ::isValid
*/
public function testIsValidWithNotExistingPath() {
$this->account->expects($this->once())
->method('hasPermission')
->with('link to any page')
->willReturn(FALSE);
$this->accessUnawareRouter->expects($this->never())
->method('match');
$this->accessAwareRouter->expects($this->once())
->method('match')
->with('/not-existing-path')
->willThrowException(new ResourceNotFoundException());
$this->pathProcessor->expects($this->once())
->method('processInbound')
->willReturnArgument(0);
$this->assertFalse($this->pathValidator->isValid('not-existing-path'));
}
/**
* Tests the getUrlIfValid() method when there is access.
*/
public function testGetUrlIfValidWithAccess() {
$this->account->expects($this->once())
->method('hasPermission')
->with('link to any page')
->willReturn(FALSE);
$this->accessAwareRouter->expects($this->once())
->method('match')
->with('/test-path')
->willReturn([RouteObjectInterface::ROUTE_NAME => 'test_route', '_raw_variables' => new ParameterBag(['key' => 'value'])]);
$this->pathProcessor->expects($this->once())
->method('processInbound')
->willReturnArgument(0);
$url = $this->pathValidator->getUrlIfValid('test-path');
$this->assertInstanceOf('Drupal\Core\Url', $url);
$this->assertEquals('test_route', $url->getRouteName());
$this->assertEquals(['key' => 'value'], $url->getRouteParameters());
}
/**
* Tests the getUrlIfValid() method with a query in the path.
*/
public function testGetUrlIfValidWithQuery() {
$this->account->expects($this->once())
->method('hasPermission')
->with('link to any page')
->willReturn(FALSE);
$this->accessAwareRouter->expects($this->once())
->method('match')
->with('/test-path?k=bar')
->willReturn([RouteObjectInterface::ROUTE_NAME => 'test_route', '_raw_variables' => new ParameterBag()]);
$this->pathProcessor->expects($this->once())
->method('processInbound')
->willReturnArgument(0);
$url = $this->pathValidator->getUrlIfValid('test-path?k=bar');
$this->assertInstanceOf('Drupal\Core\Url', $url);
$this->assertEquals('test_route', $url->getRouteName());
$this->assertEquals(['k' => 'bar'], $url->getOptions()['query']);
}
/**
* Tests the getUrlIfValid() method where there is no access.
*/
public function testGetUrlIfValidWithoutAccess() {
$this->account->expects($this->once())
->method('hasPermission')
->with('link to any page')
->willReturn(FALSE);
$this->accessAwareRouter->expects($this->once())
->method('match')
->with('/test-path')
->willThrowException(new AccessDeniedHttpException());
$this->pathProcessor->expects($this->once())
->method('processInbound')
->willReturnArgument(0);
$url = $this->pathValidator->getUrlIfValid('test-path');
$this->assertFalse($url);
}
/**
* Tests the getUrlIfValid() method with a front page + query + fragments.
*/
public function testGetUrlIfValidWithFrontPageAndQueryAndFragments() {
$url = $this->pathValidator->getUrlIfValid('<front>?hei=sen#berg');
$this->assertEquals('<front>', $url->getRouteName());
$this->assertEquals(['hei' => 'sen'], $url->getOptions()['query']);
$this->assertEquals('berg', $url->getOptions()['fragment']);
}
}

View File

@ -72,22 +72,27 @@ class UrlTest extends UnitTestCase {
* @covers ::createFromPath()
*/
public function testCreateFromPath() {
$this->router->expects($this->any())
->method('match')
->will($this->returnValueMap(array(
array('/node', array(
$this->router->expects($this->at(0))
->method('matchRequest')
->with(Request::create('/node'))
->willReturn([
RouteObjectInterface::ROUTE_NAME => 'view.frontpage.page_1',
'_raw_variables' => new ParameterBag(),
)),
array('/node/1', array(
RouteObjectInterface::ROUTE_NAME => 'node_view',
'_raw_variables' => new ParameterBag(array('node' => '1')),
)),
array('/node/2/edit', array(
RouteObjectInterface::ROUTE_NAME => 'node_edit',
'_raw_variables' => new ParameterBag(array('node' => '2')),
)),
)));
]);
$this->router->expects($this->at(1))
->method('matchRequest')
->with(Request::create('/node/1'))
->willReturn([
RouteObjectInterface::ROUTE_NAME => 'node_view',
'_raw_variables' => new ParameterBag(['node' => '1']),
]);
$this->router->expects($this->at(2))
->method('matchRequest')
->with(Request::create('/node/2/edit'))
->willReturn([
RouteObjectInterface::ROUTE_NAME => 'node_edit',
'_raw_variables' => new ParameterBag(['node' => '2']),
]);
$urls = array();
foreach ($this->map as $index => $values) {
@ -114,13 +119,12 @@ class UrlTest extends UnitTestCase {
*
* @covers ::createFromPath()
*
* @expectedException \Drupal\Core\Routing\MatchingRouteNotFoundException
* @expectedExceptionMessage No matching route could be found for the path "non-existent"
* @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
*/
public function testCreateFromPathInvalid() {
$this->router->expects($this->once())
->method('match')
->with('/non-existent')
->method('matchRequest')
->with(Request::create('/non-existent'))
->will($this->throwException(new ResourceNotFoundException()));
$this->assertNull(Url::createFromPath('non-existent'));
@ -155,15 +159,10 @@ class UrlTest extends UnitTestCase {
*
* @covers ::createFromRequest()
*
* @expectedException \Drupal\Core\Routing\MatchingRouteNotFoundException
* @expectedExceptionMessage No matching route could be found for the request: request_as_a_string
* @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
*/
public function testCreateFromRequestInvalid() {
// Mock the request in order to override the __toString() method.
$request = $this->getMock('Symfony\Component\HttpFoundation\Request');
$request->expects($this->once())
->method('__toString')
->will($this->returnValue('request_as_a_string'));
$request = Request::create('/test-path');
$this->router->expects($this->once())
->method('matchRequest')