Problem/Motivation

Drupal performs access checks on multiple occasions during a typical request-response cycle. The first time immediately after routing (from within the AccessAwareRouter) on the incoming request. But also during the rendering phase, a great number of checks are performed in order to determine if certain page elements should be rendered or not. A common use-case is when rendering menu-links. Those access checks are not applied to the current incoming request, but to potential future requests.

Currently the access-manager takes a Symfony Request object as a parameter for its check() method. On the first sight this seems very obvious but it has the rather nasty consequence that a fake request object has to be created when running access-checks before e.g. rendering links. This has proven to be cumbersome and rather costly (#2078855: Request::create() is slow, use a helper to duplicate instead).

Additionally there is only one access-check in core which genuinely requires a request object, and only should ever be run against the incoming request: The CsrfAccessCheck. All the other access checks can be rewritten in a way such that they only depend on the route defaults, the raw parameters and the upcasted parameters.

Therefore let's reverse the current situation and only pass in the request object to the access checkers when checking the incoming request. Access check services wishing to act on the incoming request need to declare that via a service tag attribute. When checking potential future requests those checks will not be run.

This allows us to streamline AccessManager::checkNamedRoute() and remove RequestHelper::duplicate().

Proposed resolution

  • Instead of a Request object, pass a RouteMatch to access-check methods by default.
  • Access check services wishing to act on the incoming request need to declare that using the service tag attribute needs_incoming_request. Use it for CsrfAccessCheck
  • Fix all access checks which do not genuinely need the request-object
  • Remove the _controller_request request argument workaround from AccessAwareRouter and CsrfAccessCheck
  • Adapt unit-tests, no changes are to be expected in web-tests

Remaining tasks

Review.

User interface changes

None.

API changes

  • Removes RequestHelper::duplicate()
  • Introduces AccessManagerInterface::checkRequest()
  • Changes check(Route $route, Request $request, AccountInterface $account) to function check(RouteMatchInterface $route_match, AccountInterface $account, Request $request = NULL) on AccessManagerInterface
  • Removes $request-parameter from AccessManagerInterface::checkNamedRoute()
  • Adds optional $needs_incoming_request to AccessManagerInterface::addCheckService()
CommentFileSizeAuthor
#64 interdiff.txt1.82 KBznerol
#64 2331079-use-route-match-in-access-checks-64.diff122.85 KBznerol
#61 2331079-use-route-match-in-access-checks-61.diff121.89 KBznerol
#57 2331079-use-route-match-in-access-checks-57.diff115.11 KBznerol
#57 interdiff.txt1.62 KBznerol
#55 2331079-use-route-match-in-access-checks-55.diff115.24 KBznerol
#55 interdiff.txt1.39 KBznerol
#54 interdiff.txt4.69 KBznerol
#54 2331079-use-route-match-in-access-checks-54.diff113.84 KBznerol
#47 2331079-use-route-match-instead-of-request-for-access-checks-47.patch113.24 KBznerol
#42 interdiff.txt31.74 KBznerol
#42 2331079-use-route-match-instead-of-request-for-access-checks-42.patch110.63 KBznerol
#41 interdiff.txt1.54 KBznerol
#41 2331079-use-route-match-instead-of-request-for-access-checks-41.patch79.95 KBznerol
#39 2331079-use-route-match-instead-of-request-for-access-checks-39.patch79.79 KBznerol
#39 interdiff.txt6.54 KBznerol
#37 2331079-use-route-match-instead-of-request-for-access-checks-37.patch75.61 KBznerol
#37 interdiff.txt594 bytesznerol
#34 interdiff.txt1016 bytesznerol
#34 2331079-use-route-match-instead-of-request-for-access-checks-34.patch76.19 KBznerol
#32 2331079-use-route-match-instead-of-request-for-access-checks-31.patch76.06 KBznerol
#32 interdiff.txt1.86 KBznerol
#29 interdiff.txt12.15 KBznerol
#29 2331079-use-route-match-instead-of-request-for-access-checks-29.patch76.17 KBznerol
#28 2331079-use-route-match-instead-of-request-for-access-checks-28.patch82.52 KBznerol
#28 interdiff.txt2.18 KBznerol
#26 interdiff.txt2.85 KBznerol
#26 2331079-use-route-match-instead-of-request-for-access-checks-25.patch81.54 KBznerol
#23 2331079-use-route-match-instead-of-request-for-access-checks-23.patch2.85 KBznerol
#23 interdiff.txt2.85 KBznerol
#22 interdiff.txt14.16 KBznerol
#22 2331079-use-route-match-instead-of-request-for-access-checks-22.patch81.36 KBznerol
#20 interdiff.txt531 bytesznerol
#20 2331079-use-route-match-instead-of-request-for-access-checks-20.patch67.21 KBznerol
#18 interdiff.txt26.61 KBznerol
#18 2331079-use-route-match-instead-of-request-for-access-checks-18.patch400 bytesznerol
#16 interdiff.txt12.98 KBznerol
#16 2331079-use-route-match-instead-of-request-for-access-checks-16.patch53.77 KBznerol
#13 interdiff.txt1.15 KBznerol
#13 2331079-use-route-match-instead-of-request-for-access-checks-13.patch40.79 KBznerol
#10 interdiff.txt2.25 KBznerol
#10 2331079-use-route-match-instead-of-request-for-access-checks-10.patch39.64 KBznerol
#5 interdiff-1-5.txt3.31 KBznerol
#5 2331079-use-route-match-instead-of-request-for-access-checks-5.patch38.82 KBznerol
#3 interdiff.txt9.33 KBdawehner
#3 2331079-use-route-match-instead-of-request-for-access-checks-3.patch38.16 KBdawehner
#1 2331079-use-route-match-instead-of-request-for-access-checks.patch37.31 KBznerol
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

znerol’s picture

Status: Active » Needs review
FileSize
37.31 KB

Status: Needs review » Needs work
dawehner’s picture

Status: Needs work » Needs review
FileSize
38.16 KB
9.33 KB

This is just an idea how to determine whether to call the ones with request or not.

Status: Needs review » Needs work
znerol’s picture

Status: Needs work » Needs review
FileSize
38.82 KB
3.31 KB

How about making the request optional in the access-checks? Attached interdiff is against #1.

With this patch it is possible to enable/disable themes, therefore it looks like the CSRF access check is working again. Additionally I was able to remove something looking like an ugly hack from AccessAwareRouter::checkAccess().

dawehner’s picture

  1. +++ b/core/lib/Drupal/Core/Access/AccessManager.php
    @@ -186,7 +186,10 @@ public function checkNamedRoute($route_name, array $parameters, AccountInterface
       public function checkRequest(Request $request, AccountInterface $account) {
    +    // @todo: Temporary workaround: Ensure that the request is available as an
    +    // upcasted route argument in the AccessArgumentsResolver.
         $route_match = RouteMatch::createFromRequest($request);
    +    $route_match->getParameters()->set('request', $request);
         return $this->check($route_match, $account, $request);
    

    Mh, in case we do that, I would suggest to add a dedicated method on the route match

  2. +++ b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
    @@ -48,9 +48,9 @@ function __construct(CsrfTokenGenerator $csrf_token) {
    -  public function access(Route $route, Request $request) {
    +  public function access(Route $route, Request $request = NULL) {
         // If this is the controller request, check CSRF access as normal.
    -    if ($request->attributes->get('_controller_request')) {
    +    if ($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;
    

    Mh, I would like to avoid that the access checkers itself have to be aware of that.

  3. +++ b/core/lib/Drupal/Core/Routing/AccessAwareRouter.php
    @@ -102,21 +102,8 @@ public function matchRequest(Request $request) {
       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->checkRequest($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();
         }
    

    The main reason why we have that complex code here is that you maybe have a not-existing route in the request, are you sure this will fly?

Status: Needs review » Needs work
znerol’s picture

Thanks for the feedback @dawehner.

#6.1 is easy to solve properly, its just a quick and dirty method to illustrate what I'm after.

Regarding #6.2, this is in fact the important decision. The patch just makes current behavior more explicit. This does not mean that we should keep it that way though. Given that we already are using optional arguments in many access-checkers, it feels natural to also make the request optional.

Regarding #6.3 git log -S _controller_request turns up two issues: #1798296: Integrate CSRF link token directly into routing system and #2322809: Do not depend on event subscribers for security: Tighten routing security by access checking in matchRequest. Therefore I'm pretty sure that the _controller_request request attribute has been introduce specifically in order to be able to figure out whether the csrf access check is executed against the current request (attribute present) or against a fake-request (attribute absent). The try...catch block is only there such that it is possible to remove the request-attribute in any case after the access-check did run.

dawehner’s picture

Regarding #6.2, this is in fact the important decision. The patch just makes current behavior more explicit. This does not mean that we should keep it that way though. Given that we already are using optional arguments in many access-checkers, it feels natural to also make the request optional.

Not sure whether you had a look at #3 but this skipped those access checkers, in case they require the request for the non-existance of the request.
This is at least the best DX from the perspective of the module developer.

Regarding #6.3 git log -S _controller_request turns up two issues: #1798296: Integrate CSRF link token directly into routing system and #2322809: Tighten routing security by access checking in matchRequest. Therefore I'm pretty sure that the _controller_request request attribute has been introduce specifically in order to be able to figure out whether the csrf access check is executed against the current request (attribute present) or against a fake-request (attribute absent). The try...catch block is only there such that it is possible to remove the request-attribute in any case after the access-check did run.

You are absolute right, so we kinda have solved that implicit now, by distinct request aware and not request aware access checkers.

znerol’s picture

Status: Needs work » Needs review
FileSize
39.64 KB
2.25 KB

@dawehner: The approach from #3 does not work because the AccessArgumentsResolver will throw an exception if the request is NULL. We'd need to filter the checks before trying to resolve the arguments.

Fixed the AccessArgumentsResolver and reinstated the workaround for #2265939: [sechole] AccessManager::checkNamedRoute() leaks attributes from current request, even though that should not be necessary anymore.

tim.plunkett’s picture

+++ b/core/lib/Drupal/Core/Access/AccessArgumentsResolver.php
@@ -49,6 +49,11 @@ protected function getArgument(\ReflectionParameter $parameter, RouteMatchInterf
+    // @todo Remove this once AccessManagerInterface::checkNamedRoute() is fixed
+    //   to not leak _raw_variables from the request being duplicated.
+    // @see https://drupal.org/node/2265939

Well #2265939: [sechole] AccessManager::checkNamedRoute() leaks attributes from current request went in. Do we have an @todo for a new issue? Or do you intend to fix it in this issue?

znerol’s picture

I'm currently trying to figure out the scope of this issue here. If removing the workaround turns out to be complicated, then I'll open a follow-up.

znerol’s picture

Fix path based breadcrumb builder. Shouldn't that thing be replaced with a menu-based breadcrumb builder instead?

Status: Needs review » Needs work
znerol’s picture

Status: Needs work » Needs review
FileSize
53.77 KB
12.98 KB

Fix config translation access checks.

Status: Needs review » Needs work
znerol’s picture

This is a solution for the workaround pointed out in #6.1: Generalize the access-arguments resolver into a component and introduce a factory for parametrizing it. Then pass the parametrized argument resolver down to the checkAll(), checkAny() and performCheck()methods.

I will fix the remaining tests when we agree on the approach. It also would be possible to break out the bigger things like the fixes for config translation and the changes to the access-arguments resolver to separate issues if necessary.

znerol’s picture

Status: Needs work » Needs review
znerol’s picture

Forgot to reinstate a use statement.

Status: Needs review » Needs work
znerol’s picture

Status: Needs work » Needs review
FileSize
81.36 KB
14.16 KB

Fix the fatal test fail by just converting Drupal\Tests\Core\Access\AccessArgumentsResolverTest<\code> to \Drupal\Tests\Component\Utility\ArgumentsResolverTest<\code>.

znerol’s picture

Status: Needs review » Needs work

znerol’s picture

Status: Needs work » Needs review
FileSize
81.54 KB
2.85 KB

Mh, #23 was the interdiff.

Status: Needs review » Needs work
znerol’s picture

Status: Needs work » Needs review
FileSize
2.18 KB
82.52 KB

ContentTranslationOverviewAccess should not attempt to retrieve the entity_type_id from the route match parameters but simply receive it through a method parameter. Like this it is always resolved properly by the argument resolver. This should fix the test failures in content translation web tests.

znerol’s picture

Let's roll back the massive changes in config translation access checkers and only modify what is really necessary.

Status: Needs review » Needs work
znerol’s picture

Status: Needs work » Needs review
FileSize
1.86 KB
76.06 KB

Add route defaults before upcasting arguments in AccessManager::checkNamedRoute(). Hopefully this will get us to zero test-failurs in web tests again.

Status: Needs review » Needs work
znerol’s picture

Status: Needs work » Needs review
FileSize
76.19 KB
1016 bytes

Let's try again. This partially reverts #32. We need to add the defaults in both places, i.e. in AccessManager::checkNamedRoute() as well as in AccessArgumentsResolverFactory::getArgumentsResolver().

znerol’s picture

Issue summary: View changes

Status: Needs review » Needs work
znerol’s picture

Status: Needs work » Needs review
FileSize
594 bytes
75.61 KB

Restore commented out set_error_handler and set_exception_handler... tsts.

Status: Needs review » Needs work
znerol’s picture

Status: Needs work » Needs review
FileSize
6.54 KB
79.79 KB

Now that web tests pass, let's remove another workaround from CsrfAccessCheck::access(): Introduce the needs_incoming_request service tag attribute and only run access-checks marked that way on the incoming request. This allows us to remove the guesswork-code which is currently inflating the csrf access check.

Status: Needs review » Needs work
znerol’s picture

Status: Needs work » Needs review
FileSize
79.95 KB
1.54 KB

Do not instantiate the arguments resolver when there are no access checks but simply deny access.

znerol’s picture

Fix current unit-tests, still need to add test-coverage for the new AccessArgumentsResolverFactory class and the new AccessManager::checkRequest method.

catch’s picture

Priority: Normal » Major
Issue tags: +beta target

Didn't review the whole patch yet, but in general really, really like this.

znerol’s picture

Issue summary: View changes
znerol’s picture

znerol’s picture

dawehner’s picture

I really like how this turned out, got a lot of understanding already from our discussion.

  1. +++ b/core/lib/Drupal/Core/Access/AccessManager.php
    @@ -114,32 +104,29 @@ class AccessManager implements ContainerAwareInterface, AccessManagerInterface {
    +    if ($needs_incoming_request) {
    +      $this->checkNeedsRequest[$service_id] = $service_id;
    +    }
    

    I'd still like some interface to ensure that.

  2. +++ b/core/lib/Drupal/Core/Access/AccessManager.php
    @@ -220,19 +202,40 @@ public function checkNamedRoute($route_name, array $parameters = array(), Accoun
    +  public function checkRequest(Request $request, AccountInterface $account = NULL) {
    ...
    +  public function check(RouteMatchInterface $route_match, AccountInterface $account = NULL, Request $request = NULL) {
    

    Do we need all those three methods to be public? I though having just checkedNamedRoute and checkRequest would be enough

  3. +++ b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
    @@ -49,25 +49,9 @@ function __construct(CsrfTokenGenerator $csrf_token) {
    -    // 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;
    

    <3

  4. +++ b/core/modules/config_translation/src/Access/ConfigTranslationFormAccess.php
    @@ -33,7 +32,7 @@ public function access(Route $route, Request $request, AccountInterface $account
    -        $target_language->id != $this->sourceLanguage->id;
    +        (empty($this->sourceLanguage) || ($target_language->id != $this->sourceLanguage->id));
    

    What is the reason to check for the existence of the source language?

  5. +++ b/core/modules/system/tests/src/Unit/Breadcrumbs/PathBasedBreadcrumbBuilderTest.php
    index 0000000..c786308
    --- /dev/null
    
    --- /dev/null
    +++ b/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php
    
    +++ b/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php
    +++ b/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php
    @@ -0,0 +1,221 @@
    
    @@ -0,0 +1,221 @@
    +<?php
    +
    
    deleted file mode 100644
    index 2a46556..0000000
    
    index 2a46556..0000000
    --- a/core/tests/Drupal/Tests/Core/Access/AccessArgumentsResolverTest.php
    
    --- a/core/tests/Drupal/Tests/Core/Access/AccessArgumentsResolverTest.php
    +++ /dev/null
    
    +++ /dev/null
    +++ /dev/null
    @@ -1,242 +0,0 @@
    
    @@ -1,242 +0,0 @@
    -<?php
    -
    -/**
    

    I wonder whether you have configured git to show moves in the patch files, see https://www.drupal.org/documentation/git/configure

  6. +++ b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
    @@ -690,13 +627,33 @@ protected static function convertAccessCheckInterfaceToString($constant) {
    +   * Add defalut expectations to the access arguments resolver factory.
    +   */
    +  protected function setupAccessArgumentsResolverFactory($constraint = NULL) {
    +    if (!isset($constraint)) {
    

    It is great to have a helper method!

znerol’s picture

I'd still like some interface to ensure that.

I actually tried that but I could not come up with a solution which fits the current architecture.

The most natural point to examine the marker-interface would be in AccessManager::loadCheck(). Regrettably this is too late because at that point in time it is already decided which checks are to be run.

The other place where the marker interface could be checked is from within RegisterAccessChecksPass::process() but that does not buy us much. We would have to keep the $needs_incoming_request parameter of the AccessManager::addCheckService() call in that case.

Do we need all those three methods to be public?

We could make check() a protected method. But that would mean we'd have to rewrite all the test-coverage. I'm okay with doing that, but in order to keep risk to a minimum and reviewability at a sane level, let's defer that to a follow-up.

What is the reason to check for the existence of the source language?

We only want to deny access when a language is locked but we want to allow access when it is missing (yes, really).

dawehner’s picture

We could make check() a protected method. But that would mean we'd have to rewrite all the test-coverage. I'm okay with doing that, but in order to keep risk to a minimum and reviewability at a sane level, let's defer that to a follow-up.

Agreed.

We only want to deny access when a language is locked but we want to allow access when it is missing (yes, really).

He, well, is there a reason this has to be changed here?

znerol’s picture

He, well, is there a reason this has to be changed here?

The patch does not change the behavior. The new empty() check is necessary after the removal of the call to $mapper->populateFromRequest() and $mapper->getLanguageWithFallback() from ConfigTranslationOverviewAccess::access(). ConfigTranslationFormAccess inherits from ConfigTranslationOverviewAccess which is really ugly. Earlier versions of the patch (before #29) contained a complete rewrite of those access-checkers, but in order to keep changes to a digestible volume I decided to roll them back and only include the bare minimum.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Great work!

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: -Needs issue summary update +Needs change record updates
diff --git a/core/lib/Drupal/Core/Access/AccessArgumentsResolverFactoryInterface.php b/core/lib/Drupal/Core/Access/AccessArgumentsResolverFactoryInterface.php
index 1fdbb80..b4c3219 100644
--- a/core/lib/Drupal/Core/Access/AccessArgumentsResolverFactoryInterface.php
+++ b/core/lib/Drupal/Core/Access/AccessArgumentsResolverFactoryInterface.php
@@ -26,7 +26,7 @@
    * @param \Symfony\Component\HttpFoundation\Request $request
    *   Optional, the request object.
    *
-   * @return \Drupal\Component\ArgumentsResolverInterface
+   * @return \Drupal\Component\Utility\ArgumentsResolverInterface
    *   The parametrized arguments resolver instance.
    */
   public function getArgumentsResolver(RouteMatchInterface $route_match, AccountInterface $account, Request $request = NULL);
diff --git a/core/lib/Drupal/Core/Access/AccessManagerInterface.php b/core/lib/Drupal/Core/Access/AccessManagerInterface.php
index 4312951..00ac6fb 100644
--- a/core/lib/Drupal/Core/Access/AccessManagerInterface.php
+++ b/core/lib/Drupal/Core/Access/AccessManagerInterface.php
@@ -8,7 +8,6 @@
 namespace Drupal\Core\Access;
 
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
@@ -55,10 +54,10 @@
   public function checkNamedRoute($route_name, array $parameters = array(), AccountInterface $account = NULL);
 
   /**
-   * Execute access checks against the incomming request.
+   * Execute access checks against the incoming request.
    *
    * @param Request $request
-   *   The incomming request.
+   *   The incoming request.
    * @param \Drupal\Core\Session\AccountInterface $account
    *   (optional) Run access checks for this account. Defaults to the current
    *   user.
@@ -100,7 +99,7 @@ public function addCheckService($service_id, $service_method, array $applies_che
    *   user.
    * @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.
+   *   incoming request, do not specify when checking routes on output.
    *
    * @return bool
    *   Returns TRUE if the user has access to the route, otherwise FALSE.
diff --git a/core/lib/Drupal/Core/Routing/AccessAwareRouter.php b/core/lib/Drupal/Core/Routing/AccessAwareRouter.php
index 307cfc9..3ca0c1e 100644
--- a/core/lib/Drupal/Core/Routing/AccessAwareRouter.php
+++ b/core/lib/Drupal/Core/Routing/AccessAwareRouter.php
@@ -10,7 +10,6 @@
 use Drupal\Core\Access\AccessManagerInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Cmf\Component\Routing\ChainRouter;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\Routing\RequestContext;
diff --git a/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php b/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php
index c786308..660ff2a 100644
--- a/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/ArgumentsResolverTest.php
@@ -84,7 +84,7 @@ public function testGetArgumentObject() {
   /**
    * Tests getArgument() with a wildcard object for a parameter with a custom name.
    */
-  public function testGetWilcardArgument() {
+  public function testGetWildcardArgument() {
     $callable = function(\stdClass $custom_name) {};
 
     $object = new \stdClass();
diff --git a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
index 18f32db..f2f39b1 100644
--- a/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/AccessManagerTest.php
@@ -635,7 +635,7 @@ protected function setupAccessChecker() {
   }
 
   /**
-   * Add defalut expectations to the access arguments resolver factory.
+   * Add default expectations to the access arguments resolver factory.
    */
   protected function setupAccessArgumentsResolverFactory($constraint = NULL) {
     if (!isset($constraint)) {
diff --git a/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php
index 1bebde8..3ec5d21 100644
--- a/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\Tests\Core\Access;
 
-use Drupal\Core\Access\AccessManagerInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
 use Drupal\Core\Access\CsrfAccessCheck;

Above are some changes to make... I was thinking about doing them on commit but I think we need a list of all CRs that need an update eg https://www.drupal.org/node/1851490 so that once committed someone can fix them. I also think we need to ensure (if necessary) documentation on d.o is updated once this gets in.

znerol’s picture

Also found https://www.drupal.org/node/2117181 (Non reusable access checkers can be defined directly in the routing definition). The example there (toolbar) still exists in HEAD and is not touched by this patch. Looks like I failed to convert all access checkers.

znerol’s picture

Status: Needs work » Needs review
FileSize
1.39 KB
115.24 KB

git grep _custom_access: turns up the following custom access checks:

\Drupal\menu_ui\Form\MenuLinkResetForm::linkIsResettable
\Drupal\shortcut\Form\SwitchShortcutSet::checkAccess
\Drupal\toolbar\Controller\ToolbarController::checkSubTreeAccess

The only one which currently requires a request is ToolbarController::checkSubTreeAccess, but that can be easily rewritten.

The reason why this was not caught by the test-suite is that the toolbar.subtrees route is never actively used in the PHP-part, i.e. nothing tries to print an access-checked URL for that route. Instead the path is constructed directly in the JavaScript. This means that ToolbarController::checkSubTreeAccess() is currently only ever run on an incoming request where the request object is always available.

The following handbook page also needs an update: https://www.drupal.org/node/2122195 (Access checking on routes)

dawehner’s picture

Yeah I think it it totally fine to limit custom access checkers for just route match based ones. One big reason is that this callback
is considered as a lazy excuse anyway, so it is okay to not have the full freedom.

znerol’s picture

Oh, right. We actually should remove the request from CustomAccessCheck then.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

I really like the amount of cleanups provided by this patch though, well, this will probably not land before #2287071: Add cacheability metadata to access checks so that you need to rework quite a lot here.

Wim Leers’s picture

Amazing simplification and clean-up!

This will indeed unfortunately conflict with #2287071, but fortunately they touch the same files for very different reasons, so the conflict should be doable to resolve.

catch’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Avoid commit conflicts

I just committed [##2287071] so this will need a rebase. Adding "avoid commit conflicts" since I would have put this in before any other patch that wasn't 250kb..

znerol’s picture

Status: Needs work » Needs review
FileSize
121.89 KB

Let's see whether I got this right.

Wim Leers’s picture

Wow, that was fast!

I created a diff locally between the patches in #57 and #61, and all changes looked sane from a first read-through.

Status: Needs review » Needs work

The last submitted patch, 61: 2331079-use-route-match-in-access-checks-61.diff, failed testing.

znerol’s picture

znerol’s picture

Status: Needs work » Needs review
dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Awesome!

@znerol
Still curious about your git config, see above.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: -Avoid commit conflicts

Nice work on the quick reroll and back to rtbc. Let's update those change records.

Committed d617be8 and pushed to 8.0.x. Thanks!

  • alexpott committed d617be8 on 8.0.x
    Issue #2331079 by znerol, dawehner: Use RouteMatch in access-checks and...
tstoeckler’s picture

The config translation changes here are wrong. Surely config_translation could be refactored to be less dependant on the request object and instead utilize a RouteMatch or whatever is fancy these days. As it stands, though, ConfigMapperInterface::populateFromRequest() must be run in the access checker. Not running it will lead to very hard to catch bugs in multilingual scenarios. In the same light the empty() check is not correct. If there is no source language then access should not be granted.

Since we didn't write any tests for this complicated behavior it's our own fault that this regressed and thus I also do not suggest rolling this back. I will open a follow-up where I will try to write a test to prove my point.

znerol’s picture

ConfigMapperInterface::populateFromRequest() must be run in the access checker. Not running it will lead to very hard to catch bugs in multilingual scenarios.

I disagree. The access check does not depend on the populated mapper.

If there is no source language then access should not be granted.

Dito, translation should be possible even though a source language is missing.

Update: Oh, you are in fact right in the second point - translation should only be granted if the source language exists, except if it is english, then it should be always granted.

tstoeckler’s picture

tstoeckler’s picture

Oh, you are in fact right in the second point - translation should only be granted if the source language exists, except if it is english, then it should be always granted.

Config translation accounts for that fallback, see ConfigMapperInterface::getLanguageWithFallback().

I disagree. The access check does not depend on the populated mapper.

This is not a matter of opinion. It does depend on the populated mapper as described in the follow-up. Sorry to be so blunt, but you are wrong here.

Berdir’s picture

Another thing, this broke Page Manager's way of doing access checks.

Page manager currently uses _entity_create in combination with a hardcoded id that is not coming from the route but specified as a default.

That is in $request->attributes, but does not end up in $routeMatch->getParameters() (nor getRawParameters()).

The result is that the access check is now failing as it can't find the entity anymore.

Any suggestions on how to fix this? Would be sad if we'd have to go with custom access checks, because we also use _entity_view, so if that at some point stops working too, page_manager will be sad.

znerol’s picture

Re #73 summary of IRC discussion: Page manager generates route defaults with an underscore prefix. However, per convention those are not considered as paremeters and therefore are not available from RouteMatch::getParameters(). See also that answer on Drupal SE.

tim.plunkett’s picture

I think we need to fix RouteMatch. I needed to prefix _page to prevent conflicts, see #2284157: Use _page instead of plain 'page' in route defaults.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.