Issue #2323721 by dawehner, chx: Fixed [sechole] Link field item and menu link information leakage.
parent
aca1ec38eb
commit
68f576fe81
|
@ -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
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() . '/'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -81,6 +81,13 @@ class Shortcut extends ContentEntityBase implements ShortcutInterface {
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUrl() {
|
||||
return new Url($this->getRouteName(), $this->getRouteParams());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -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.'));
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue