1

The example given in Access checking on routes for using _custom_access is the following.

example.routing.yml

example:
  path: '/example'
  defaults:
    _controller: '\Drupal\example\Controller\ExampleController::content'
  requirements:
    _custom_access:  '\Drupal\example\Controller\ExampleController::access'

ExampleController.php

/**
 * @file
 * Contains \Drupal\example\Controller\ExamleController.
 */

namespace Drupal\example\Controller;

use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpFoundation\Request;

    /**
     * Builds an example page.
     */
    class ExampleController {

      /**
       * Checks access for a specific request.
       *
       * @param \Drupal\Core\Session\AccountInterface $account
       *   Run access checks for this account.
       */
      public function access(AccountInterface $account) {
        // Check permissions and combine that with any custom access checking needed. Pass forward
        // parameters from the route and/or request as needed.
        return $account->hasPermission('do example things') && $this->someOtherCustomCondition();
      }
    }

From this example, I get the first parameter of such callbacks is $account as I would expect, since the access is normally checked against an account.

Looking at code used from Drupal core modules, I find the following.

toolbar.routing.yml

toolbar.subtrees:
  path: '/toolbar/subtrees/{hash}'
  defaults:
    _controller: '\Drupal\toolbar\Controller\ToolbarController::subtreesAjax'
  options:
    _theme: ajax_base_page
  requirements:
    _custom_access: '\Drupal\toolbar\Controller\ToolbarController::checkSubTreeAccess'

ToolbarController.php

public function checkSubTreeAccess($hash) {
  $expected_hash = _toolbar_get_subtrees_hash() [0];
  return AccessResult::allowedIf($this->currentUser()->hasPermission('access toolbar') && Crypt::hashEquals($expected_hash, $hash))->cachePerPermissions();
}

In this case, $account is not passed as parameter, but the controller gets it from the dependency injection container, which leaves me puzzled.

I also checked at the class implementing the access service used for _custom_access, but I just see the following code, which doesn't make things clearer.

public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
  $callable = $this->controllerResolver->getControllerFromDefinition($route->getRequirement('_custom_access'));
  $arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account);
  $arguments = $arguments_resolver->getArguments($callable);

  return call_user_func_array($callable, $arguments);
}

(See CustomAccessCheck::access().)

If then I watch at ArgumentsResolver::getArguments() (the method called with $arguments_resolver->getArguments($callable) in the previous method), I see the following code.

public function getArguments(callable $callable) {
  $arguments = array();
  foreach ($this->getReflector($callable)->getParameters() as $parameter) {
    $arguments[] = $this->getArgument($parameter);
  }
  return $arguments;
}

$account seems ignored.

What arguments does a _custom_access callback exactly receive?

7

See \Drupal\Core\Access\AccessArgumentsResolverFactory::getArgumentsResolver() which is called by your snippet above to create the argument resolver, I think that covers it pretty well.

It gets all raw parameters, all possibly upcasted parameters, plus request, route match and account. The access callback receives the arguments from that list, based on the names and type hints.

So account isn't ignored.

That logic is the same as for any other access check, the whole point of _custom_access is to have an easy way to do a one-off access check without having to go through the additional steps of a service that's defined with a special tag.

  • Then, the Toolbar module could have used checkSubTreeAccess($hash, $account) instead of using the code it is actually using. It is its code that confused me. Is there any reason for the module to use $this->currentUser() to check the user permissions? – kiamlaluno May 18 '16 at 8:01
  • No reason. It was probably converted from something and the person who did it didn't know. Route access check is always for the current user, so it doesn't really make a difference – Berdir May 18 '16 at 8:06
  • Exactly what I thought. It would not be access checking if the current user is not considered. (As side note, it's a shame you don't get more up-votes for the helpful answers you give.) Thank you! – kiamlaluno May 18 '16 at 8:11
  • No problem. Yeah, as I wrote on meta once, Drupal Answers in general only gets few upvotes I think. – Berdir May 18 '16 at 9:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.