diff --git a/core/core.services.yml b/core/core.services.yml index c43da8c..4bd3af1 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -660,11 +660,11 @@ services: csrf_token: class: Drupal\Core\Access\CsrfTokenGenerator arguments: ['@private_key'] - access_arguments_resolver: - class: Drupal\Core\Access\AccessArgumentsResolver + access_arguments_resolver_factory: + class: Drupal\Core\Access\AccessArgumentsResolverFactory access_manager: class: Drupal\Core\Access\AccessManager - arguments: ['@router.route_provider', '@url_generator', '@paramconverter_manager', '@access_arguments_resolver', '@request_stack'] + arguments: ['@router.route_provider', '@paramconverter_manager', '@access_arguments_resolver_factory'] calls: - [setContainer, ['@service_container']] access_route_subscriber: @@ -691,13 +691,13 @@ services: - { name: access_check, applies_to: _access_theme } access_check.custom: class: Drupal\Core\Access\CustomAccessCheck - arguments: ['@controller_resolver', '@access_arguments_resolver'] + arguments: ['@controller_resolver', '@access_arguments_resolver_factory'] tags: - { name: access_check, applies_to: _custom_access } access_check.csrf: class: Drupal\Core\Access\CsrfAccessCheck tags: - - { name: access_check, applies_to: _csrf_token } + - { name: access_check, applies_to: _csrf_token, needs_incoming_request: TRUE } arguments: ['@csrf_token'] maintenance_mode: class: Drupal\Core\Site\MaintenanceMode diff --git a/core/lib/Drupal/Component/Utility/ArgumentsResolver.php b/core/lib/Drupal/Component/Utility/ArgumentsResolver.php new file mode 100644 index 0000000..ecb4ce1 --- /dev/null +++ b/core/lib/Drupal/Component/Utility/ArgumentsResolver.php @@ -0,0 +1,150 @@ +scalars = $scalars; + $this->objects = $objects; + $this->wildcards = $wildcards; + } + + /** + * {@inheritdoc} + */ + public function getArguments(callable $callable) { + $arguments = array(); + foreach ($this->getReflector($callable)->getParameters() as $parameter) { + $arguments[] = $this->getArgument($parameter); + } + return $arguments; + } + + /** + * Returns the argument value for a parameter. + * + * @param \ReflectionParameter $parameter + * The parameter of a callable to get the value for. + * + * @return mixed + * The value of the requested parameter value. + * + * @throws \RuntimeException + * Thrown when there is a missing parameter. + */ + protected function getArgument(\ReflectionParameter $parameter) { + $parameter_type_hint = $parameter->getClass(); + $parameter_name = $parameter->getName(); + + // If the argument exists and is NULL, return it, regardless of + // parameter type hint. + if (!isset($this->objects[$parameter_name]) && array_key_exists($parameter_name, $this->objects)) { + return NULL; + } + + if ($parameter_type_hint) { + // If the argument exists and complies with the type hint, return it. + if (isset($this->objects[$parameter_name]) && is_object($this->objects[$parameter_name]) && $parameter_type_hint->isInstance($this->objects[$parameter_name])) { + return $this->objects[$parameter_name]; + } + // Otherwise, resolve wildcard arguments by type matching. + foreach ($this->wildcards as $wildcard) { + if ($parameter_type_hint->isInstance($wildcard)) { + return $wildcard; + } + } + } + elseif (isset($this->scalars[$parameter_name])) { + return $this->scalars[$parameter_name]; + } + + // If the callable provides a default value, use it. + if ($parameter->isDefaultValueAvailable()) { + return $parameter->getDefaultValue(); + } + + // Can't resolve it: call a method that throws an exception or can be + // overridden to do something else. + return $this->handleUnresolvedArgument($parameter); + } + + /** + * Returns a reflector for the access check callable. + * + * The access checker may be either a procedural function (in which case the + * callable is the function name) or a method (in which case the callable is + * an array of the object and method name). + * + * @param callable $callable + * The callable (either a function or a method). + * + * @return \ReflectionFunctionAbstract + * The ReflectionMethod or ReflectionFunction to introspect the callable. + */ + protected function getReflector(callable $callable) { + return is_array($callable) ? new \ReflectionMethod($callable[0], $callable[1]) : new \ReflectionFunction($callable); + } + + /** + * Handles unresolved arguments for getArgument(). + * + * Subclasses that override this method may return a default value + * instead of throwing an exception. + * + * @throws \RuntimeException + * Thrown when there is a missing parameter. + */ + protected function handleUnresolvedArgument(\ReflectionParameter $parameter) { + $class = $parameter->getDeclaringClass(); + $function = $parameter->getDeclaringFunction(); + if ($class && !$function->isClosure()) { + $function_name = $class->getName() . '::' . $function->getName(); + } + else { + $function_name = $function->getName(); + } + throw new \RuntimeException(sprintf('Callable "%s" requires a value for the "$%s" argument.', $function_name, $parameter->getName())); + } + +} diff --git a/core/lib/Drupal/Component/Utility/ArgumentsResolverInterface.php b/core/lib/Drupal/Component/Utility/ArgumentsResolverInterface.php new file mode 100644 index 0000000..3b27b7c --- /dev/null +++ b/core/lib/Drupal/Component/Utility/ArgumentsResolverInterface.php @@ -0,0 +1,26 @@ +getReflector($callable)->getParameters() as $parameter) { - $arguments[] = $this->getArgument($parameter, $route, $request, $account); - } - return $arguments; - } - - /** - * Returns the argument value for a parameter. - * - * @param \ReflectionParameter $parameter - * The parameter of a callable to get the value for. - * @param \Symfony\Component\Routing\Route $route - * The access checked route. - * @param \Symfony\Component\HttpFoundation\Request $request - * The current request. - * @param \Drupal\Core\Session\AccountInterface $account - * The current user. - * - * @return mixed - * The value of the requested parameter value. - * - * @throws \RuntimeException - * Thrown when there is a missing parameter. - */ - protected function getArgument(\ReflectionParameter $parameter, Route $route, Request $request, AccountInterface $account) { - $upcasted_route_arguments = $request->attributes->all(); - $raw_route_arguments = isset($upcasted_route_arguments['_raw_variables']) ? $upcasted_route_arguments['_raw_variables']->all() : $upcasted_route_arguments; - $parameter_type_hint = $parameter->getClass(); - $parameter_name = $parameter->getName(); - - // @todo Remove this once AccessManagerInterface::checkNamedRoute() is fixed - // to not leak _raw_variables from the request being duplicated. - // @see https://drupal.org/node/2265939 - $raw_route_arguments += $upcasted_route_arguments; - - // If the route argument exists and is NULL, return it, regardless of - // parameter type hint. - if (!isset($upcasted_route_arguments[$parameter_name]) && array_key_exists($parameter_name, $upcasted_route_arguments)) { - return NULL; - } - - if ($parameter_type_hint) { - // If the argument exists and complies with the type hint, return it. - if (isset($upcasted_route_arguments[$parameter_name]) && is_object($upcasted_route_arguments[$parameter_name]) && $parameter_type_hint->isInstance($upcasted_route_arguments[$parameter_name])) { - return $upcasted_route_arguments[$parameter_name]; - } - // Otherwise, resolve $request, $route, and $account by type matching - // only. This way, the callable may rename them in case the route - // defines other parameters with these names. - foreach (array($request, $route, $account) as $special_argument) { - if ($parameter_type_hint->isInstance($special_argument)) { - return $special_argument; - } - } - } - elseif (isset($raw_route_arguments[$parameter_name])) { - return $raw_route_arguments[$parameter_name]; - } - - // If the callable provides a default value, use it. - if ($parameter->isDefaultValueAvailable()) { - return $parameter->getDefaultValue(); - } - - // Can't resolve it: call a method that throws an exception or can be - // overridden to do something else. - return $this->handleUnresolvedArgument($parameter); - } - - /** - * Returns a reflector for the access check callable. - * - * The access checker may be either a procedural function (in which case the - * callable is the function name) or a method (in which case the callable is - * an array of the object and method name). - * - * @param callable $callable - * The callable (either a function or a method). - * - * @return \ReflectionFunctionAbstract - * The ReflectionMethod or ReflectionFunction to introspect the callable. - */ - protected function getReflector(callable $callable) { - return is_array($callable) ? new \ReflectionMethod($callable[0], $callable[1]) : new \ReflectionFunction($callable); - } - - /** - * Handles unresolved arguments for getArgument(). - * - * Subclasses that override this method may return a default value - * instead of throwing an exception. - * - * @throws \RuntimeException - * Thrown when there is a missing parameter. - */ - protected function handleUnresolvedArgument(\ReflectionParameter $parameter) { - $class = $parameter->getDeclaringClass(); - $function = $parameter->getDeclaringFunction(); - if ($class && !$function->isClosure()) { - $function_name = $class->getName() . '::' . $function->getName(); - } - else { - $function_name = $function->getName(); - } - throw new \RuntimeException(sprintf('Access callable "%s" requires a value for the "$%s" argument.', $function_name, $parameter->getName())); - } - -} diff --git a/core/lib/Drupal/Core/Access/AccessArgumentsResolverFactory.php b/core/lib/Drupal/Core/Access/AccessArgumentsResolverFactory.php new file mode 100644 index 0000000..cdef380 --- /dev/null +++ b/core/lib/Drupal/Core/Access/AccessArgumentsResolverFactory.php @@ -0,0 +1,45 @@ +getRouteObject(); + + // Defaults for the parameters defined on the route object need to be added + // to the raw arguments. + $raw_route_arguments = $route_match->getRawParameters()->all() + $route->getDefaults(); + + $upcasted_route_arguments = $route_match->getParameters()->all(); + + // Parameters which are not defined on the route object, but still are + // essential for access checking are passed as wildcards to the argument + // resolver. An access-check method with a parameter of type Route, + // RouteMatchInterface, AccountInterface or Request will receive those + // arguments regardless of the parameter name. + $wildcard_arguments = [$route, $route_match, $account]; + if (isset($request)) { + $wildcard_arguments[] = $request; + } + + return new ArgumentsResolver($raw_route_arguments, $upcasted_route_arguments, $wildcard_arguments); + } + +} diff --git a/core/lib/Drupal/Core/Access/AccessArgumentsResolverFactoryInterface.php b/core/lib/Drupal/Core/Access/AccessArgumentsResolverFactoryInterface.php new file mode 100644 index 0000000..1fdbb80 --- /dev/null +++ b/core/lib/Drupal/Core/Access/AccessArgumentsResolverFactoryInterface.php @@ -0,0 +1,34 @@ +routeProvider = $route_provider; - $this->urlGenerator = $url_generator; $this->paramConverterManager = $paramconverter_manager; - $this->argumentsResolver = $arguments_resolver; - $this->requestStack = $requestStack; + $this->argumentsResolverFactory = $arguments_resolver_factory; } /** * {@inheritdoc} */ - public function addCheckService($service_id, $service_method, array $applies_checks = array()) { + public function addCheckService($service_id, $service_method, array $applies_checks = array(), $needs_incoming_request = FALSE) { $this->checkIds[] = $service_id; $this->checkMethods[$service_id] = $service_method; + if ($needs_incoming_request) { + $this->checkNeedsRequest[$service_id] = $service_id; + } foreach ($applies_checks as $applies_check) { $this->staticRequirementMap[$applies_check][] = $service_id; } @@ -182,22 +169,17 @@ protected function applies(Route $route) { /** * {@inheritdoc} */ - public function checkNamedRoute($route_name, array $parameters = array(), AccountInterface $account, Request $route_request = NULL) { + public function checkNamedRoute($route_name, array $parameters, AccountInterface $account) { try { $route = $this->routeProvider->getRouteByName($route_name, $parameters); - if (empty($route_request)) { - // Create a cloned request with fresh attributes. - $route_request = RequestHelper::duplicate($this->requestStack->getCurrentRequest(), $this->urlGenerator->generate($route_name, $parameters)); - $route_request->attributes->replace(array()); - - // Populate $route_request->attributes with both raw and converted - // parameters. - $parameters += $route->getDefaults(); - $route_request->attributes->set('_raw_variables', new ParameterBag($parameters)); - $parameters[RouteObjectInterface::ROUTE_OBJECT] = $route; - $route_request->attributes->add($this->paramConverterManager->convert($parameters, $route_request)); - } - return $this->check($route, $route_request, $account); + + // ParamConverterManager relies on the route object being available + // from the parameters array. + $parameters[RouteObjectInterface::ROUTE_OBJECT] = $route; + $upcasted_parameters = $this->paramConverterManager->convert($parameters + $route->getDefaults(), new Request()); + + $route_match = new RouteMatch($route_name, $route, $upcasted_parameters, $parameters); + return $this->check($route_match, $account); } catch (RouteNotFoundException $e) { return FALSE; @@ -210,16 +192,37 @@ public function checkNamedRoute($route_name, array $parameters = array(), Accoun /** * {@inheritdoc} */ - public function check(Route $route, Request $request, AccountInterface $account) { + public function checkRequest(Request $request, AccountInterface $account) { + $route_match = RouteMatch::createFromRequest($request); + return $this->check($route_match, $account, $request); + } + + /** + * {@inheritdoc} + */ + public function check(RouteMatchInterface $route_match, AccountInterface $account, Request $request = NULL) { + $access = FALSE; + + $route = $route_match->getRouteObject(); $checks = $route->getOption('_access_checks') ?: array(); $conjunction = $route->getOption('_access_mode') ?: static::ACCESS_MODE_ALL; - if ($conjunction == static::ACCESS_MODE_ALL) { - return $this->checkAll($checks, $route, $request, $account); + // Filter out checks which require the incoming request. + if (!isset($request)) { + $checks = array_diff($checks, $this->checkNeedsRequest); } - else { - return $this->checkAny($checks, $route, $request, $account); + + if (!empty($checks)) { + $arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account, $request); + if ($conjunction == static::ACCESS_MODE_ALL) { + return $this->checkAll($checks, $arguments_resolver); + } + else { + return $this->checkAny($checks, $arguments_resolver); + } } + + return $access; } /** @@ -227,17 +230,13 @@ public function check(Route $route, Request $request, AccountInterface $account) * * @param array $checks * Contains the list of checks on the route definition. - * @param \Symfony\Component\Routing\Route $route - * The route to check access to. - * @param \Symfony\Component\HttpFoundation\Request $request - * The incoming request object. - * @param \Drupal\Core\Session\AccountInterface $account - * The current user. + * @param \Drupal\Component\Utility\ArgumentsResolverInterface $arguments_resolver + * The parametrized arguments resolver instance. * * @return bool - * Returns TRUE if the user has access to the route, else FALSE. + * Returns TRUE if the user has access to the route, else FALSE. */ - protected function checkAll(array $checks, Route $route, Request $request, AccountInterface $account) { + protected function checkAll(array $checks, ArgumentsResolverInterface $arguments_resolver) { $access = FALSE; foreach ($checks as $service_id) { @@ -245,7 +244,7 @@ protected function checkAll(array $checks, Route $route, Request $request, Accou $this->loadCheck($service_id); } - $service_access = $this->performCheck($service_id, $route, $request, $account); + $service_access = $this->performCheck($service_id, $arguments_resolver); if ($service_access === AccessInterface::ALLOW) { $access = TRUE; @@ -265,17 +264,13 @@ protected function checkAll(array $checks, Route $route, Request $request, Accou * * @param array $checks * Contains the list of checks on the route definition. - * @param \Symfony\Component\Routing\Route $route - * The route to check access to. - * @param \Symfony\Component\HttpFoundation\Request $request - * The incoming request object. - * @param \Drupal\Core\Session\AccountInterface $account - * The current user. + * @param \Drupal\Component\Utility\ArgumentsResolverInterface $arguments_resolver + * The parametrized arguments resolver instance. * * @return bool - * Returns TRUE if the user has access to the route, else FALSE. + * Returns TRUE if the user has access to the route, else FALSE. */ - protected function checkAny(array $checks, $route, $request, AccountInterface $account) { + protected function checkAny(array $checks, ArgumentsResolverInterface $arguments_resolver) { // No checks == deny by default. $access = FALSE; @@ -284,7 +279,7 @@ protected function checkAny(array $checks, $route, $request, AccountInterface $a $this->loadCheck($service_id); } - $service_access = $this->performCheck($service_id, $route, $request, $account); + $service_access = $this->performCheck($service_id, $arguments_resolver); if ($service_access === AccessInterface::ALLOW) { $access = TRUE; @@ -302,12 +297,8 @@ protected function checkAny(array $checks, $route, $request, AccountInterface $a * * @param string $service_id * The access check service ID to use. - * @param \Symfony\Component\Routing\Route $route - * The route to check access to. - * @param \Symfony\Component\HttpFoundation\Request $request - * The incoming request object. - * @param \Drupal\Core\Session\AccountInterface $account - * The current user. + * @param \Drupal\Component\Utility\ArgumentsResolverInterface $arguments_resolver + * The parametrized arguments resolver instance. * * @throws \Drupal\Core\Access\AccessException * Thrown when the access check returns an invalid value. @@ -315,9 +306,9 @@ protected function checkAny(array $checks, $route, $request, AccountInterface $a * @return string * A \Drupal\Core\Access\AccessInterface constant value. */ - protected function performCheck($service_id, $route, $request, $account) { + protected function performCheck($service_id, ArgumentsResolverInterface $arguments_resolver) { $callable = array($this->checks[$service_id], $this->checkMethods[$service_id]); - $arguments = $this->argumentsResolver->getArguments($callable, $route, $request, $account); + $arguments = $arguments_resolver->getArguments($callable); $service_access = call_user_func_array($callable, $arguments); if (!in_array($service_access, array(AccessInterface::ALLOW, AccessInterface::DENY, AccessInterface::KILL), TRUE)) { diff --git a/core/lib/Drupal/Core/Access/AccessManagerInterface.php b/core/lib/Drupal/Core/Access/AccessManagerInterface.php index df88ba6..9c1f76c 100644 --- a/core/lib/Drupal/Core/Access/AccessManagerInterface.php +++ b/core/lib/Drupal/Core/Access/AccessManagerInterface.php @@ -7,10 +7,11 @@ namespace Drupal\Core\Access; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; -use Symfony\Component\HttpFoundation\Request; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Routing\RouteMatchInterface; /** * Provides an interface for attaching and running access check services. @@ -46,14 +47,21 @@ * Optional array of values to substitute into the route path patern. * @param \Drupal\Core\Session\AccountInterface $account * The current user. - * @param \Symfony\Component\HttpFoundation\Request $route_request - * Optional incoming request object. If not provided, one will be built - * using the route information and the current request from the container. * * @return bool * Returns TRUE if the user has access to the route, otherwise FALSE. */ - public function checkNamedRoute($route_name, array $parameters = array(), AccountInterface $account, Request $route_request = NULL); + public function checkNamedRoute($route_name, array $parameters = array(), AccountInterface $account); + + /** + * Execute access checks against the incomming request. + * + * @param Request $request + * The incomming request. + * @param \Drupal\Core\Session\AccountInterface $account + * The current user. + */ + public function checkRequest(Request $request, AccountInterface $account); /** * For each route, saves a list of applicable access checks to the route. @@ -73,24 +81,27 @@ public function setChecks(RouteCollection $routes); * @param array $applies_checks * (optional) An array of route requirement keys the checker service applies * to. + * @param bool $needs_incoming_request + * (optional) True if access-check method only acts on an incoming request. */ - public function addCheckService($service_id, $service_method, array $applies_checks = array()); + public function addCheckService($service_id, $service_method, array $applies_checks = array(), $needs_incoming_request = FALSE); /** * Checks a route against applicable access check services. * * Determines whether the route is accessible or not. * - * @param \Symfony\Component\Routing\Route $route - * The route to check access to. - * @param \Symfony\Component\HttpFoundation\Request $request - * The incoming request object. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The route match. * @param \Drupal\Core\Session\AccountInterface $account * The current account. + * @param \Symfony\Component\HttpFoundation\Request $request + * Optional, a request. Only supply this parameter when checking the + * incomming request, do not specify when checking routes on output. * * @return bool * Returns TRUE if the user has access to the route, otherwise FALSE. */ - public function check(Route $route, Request $request, AccountInterface $account); + public function check(RouteMatchInterface $route_match, AccountInterface $account, Request $request = NULL); } diff --git a/core/lib/Drupal/Core/Access/CsrfAccessCheck.php b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php index f6b60d3..c8988dc 100644 --- a/core/lib/Drupal/Core/Access/CsrfAccessCheck.php +++ b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php @@ -33,7 +33,7 @@ class CsrfAccessCheck implements RoutingAccessInterface { * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token * The CSRF token generator. */ - function __construct(CsrfTokenGenerator $csrf_token) { + public function __construct(CsrfTokenGenerator $csrf_token) { $this->csrfToken = $csrf_token; } @@ -49,25 +49,9 @@ function __construct(CsrfTokenGenerator $csrf_token) { * A \Drupal\Core\Access\AccessInterface constant value. */ public function access(Route $route, Request $request) { - // If this is the controller request, check CSRF access as normal. - if ($request->attributes->get('_controller_request')) { - // @todo Remove dependency on the internal _system_path attribute: - // https://www.drupal.org/node/2293501. - return $this->csrfToken->validate($request->query->get('token'), $request->attributes->get('_system_path')) ? static::ALLOW : static::KILL; - } - - // Otherwise, this could be another requested access check that we don't - // want to check CSRF tokens on. - $conjunction = $route->getOption('_access_mode') ?: AccessManagerInterface::ACCESS_MODE_ANY; - // Return ALLOW if all access checks are needed. - if ($conjunction == AccessManagerInterface::ACCESS_MODE_ALL) { - return static::ALLOW; - } - // Return DENY otherwise, as another access checker should grant access - // for the route. - else { - return static::DENY; - } + // @todo Remove dependency on the internal _system_path attribute: + // https://www.drupal.org/node/2293501. + return $this->csrfToken->validate($request->query->get('token'), $request->attributes->get('_system_path')) ? static::ALLOW : static::KILL; } } diff --git a/core/lib/Drupal/Core/Access/CustomAccessCheck.php b/core/lib/Drupal/Core/Access/CustomAccessCheck.php index c128a5b..712ecd8 100644 --- a/core/lib/Drupal/Core/Access/CustomAccessCheck.php +++ b/core/lib/Drupal/Core/Access/CustomAccessCheck.php @@ -9,6 +9,7 @@ use Drupal\Core\Controller\ControllerResolverInterface; use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface; +use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; @@ -35,39 +36,41 @@ class CustomAccessCheck implements RoutingAccessInterface { /** * The arguments resolver. * - * @var \Drupal\Core\Access\AccessArgumentsResolverInterface + * @var \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface */ - protected $argumentsResolver; + protected $argumentsResolverFactory; /** * Constructs a CustomAccessCheck instance. * * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver * The controller resolver. - * @param \Drupal\Core\Access\AccessArgumentsResolverInterface $arguments_resolver - * The arguments resolver. + * @param \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface $arguments_resolver_factory + * The arguments resolver factory. */ - public function __construct(ControllerResolverInterface $controller_resolver, AccessArgumentsResolverInterface $arguments_resolver) { + public function __construct(ControllerResolverInterface $controller_resolver, AccessArgumentsResolverFactoryInterface $arguments_resolver_factory) { $this->controllerResolver = $controller_resolver; - $this->argumentsResolver = $arguments_resolver; + $this->argumentsResolverFactory = $arguments_resolver_factory; } /** * Checks access for the account and route using the custom access checker. * - * @param \Symfony\Component\Routing\Route $route - * The route to check against. - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The route match object to be checked. * @param \Drupal\Core\Session\AccountInterface $account - * The currently logged in account. + * The account being checked. + * @param \Symfony\Component\HttpFoundation\Request $request + * Optional, the request object. * * @return string * A \Drupal\Core\Access\AccessInterface constant value. */ - public function access(Route $route, Request $request, AccountInterface $account) { + public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account, Request $request = NULL) { $callable = $this->controllerResolver->getControllerFromDefinition($route->getRequirement('_custom_access')); - $arguments = $this->argumentsResolver->getArguments($callable, $route, $request, $account); + $arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account, $request); + $arguments = $arguments_resolver->getArguments($callable); + return call_user_func_array($callable, $arguments); } diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php index 69494af..4864eaf 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php @@ -28,6 +28,7 @@ public function process(ContainerBuilder $container) { foreach ($container->findTaggedServiceIds('access_check') as $id => $attributes) { $applies = array(); $method = 'access'; + $needs_incoming_request = FALSE; foreach ($attributes as $attribute) { if (isset($attribute['applies_to'])) { $applies[] = $attribute['applies_to']; @@ -35,8 +36,11 @@ public function process(ContainerBuilder $container) { if (isset($attribute['method'])) { $method = $attribute['method']; } + if (!empty($attribute['needs_incoming_request'])) { + $needs_incoming_request = TRUE; + } } - $access_manager->addMethodCall('addCheckService', array($id, $method, $applies)); + $access_manager->addMethodCall('addCheckService', array($id, $method, $applies, $needs_incoming_request)); } } } diff --git a/core/lib/Drupal/Core/Entity/EntityAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityAccessCheck.php index 951abb0..7e8f9a2 100644 --- a/core/lib/Drupal/Core/Entity/EntityAccessCheck.php +++ b/core/lib/Drupal/Core/Entity/EntityAccessCheck.php @@ -8,9 +8,9 @@ namespace Drupal\Core\Entity; use Drupal\Core\Routing\Access\AccessInterface; +use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\Routing\Route; -use Symfony\Component\HttpFoundation\Request; /** * Provides a generic access checker for entities. @@ -32,21 +32,22 @@ class EntityAccessCheck implements AccessInterface { * * @param \Symfony\Component\Routing\Route $route * The route to check against. - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The parametrized route * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * * @return string * A \Drupal\Core\Access\AccessInterface constant value. */ - public function access(Route $route, Request $request, AccountInterface $account) { + public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) { // Split the entity type and the operation. $requirement = $route->getRequirement('_entity_access'); list($entity_type, $operation) = explode('.', $requirement); // If there is valid entity of the given entity type, check its access. - if ($request->attributes->has($entity_type)) { - $entity = $request->attributes->get($entity_type); + $parameters = $route_match->getParameters(); + if ($parameters->has($entity_type)) { + $entity = $parameters->get($entity_type); if ($entity instanceof EntityInterface) { return $entity->access($operation, $account) ? static::ALLOW : static::DENY; } diff --git a/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php index e3fab39..fc68a97 100644 --- a/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php +++ b/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php @@ -8,8 +8,8 @@ namespace Drupal\Core\Entity; use Drupal\Core\Routing\Access\AccessInterface; +use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; /** @@ -46,22 +46,22 @@ public function __construct(EntityManagerInterface $entity_manager) { * * @param \Symfony\Component\Routing\Route $route * The route to check against. - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The parametrized route. * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * * @return string * A \Drupal\Core\Access\AccessInterface constant value. */ - public function access(Route $route, Request $request, AccountInterface $account) { + public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) { list($entity_type, $bundle) = explode(':', $route->getRequirement($this->requirementsKey) . ':'); // The bundle argument can contain request argument placeholders like // {name}, loop over the raw variables and attempt to replace them in the // bundle name. If a placeholder does not exist, it won't get replaced. if ($bundle && strpos($bundle, '{') !== FALSE) { - foreach ($request->get('_raw_variables')->all() as $name => $value) { + foreach ($route_match->getRawParameters()->all() as $name => $value) { $bundle = str_replace('{' . $name . '}', $value, $bundle); } // If we were unable to replace all placeholders, deny access. diff --git a/core/lib/Drupal/Core/Routing/AccessAwareRouter.php b/core/lib/Drupal/Core/Routing/AccessAwareRouter.php index b8409df..307cfc9 100644 --- a/core/lib/Drupal/Core/Routing/AccessAwareRouter.php +++ b/core/lib/Drupal/Core/Routing/AccessAwareRouter.php @@ -102,21 +102,8 @@ public function matchRequest(Request $request) { * The request to access check. */ protected function checkAccess(Request $request) { - // The controller is being handled by the HTTP kernel, so add an attribute - // to tell us this is the controller request. - $request->attributes->set('_controller_request', TRUE); - $e = FALSE; - try { - if (!$this->accessManager->check($request->attributes->get(RouteObjectInterface::ROUTE_OBJECT), $request, $this->account)) { - $e = new AccessDeniedHttpException(); - } - } - catch (\Exception $e) { - } - // @todo Once PHP 5.5 is a requirement refactor this using finally. - $request->attributes->remove('_controller_request'); - if ($e) { - throw $e; + if (!$this->accessManager->checkRequest($request, $this->account)) { + throw new AccessDeniedHttpException(); } } diff --git a/core/lib/Drupal/Core/Routing/RequestHelper.php b/core/lib/Drupal/Core/Routing/RequestHelper.php index a56ed83..55d85aa 100644 --- a/core/lib/Drupal/Core/Routing/RequestHelper.php +++ b/core/lib/Drupal/Core/Routing/RequestHelper.php @@ -15,120 +15,6 @@ class RequestHelper { /** - * Duplicates a request for another path. - * - * This method does basically the same as Request::create() but keeping all - * the previous variables to speed it up. - * - * @param \Symfony\Component\HttpFoundation\Request $original_request - * The original request object to clone. - * @param string $uri - * The URI. - * @param string $method - * The HTTP method. - * @param array $parameters - * The query (GET) or request (POST) parameters. - * @param array $query - * The GET parameters. - * @param array $post - * The POST parameters. - * @param array $attributes - * The request attributes (parameters parsed from the PATH_INFO, ...). - * @param array $cookies - * The COOKIE parameters. - * @param array $files - * The FILES parameters. - * @param array $server - * The SERVER parameters. - * - * @return \Symfony\Component\HttpFoundation\Request - * The cloned request instance. - * - * @see \Symfony\Component\HttpFoundation\Request::create() - * @see \Symfony\Component\HttpFoundation\Request::duplicate() - */ - public static function duplicate(Request $original_request, $uri, $method = 'GET', $parameters = array(), array $query = NULL, array $post = NULL, array $attributes = NULL, array $cookies = NULL, array $files = NULL, array $server = NULL) { - $request = $original_request->duplicate($query, $post, $attributes, $cookies, $files, $server); - - $server = array(); - - $server['PATH_INFO'] = ''; - $server['REQUEST_METHOD'] = strtoupper($method); - - $components = parse_url($uri); - if (isset($components['host'])) { - $server['SERVER_NAME'] = $components['host']; - $server['HTTP_HOST'] = $components['host']; - } - - if (isset($components['scheme'])) { - if ('https' === $components['scheme']) { - $server['HTTPS'] = 'on'; - $server['SERVER_PORT'] = 443; - } - else { - unset($server['HTTPS']); - $server['SERVER_PORT'] = 80; - } - } - - if (isset($components['port'])) { - $server['SERVER_PORT'] = $components['port']; - $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $components['port']; - } - - if (isset($components['user'])) { - $server['PHP_AUTH_USER'] = $components['user']; - } - - if (isset($components['pass'])) { - $server['PHP_AUTH_PW'] = $components['pass']; - } - - if (!isset($components['path'])) { - $components['path'] = '/'; - } - - switch (strtoupper($method)) { - case 'POST': - case 'PUT': - case 'DELETE': - if (!isset($server['CONTENT_TYPE'])) { - $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; - } - case 'PATCH': - $post = $parameters; - $query = array(); - break; - default: - $post = array(); - $query = $parameters; - break; - } - - if (isset($components['query'])) { - parse_str(html_entity_decode($components['query']), $query_string); - $query = array_replace($query_string, $query); - } - $query_string = http_build_query($query, '', '&'); - - // Prepend a ? if there is a query string. - if ($query_string !== '') { - $query_string = '?' . $query_string; - } - - $server['REQUEST_URI'] = $components['path'] . $query_string; - $server['QUERY_STRING'] = $query_string; - $request->server->add($server); - // The 'request' attribute name corresponds to $_REQUEST, but Symfony - // documents it as holding the POST parameters. - $request->request->add($post); - $request->query->add($query); - - return $request; - } - - /** * Returns whether the request is using a clean URL. * * A clean URL is one that does not include the script name. For example, diff --git a/core/modules/config_translation/config_translation.services.yml b/core/modules/config_translation/config_translation.services.yml index f4ed510..e6c00b6 100644 --- a/core/modules/config_translation/config_translation.services.yml +++ b/core/modules/config_translation/config_translation.services.yml @@ -7,13 +7,13 @@ services: config_translation.access.overview: class: Drupal\config_translation\Access\ConfigTranslationOverviewAccess - arguments: ['@plugin.manager.config_translation.mapper'] + arguments: ['@plugin.manager.config_translation.mapper', '@language_manager'] tags: - { name: access_check, applies_to: _config_translation_overview_access } config_translation.access.form: class: Drupal\config_translation\Access\ConfigTranslationFormAccess - arguments: ['@plugin.manager.config_translation.mapper'] + arguments: ['@plugin.manager.config_translation.mapper', '@language_manager'] tags: - { name: access_check, applies_to: _config_translation_form_access } diff --git a/core/modules/config_translation/src/Access/ConfigTranslationFormAccess.php b/core/modules/config_translation/src/Access/ConfigTranslationFormAccess.php index 791ef85..f9a5116 100644 --- a/core/modules/config_translation/src/Access/ConfigTranslationFormAccess.php +++ b/core/modules/config_translation/src/Access/ConfigTranslationFormAccess.php @@ -8,7 +8,6 @@ namespace Drupal\config_translation\Access; use Drupal\Core\Session\AccountInterface; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; /** @@ -19,12 +18,12 @@ class ConfigTranslationFormAccess extends ConfigTranslationOverviewAccess { /** * {@inheritdoc} */ - public function access(Route $route, Request $request, AccountInterface $account) { + public function access(Route $route, AccountInterface $account, $langcode = NULL) { // For the translation forms we have a target language, so we need some // checks in addition to the checks performed for the translation overview. - $base_access = parent::access($route, $request, $account); + $base_access = parent::access($route, $account); if ($base_access === static::ALLOW) { - $target_language = language_load($request->attributes->get('langcode')); + $target_language = $this->languageManager->getLanguage($langcode); // Make sure that the target language is not locked, and that the target // language is not the original submission language. Although technically @@ -33,7 +32,7 @@ public function access(Route $route, Request $request, AccountInterface $account $access = !empty($target_language) && !$target_language->locked && - $target_language->id != $this->sourceLanguage->id; + (empty($this->sourceLanguage) || ($target_language->id != $this->sourceLanguage->id)); return $access ? static::ALLOW : static::DENY; } diff --git a/core/modules/config_translation/src/Access/ConfigTranslationOverviewAccess.php b/core/modules/config_translation/src/Access/ConfigTranslationOverviewAccess.php index 529bc4a..b8203a2 100644 --- a/core/modules/config_translation/src/Access/ConfigTranslationOverviewAccess.php +++ b/core/modules/config_translation/src/Access/ConfigTranslationOverviewAccess.php @@ -7,10 +7,10 @@ namespace Drupal\config_translation\Access; +use Drupal\Core\Language\LanguageManagerInterface; use Drupal\config_translation\ConfigMapperManagerInterface; use Drupal\Core\Routing\Access\AccessInterface; use Drupal\Core\Session\AccountInterface; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; /** @@ -26,6 +26,13 @@ class ConfigTranslationOverviewAccess implements AccessInterface { protected $configMapperManager; /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManager; + + /** * The source language. * * @var \Drupal\Core\Language\LanguageInterface @@ -38,8 +45,9 @@ class ConfigTranslationOverviewAccess implements AccessInterface { * @param \Drupal\config_translation\ConfigMapperManagerInterface $config_mapper_manager * The mapper plugin discovery service. */ - public function __construct(ConfigMapperManagerInterface $config_mapper_manager) { + public function __construct(ConfigMapperManagerInterface $config_mapper_manager, LanguageManagerInterface $language_manager) { $this->configMapperManager = $config_mapper_manager; + $this->languageManager = $language_manager; } /** @@ -47,29 +55,25 @@ public function __construct(ConfigMapperManagerInterface $config_mapper_manager) * * @param \Symfony\Component\Routing\Route $route * The route to check against. - * @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(Route $route, Request $request, AccountInterface $account) { + public function access(Route $route, AccountInterface $account) { /** @var \Drupal\config_translation\ConfigMapperInterface $mapper */ $mapper = $this->configMapperManager->createInstance($route->getDefault('plugin_id')); - $mapper->populateFromRequest($request); - - $this->sourceLanguage = $mapper->getLanguageWithFallback(); + $this->sourceLanguage = $this->languageManager->getLanguage($mapper->getLangcode()); // Allow access to the translation overview if the proper permission is // granted, the configuration has translatable pieces, and the source - // language is not locked. + // language is not locked if it is present. $access = $account->hasPermission('translate configuration') && $mapper->hasSchema() && $mapper->hasTranslatable() && - !$this->sourceLanguage->locked; + empty($this->sourceLanguage->locked); return $access ? static::ALLOW : static::DENY; } diff --git a/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php b/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php index 5f12452..b8a22a6 100644 --- a/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php +++ b/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php @@ -11,8 +11,8 @@ use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Routing\Access\AccessInterface; +use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; /** @@ -52,8 +52,8 @@ public function __construct(EntityManagerInterface $manager, LanguageManagerInte * * @param \Symfony\Component\Routing\Route $route * The route to check against. - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The parametrized route. * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * @param string $source @@ -69,9 +69,9 @@ public function __construct(EntityManagerInterface $manager, LanguageManagerInte * @return string * A \Drupal\Core\Access\AccessInterface constant value. */ - public function access(Route $route, Request $request, AccountInterface $account, $source = NULL, $target = NULL, $language = NULL, $entity_type_id = NULL) { + public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account, $source = NULL, $target = NULL, $language = NULL, $entity_type_id = NULL) { /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */ - if ($entity = $request->attributes->get($entity_type_id)) { + if ($entity = $route_match->getParameter($entity_type_id)) { $operation = $route->getRequirement('_access_content_translation_manage'); diff --git a/core/modules/content_translation/src/Access/ContentTranslationOverviewAccess.php b/core/modules/content_translation/src/Access/ContentTranslationOverviewAccess.php index 893b8b1..d3c1b0a 100644 --- a/core/modules/content_translation/src/Access/ContentTranslationOverviewAccess.php +++ b/core/modules/content_translation/src/Access/ContentTranslationOverviewAccess.php @@ -9,8 +9,8 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Routing\Access\AccessInterface; +use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; -use Symfony\Component\HttpFoundation\Request; /** * Access check for entity translation overview. @@ -37,23 +37,25 @@ public function __construct(EntityManagerInterface $manager) { /** * Checks access to the translation overview for the entity and bundle. * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The parametrized route. * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. + * @param string $entity_type_id + * The entity type ID. * * @return string * A \Drupal\Core\Access\AccessInterface constant value. */ - public function access(Request $request, AccountInterface $account) { - $entity_type = $request->attributes->get('entity_type_id'); - $entity = $request->attributes->get($entity_type); + public function access(RouteMatchInterface $route_match, AccountInterface $account, $entity_type_id) { + /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $route_match->getParameter($entity_type_id); if ($entity && $entity->isTranslatable()) { // Get entity base info. $bundle = $entity->bundle(); // Get entity access callback. - $definition = $this->entityManager->getDefinition($entity_type); + $definition = $this->entityManager->getDefinition($entity_type_id); $translation = $definition->get('translation'); $access_callback = $translation['content_translation']['access_callback']; if (call_user_func($access_callback, $entity)) { @@ -66,9 +68,9 @@ public function access(Request $request, AccountInterface $account) { } // Check per entity permission. - $permission = "translate {$entity_type}"; + $permission = "translate {$entity_type_id}"; if ($definition->getPermissionGranularity() == 'bundle') { - $permission = "translate {$bundle} {$entity_type}"; + $permission = "translate {$bundle} {$entity_type_id}"; } if ($account->hasPermission($permission)) { return static::ALLOW; diff --git a/core/modules/field_ui/src/Access/FormModeAccessCheck.php b/core/modules/field_ui/src/Access/FormModeAccessCheck.php index d35dd15..3a7e57a 100644 --- a/core/modules/field_ui/src/Access/FormModeAccessCheck.php +++ b/core/modules/field_ui/src/Access/FormModeAccessCheck.php @@ -9,9 +9,9 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Routing\Access\AccessInterface; +use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\Routing\Route; -use Symfony\Component\HttpFoundation\Request; /** * Defines an access check for entity form mode routes. @@ -42,8 +42,8 @@ public function __construct(EntityManagerInterface $entity_manager) { * * @param \Symfony\Component\Routing\Route $route * The route to check against. - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The parametrized route. * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * @param string $form_mode_name @@ -60,11 +60,11 @@ public function __construct(EntityManagerInterface $entity_manager) { * @return string * A \Drupal\Core\Access\AccessInterface constant value. */ - public function access(Route $route, Request $request, AccountInterface $account, $form_mode_name = 'default', $bundle = NULL) { + public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account, $form_mode_name = 'default', $bundle = NULL) { if ($entity_type_id = $route->getDefault('entity_type_id')) { if (!isset($bundle)) { $entity_type = $this->entityManager->getDefinition($entity_type_id); - $bundle = $request->attributes->get('_raw_variables')->get($entity_type->getBundleEntityType()); + $bundle = $route_match->getRawParameter($entity_type->getBundleEntityType()); } $visibility = FALSE; diff --git a/core/modules/field_ui/src/Access/ViewModeAccessCheck.php b/core/modules/field_ui/src/Access/ViewModeAccessCheck.php index 8e93040..4fef23e 100644 --- a/core/modules/field_ui/src/Access/ViewModeAccessCheck.php +++ b/core/modules/field_ui/src/Access/ViewModeAccessCheck.php @@ -9,9 +9,9 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Routing\Access\AccessInterface; +use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\Routing\Route; -use Symfony\Component\HttpFoundation\Request; /** * Defines an access check for entity view mode routes. @@ -42,8 +42,8 @@ public function __construct(EntityManagerInterface $entity_manager) { * * @param \Symfony\Component\Routing\Route $route * The route to check against. - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The parametrized route. * @param \Drupal\Core\Session\AccountInterface $account * The currently logged in account. * @param string $view_mode_name @@ -60,11 +60,11 @@ public function __construct(EntityManagerInterface $entity_manager) { * @return string * A \Drupal\Core\Access\AccessInterface constant value. */ - public function access(Route $route, Request $request, AccountInterface $account, $view_mode_name = 'default', $bundle = NULL) { + public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account, $view_mode_name = 'default', $bundle = NULL) { if ($entity_type_id = $route->getDefault('entity_type_id')) { if (!isset($bundle)) { $entity_type = $this->entityManager->getDefinition($entity_type_id); - $bundle = $request->attributes->get('_raw_variables')->get($entity_type->getBundleEntityType()); + $bundle = $route_match->getRawParameter($entity_type->getBundleEntityType()); } $visibility = FALSE; diff --git a/core/modules/system/src/PathBasedBreadcrumbBuilder.php b/core/modules/system/src/PathBasedBreadcrumbBuilder.php index 7f1bec6..1075ee7 100644 --- a/core/modules/system/src/PathBasedBreadcrumbBuilder.php +++ b/core/modules/system/src/PathBasedBreadcrumbBuilder.php @@ -139,11 +139,7 @@ public function build(RouteMatchInterface $route_match) { // Copy the path elements for up-casting. $route_request = $this->getRequestForPath(implode('/', $path_elements), $exclude); if ($route_request) { - $route_name = $route_request->attributes->get(RouteObjectInterface::ROUTE_NAME); - // Note that the parameters don't really matter here since we're - // passing in the request which already has the upcast attributes. - $parameters = array(); - $access = $this->accessManager->checkNamedRoute($route_name, $parameters, $this->currentUser, $route_request); + $access = $this->accessManager->checkRequest($route_request, $this->currentUser); if ($access) { $title = $this->titleResolver->getTitle($route_request, $route_request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)); } diff --git a/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php b/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php index 5d24f20..3115b18 100644 --- a/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php +++ b/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php @@ -325,7 +325,7 @@ public function testBuildWithUserPath() { */ public function setupAccessManagerWithTrue() { $this->accessManager->expects($this->any()) - ->method('checkNamedRoute') + ->method('checkRequest') ->will($this->returnValue(TRUE)); } diff --git a/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php b/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php new file mode 100644 index 0000000..c786308 --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php @@ -0,0 +1,221 @@ +getArguments($callable); + $this->assertSame($expected, $arguments); + } + + /** + * Provides test data to testGetArgument(). + */ + public function providerTestGetArgument() { + $data = []; + + // Test an optional parameter with no provided value. + $data[] = [ + function($foo = 'foo') {}, [], [], [] , ['foo'], + ]; + + // Test an optional parameter with a provided value. + $data[] = [ + function($foo = 'foo') {}, ['foo' => 'bar'], [], [], ['bar'], + ]; + + // Test with a provided value. + $data[] = [ + function($foo) {}, ['foo' => 'bar'], [], [], ['bar'], + ]; + + // Test with an explicitly NULL value. + $data[] = [ + function($foo) {}, [], ['foo' => NULL], [], [NULL], + ]; + + // Test with a raw value that overrides the provided upcast value, since + // it is not typehinted. + $scalars = ['foo' => 'baz']; + $objects = ['foo' => new \stdClass()]; + $data[] = [ + function($foo) {}, $scalars, $objects, [], ['baz'], + ]; + + return $data; + } + + /** + * Tests getArgument() with an object. + */ + public function testGetArgumentObject() { + $callable = function(\stdClass $object) {}; + + $object = new \stdClass(); + $arguments = (new ArgumentsResolver([], ['object' => $object], []))->getArguments($callable); + $this->assertSame([$object], $arguments); + } + + /** + * Tests getArgument() with a wildcard object for a parameter with a custom name. + */ + public function testGetWilcardArgument() { + $callable = function(\stdClass $custom_name) {}; + + $object = new \stdClass(); + $arguments = (new ArgumentsResolver([], [], [$object]))->getArguments($callable); + $this->assertSame([$object], $arguments); + } + + /** + * Tests getArgument() with a Route, Request, and Account object. + */ + public function testGetArgumentOrder() { + $a1 = $this->getMock('\Drupal\Tests\Component\Utility\TestInterface1'); + $a2 = $this->getMock('\Drupal\Tests\Component\Utility\TestClass'); + $a3 = $this->getMock('\Drupal\Tests\Component\Utility\TestInterface2'); + + $objects = [ + 't1' => $a1, + 'tc' => $a2, + ]; + $wildcards = [$a3]; + $resolver = new ArgumentsResolver([], $objects, $wildcards); + + $callable = function(TestInterface1 $t1, TestClass $tc, TestInterface2 $t2) {}; + $arguments = $resolver->getArguments($callable); + $this->assertSame([$a1, $a2, $a3], $arguments); + + // Test again, but with the arguments in a different order. + $callable = function(TestInterface2 $t2, TestClass $tc, TestInterface1 $t1) {}; + $arguments = $resolver->getArguments($callable); + $this->assertSame([$a3, $a2, $a1], $arguments); + } + + /** + * Tests getArgument() with a wildcard parameter with no typehint. + * + * Without the typehint, the wildcard object will not be passed to the callable. + * + * @expectedException \RuntimeException + * @expectedExceptionMessage requires a value for the "$route" argument. + */ + public function testGetWildcardArgumentNoTypehint() { + $a = $this->getMock('\Drupal\Tests\Component\Utility\TestInterface1'); + $wildcards = [$a]; + $resolver = new ArgumentsResolver([], [], $wildcards); + + $callable = function($route) {}; + $arguments = $resolver->getArguments($callable); + $this->assertNull($arguments); + } + + /** + * Tests getArgument() with a named parameter with no typehint and a value. + * + * Without the typehint, passing a value to a named parameter will still + * receive the provided value. + */ + public function testGetArgumentRouteNoTypehintAndValue() { + $scalars = ['route' => 'foo']; + $resolver = new ArgumentsResolver($scalars, [], []); + + $callable = function($route) {}; + $arguments = $resolver->getArguments($callable); + $this->assertSame(['foo'], $arguments); + } + + /** + * Tests handleUnresolvedArgument() for a scalar argument. + * + * @expectedException \RuntimeException + * @expectedExceptionMessage requires a value for the "$foo" argument. + */ + public function testHandleNotUpcastedArgument() { + $objects = ['foo' => 'bar']; + $scalars = ['foo' => 'baz']; + $resolver = new ArgumentsResolver($scalars, $objects, []); + + $callable = function(\stdClass $foo) {}; + $arguments = $resolver->getArguments($callable); + $this->assertNull($arguments); + } + + /** + * Tests handleUnresolvedArgument() for missing arguments. + * + * @expectedException \RuntimeException + * @expectedExceptionMessage requires a value for the "$foo" argument. + * + * @dataProvider providerTestHandleUnresolvedArgument + */ + public function testHandleUnresolvedArgument($callable) { + $resolver = new ArgumentsResolver([], [], []); + $arguments = $resolver->getArguments($callable); + $this->assertNull($arguments); + } + + /** + * Provides test data to testHandleUnresolvedArgument(). + */ + public function providerTestHandleUnresolvedArgument() { + $data = []; + $data[] = [function($foo) {}]; + $data[] = [[new TestClass(), 'access']]; + $data[] = ['test_access_arguments_resolver_access']; + return $data; + } + +} + +/** + * Provides a test class. + */ +class TestClass { + public function access($foo) { + } +} + +/** + * Provides a test interface. + */ +interface TestInterface1 { +} + +/** + * Provides a different test interface. + */ +interface TestInterface2 { +} + +} + +namespace { + function test_access_arguments_resolver_access($foo) { + } +} diff --git a/core/tests/Drupal/Tests/Core/Access/AccessArgumentsResolverTest.php b/core/tests/Drupal/Tests/Core/Access/AccessArgumentsResolverTest.php deleted file mode 100644 index 2a46556..0000000 --- a/core/tests/Drupal/Tests/Core/Access/AccessArgumentsResolverTest.php +++ /dev/null @@ -1,242 +0,0 @@ -account = $this->getMock('Drupal\Core\Session\AccountInterface'); - $this->route = new Route('/test'); - } - - /** - * Tests the getArgument() method. - * - * @dataProvider providerTestGetArgument - */ - public function testGetArgument($callable, $request, $expected) { - $arguments = (new AccessArgumentsResolver())->getArguments($callable, $this->route, $request, $this->account); - $this->assertSame($expected, $arguments); - } - - /** - * Provides test data to testGetArgument(). - */ - public function providerTestGetArgument() { - $data = array(); - - // Test an optional parameter with no provided value. - $data[] = array( - function($foo = 'foo') {}, new Request(), array('foo'), - ); - - // Test an optional parameter with a provided value. - $request = new Request(); - $request->attributes->set('foo', 'bar'); - $data[] = array( - function($foo = 'foo') {}, $request, array('bar'), - ); - - // Test with a provided value. - $request = new Request(); - $request->attributes->set('foo', 'bar'); - $data[] = array( - function($foo) {}, $request, array('bar'), - ); - - // Test with an explicitly NULL value. - $request = new Request(); - $request->attributes->set('foo', NULL); - $data[] = array( - function($foo) {}, $request, array(NULL), - ); - - // Test with a raw value that overrides the provided upcast value, since - // it is not typehinted. - $request = new Request(); - $request->attributes->set('foo', 'bar'); - $request->attributes->set('_raw_variables', new ParameterBag(array('foo' => 'baz'))); - $data[] = array( - function($foo) {}, $request, array('baz'), - ); - - return $data; - } - - /** - * Tests getArgument() with a Route object. - */ - public function testGetArgumentRoute() { - $callable = function(Route $route) {}; - $request = new Request(); - - $arguments = (new AccessArgumentsResolver())->getArguments($callable, $this->route, $request, $this->account); - $this->assertSame(array($this->route), $arguments); - } - - /** - * Tests getArgument() with a Route object for a parameter with a custom name. - */ - public function testGetArgumentRouteCustomName() { - $callable = function(Route $custom_name) {}; - $request = new Request(); - - $arguments = (new AccessArgumentsResolver())->getArguments($callable, $this->route, $request, $this->account); - $this->assertSame(array($this->route), $arguments); - } - - /** - * Tests getArgument() with a Route, Request, and Account object. - */ - public function testGetArgumentRouteRequestAccount() { - $callable = function(Route $route, Request $request, AccountInterface $account) {}; - $request = new Request(); - - $arguments = (new AccessArgumentsResolver())->getArguments($callable, $this->route, $request, $this->account); - $this->assertSame(array($this->route, $request, $this->account), $arguments); - - // Test again, but with the arguments in a different order. - $callable = function(AccountInterface $account, Request $request, Route $route) {}; - $request = new Request(); - - $arguments = (new AccessArgumentsResolver())->getArguments($callable, $this->route, $request, $this->account); - $this->assertSame(array($this->account, $request, $this->route), $arguments); - } - - /** - * Tests getArgument() with a '$route' parameter with no typehint. - * - * Without the typehint, the Route object will not be passed to the callable. - * - * @expectedException \RuntimeException - * @expectedExceptionMessage requires a value for the "$route" argument. - */ - public function testGetArgumentRouteNoTypehint() { - $callable = function($route) {}; - $request = new Request(); - - $arguments = (new AccessArgumentsResolver())->getArguments($callable, $this->route, $request, $this->account); - $this->assertNull($arguments); - } - - /** - * Tests getArgument() with a '$route' parameter with no typehint and a value. - * - * Without the typehint, passing a value to a parameter named '$route' will - * still receive the provided value. - */ - public function testGetArgumentRouteNoTypehintAndValue() { - $callable = function($route) {}; - $request = new Request(); - $request->attributes->set('route', 'foo'); - - $arguments = (new AccessArgumentsResolver())->getArguments($callable, $this->route, $request, $this->account); - $this->assertSame(array('foo'), $arguments); - } - - /** - * Tests getArgument() when upcasting is bypassed. - */ - public function testGetArgumentBypassUpcasting() { - $callable = function(Route $route = NULL) {}; - - $request = new Request(); - $request->attributes->set('route', NULL); - - $arguments = (new AccessArgumentsResolver())->getArguments($callable, $this->route, $request, $this->account); - $this->assertSame(array(NULL), $arguments); - } - - /** - * Tests handleUnresolvedArgument() for a non-upcast argument. - * - * @expectedException \RuntimeException - * @expectedExceptionMessage requires a value for the "$foo" argument. - */ - public function testHandleNotUpcastedArgument() { - $callable = function(\stdClass $foo) {}; - - $request = new Request(); - $request->attributes->set('foo', 'bar'); - $request->attributes->set('_raw_variables', new ParameterBag(array('foo' => 'baz'))); - - $arguments = (new AccessArgumentsResolver())->getArguments($callable, $this->route, $request, $this->account); - $this->assertNull($arguments); - } - - /** - * Tests handleUnresolvedArgument() for missing arguments. - * - * @expectedException \RuntimeException - * @expectedExceptionMessage requires a value for the "$foo" argument. - * - * @dataProvider providerTestHandleUnresolvedArgument - */ - public function testHandleUnresolvedArgument($callable) { - $request = new Request(); - - $arguments = (new AccessArgumentsResolver())->getArguments($callable, $this->route, $request, $this->account); - $this->assertNull($arguments); - } - - /** - * Provides test data to testHandleUnresolvedArgument(). - */ - public function providerTestHandleUnresolvedArgument() { - $data = array(); - $data[] = array(function($foo) {}); - $data[] = array(array(new TestClass(), 'access')); - $data[] = array('test_access_arguments_resolver_access'); - return $data; - } - -} - -/** - * Provides a test class. - */ -class TestClass { - public function access($foo) { - } -} - -} - -namespace { - function test_access_arguments_resolver_access($foo) { - } -} diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityAccessCheckTest.php index ba5ffcf..3b70d5e 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityAccessCheckTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityAccessCheckTest.php @@ -7,7 +7,7 @@ namespace Drupal\Tests\Core\Entity; -use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\Routing\Route; use Drupal\Core\Access\AccessCheckInterface; use Drupal\Core\Entity\EntityAccessCheck; @@ -25,7 +25,7 @@ class EntityAccessCheckTest extends UnitTestCase { */ public function testAccess() { $route = new Route('/foo', array(), array('_entity_access' => 'node.update')); - $request = new Request(); + $upcasted_arguments = new ParameterBag(); $node = $this->getMockBuilder('Drupal\node\Entity\Node') ->disableOriginalConstructor() ->getMock(); @@ -33,9 +33,9 @@ public function testAccess() { ->method('access') ->will($this->returnValue(TRUE)); $access_check = new EntityAccessCheck(); - $request->attributes->set('node', $node); + $upcasted_arguments->set('node', $node); $account = $this->getMock('Drupal\Core\Session\AccountInterface'); - $access = $access_check->access($route, $request, $account); + $access = $access_check->access($route, $upcasted_arguments, $account); $this->assertSame(AccessCheckInterface::ALLOW, $access); }