Problem/Motivation

Calling $this->getContextValue($context_name) from within a block's ::blockAccess() or ::access() causes a fatal exception, because the contexts haven't been mapped to the block plugin before that function is called.

In BlockAccessControlHandler, it correctly maps context for all of the visibility conditions, so the conditions are able to use context, but the block plugin itself can't!

Proposed resolution

Map contexts for the block plugin in BlockAccessControlHandler before calling ->access()!

Remaining tasks

  • Write patch
  • Get review
  • Commit!

User interface changes

None.

API changes

None.

Data model changes

None.

Comments

dsnopek created an issue. See original summary.

dsnopek’s picture

Issue summary: View changes
StatusFileSize
new1.1 KB

Here's a patch! This code is by @EclipseGC - I just added the try/catch.

dsnopek’s picture

Status: Active » Needs work
Issue tags: +Needs tests
tim.plunkett’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new2.82 KB
new1.72 KB

If things worked as expected, we should also be able to remove the getRuntimeContexts/applyContextMapping calls from BlockViewBuilder.
But \Drupal\block\BlockViewBuilder::lazyBuilder() purposefully does a fresh entity_load() :(

phenaproxima’s picture

phenaproxima’s picture

This patch makes sense to me. Two things jump out, though:

  1. +++ b/core/modules/block/src/BlockAccessControlHandler.php
    @@ -127,7 +127,16 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
    +          if ($entity->getPlugin() instanceof ContextAwarePluginInterface) {
    +            $contexts = $this->contextRepository->getRuntimeContexts(array_values($entity->getPlugin()->getContextMapping()));
    +            $this->contextHandler->applyContextMapping($entity->getPlugin(), $contexts);
    +          }
    +          $access = $entity->getPlugin()->access($account, TRUE);
    

    Nit: $entity->getPlugin() is called several times. Maybe call it once and then re-use the return value? That might make it a bit easier to read.

  2. +++ b/core/modules/block/src/BlockAccessControlHandler.php
    @@ -127,7 +127,16 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
    +        catch (ContextException $e) {
    +          $access = AccessResult::forbidden();
    +        }
    

    If it's reasonably easy to do, can the failure be logged? Either that, or don't catch the exception at all -- to me, this looks as if we may be swallowing potentially useful debugging info.

eclipsegc’s picture

If we don't catch it, the page won't render. And we don't want to log it because missing contexts on conditions is treated a access/visibility = false in core.

Eclipse

dsnopek’s picture

re #6.2 and #7 (reworded slightly): catching that exception and making access forbidden is exactly what we're doing when context fails to map for the conditions (only a few lines above the code added by this patch). So, this is just doing the exact same thing for the block plugins own access checks as we're already doing for the visibility conditions.

phenaproxima’s picture

Okay, I didn't know that. Makes sense. Thanks @EclipseGc and @dsnopek for clearing that up. Maybe we can add a comment to that effect, just so it's not a WTF for others?

dsnopek’s picture

StatusFileSize
new2.99 KB
new1.5 KB

Here's an update that addresses phenaproxima's review in #6.1 and #9. It adds a simple comment that just refers to the comment right above the code we add which does this for conditions.

But now I have a question/concern... When we're missing contexts for the conditions above, we also do ->setMaxAge(0):

        // If any context is missing then we might be missing cacheable
        // metadata, and don't know based on what conditions the block is
        // accessible or not. For example, blocks that have a node type
        // condition will have a missing context on any non-node route like the
        // frontpage.
        // @todo Avoid setting max-age 0 for some or all cases, for example by
        //   treating available contexts without value differently in
        //   https://www.drupal.org/node/2521956.
        $access = AccessResult::forbidden()->setCacheMaxAge(0);

Based on that comment (assuming I'm understanding it correctly) I think we need to do that in our patch too, right?

dsnopek’s picture

StatusFileSize
new3.21 KB
new923 bytes

Discussed briefly with @berdir on IRC - he confirmed that we'll need to do the ->setCacheMaxAge(0) bit too. So, I added that and copied the @todo from above as well (so we can removed that when #2521956: Missing contexts prevent caching of block access is fixed).

berdir’s picture

Status: Needs review » Reviewed & tested by the community

Looks good to me. We've discussed the exception catching at length in #2620126: No way to handle 'optional' contexts without try/catch, this is the way it works now, as bad as it (IMHO) is.

Quickly discussed in IRC that we now do the assignment twice. But we can't change that, since we re-load those blocks again due to the lazy builder approach, sadly.

eclipsegc’s picture

Huge ++ from me on this.

Eclipse

berdir’s picture

Priority: Normal » Major

#2620126: No way to handle 'optional' contexts without try/catch was major and was closed as a duplicate, so setting this to major.

wim leers’s picture

Patch looks good. Particularly the max-age zero bit.

alexpott’s picture

Assigned: dsnopek » catch

I know @catch has has some reservations about this patch - I'm going to assign it to @catch to make sure that they're accounted for.

catch’s picture

Status: Reviewed & tested by the community » Fixed

I still really dislike having to do the try/catch around the exception like this, it's not an exceptional (excuse pun) situation like Symfony HTTP exceptions (which we had an equivalent of with special DRUPAL_NOT_FOUND and drupal_not_found() return values and request short-circuiting), or form handling which similarly needs to short-circuit the request logic - this is just a method that gets some stuff and returns some stuff back or not.

The patch here is OK as a minimal fix in 8.0.x though, so committed/pushed to that and 8.1.x - and re-opening #2620126: No way to handle 'optional' contexts without try/catch.

Committed/pushed to 8.1.x and cherry-picked to 8.0.x. Thanks!

  • catch committed be80816 on 8.1.x
    Issue #2635238 by dsnopek, tim.plunkett: Contexts not mapped in time to...

  • catch committed 614105d on
    Issue #2635238 by dsnopek, tim.plunkett: Contexts not mapped in time to...
dsnopek’s picture

Thanks, everyone, for your help on this! And thanks, @catch, for committing despite the less than ideal situation with exceptions!

Status: Fixed » Closed (fixed)

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