Problem/Motivation
Any page access check that relies on page context
- works well when checked for the current request route
- has problems (throws or has wrong result) when checked for another route, like in Url::access().
This makes access check for links to pages unusable, which limits the usefulness of Page Manager.
The flaw originates from the page context system only working for the current page.
Steps to reproduce
- Create a page that uses page contexts for access check
- Create a link to it and check access
Proposed resolution
- Check page access in a sub-request that terminates after access check
- Repair other stuff that stands in the way
- Add follow-ups to move relevant parts to core
This approach starts in #29.
Remaining tasks
- Review and committ
User interface changes
None.
API changes
Data model changes
None.
Release record snippet
A long standing flaw in page manager access is fixed. A SubRequestAccessChecker service is available.
Original Report
I'm running into this error whenever trying to edit a menu item
Drupal\Component\Plugin\Exception\ContextException: Required contexts without a value: node. in Drupal\Core\Plugin\Context\ContextHandler->applyContextMapping() (line 112 of /var/www/aaep.org/http/web/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php).
When I add new menu items to replace the ones that weren't working using the same links and titles it works, but then another random menu link breaks.
Here is the full error message
The website encountered an unexpected error. Please try again later.
Drupal\Component\Plugin\Exception\ContextException: Required contexts without a value: node. in Drupal\Core\Plugin\Context\ContextHandler->applyContextMapping() (line 112 of core/lib/Drupal/Core/Plugin/Context/ContextHandler.php). Drupal\page_manager\Entity\PageAccess->checkAccess(Object, 'view', Object) (Line: 93) Drupal\Core\Entity\EntityAccessControlHandler->access(Object, 'view', Object, 1) (Line: 340) Drupal\Core\Entity\Entity->access('view', Object, 1) (Line: 61) Drupal\Core\Entity\EntityAccessCheck->access(Object, Object, Object) (Line: 29) Drupal\page_manager\Entity\PageAccessCheck->access(Object, Object, Object) call_user_func_array(Array, Array) (Line: 163) Drupal\Core\Access\AccessManager->performCheck('page_manager.page_access_check', Object) (Line: 139) Drupal\Core\Access\AccessManager->check(Object, Object, NULL, 1) (Line: 92) Drupal\Core\Access\AccessManager->checkNamedRoute('entity.node.canonical', Array, Object, 1) (Line: 67) Drupal\menu_link_content\MenuLinkContentAccessControlHandler->checkAccess(Object, 'update', Object) (Line: 93) Drupal\Core\Entity\EntityAccessControlHandler->access(Object, 'update', Object, 1) (Line: 593) Drupal\Core\Entity\ContentEntityBase->access('update', Object, 1) (Line: 61) Drupal\Core\Entity\EntityAccessCheck->access(Object, Object, Object) call_user_func_array(Array, Array) (Line: 163) Drupal\Core\Access\AccessManager->performCheck('access_check.entity', Object) (Line: 139) Drupal\Core\Access\AccessManager->check(Object, Object, Object, 1) (Line: 112) Drupal\Core\Access\AccessManager->checkRequest(Object, Object, 1) (Line: 102) Drupal\Core\Routing\AccessAwareRouter->checkAccess(Object) (Line: 87) Drupal\Core\Routing\AccessAwareRouter->matchRequest(Object) (Line: 154) Symfony\Component\HttpKernel\EventListener\RouterListener->onKernelRequest(Object, 'kernel.request', Object) (Line: 111) Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('kernel.request', Object) (Line: 125) Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 64) Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57) Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47) Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 99) Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 78) Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47) Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 50) Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23) Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 652) Drupal\Core\DrupalKernel->handle(Object) (Line: 19)According to someone on stack overflow as seen here http://drupal.stackexchange.com/questions/223365/required-contexts-witho...
According to that backtrace, it looks like you have a link to a page_manager page and there you probably have access conditions to limit access to a certain node type or so.
That's a page_manager bug that you need to report there. The exception can be fixed pretty easily, but it is also possible that you then simply don't have access to edit that menu link, as page_manager things that access is denied as the node type isn't matching as there is no node. That might be a bit harder to fix.
Part of that might be because the core permission to see and edit all menu links (link to any page or so) isn't fully working either.
| Comment | File | Size | Author |
|---|
Issue fork page_manager-2837833
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
amy.h commentedComment #3
berdir"Someone" reporting in :p (A link to the question might be helpful, and back)
We need a try/catch in \Drupal\page_manager\Entity\PageAccess::checkAccess, just like in \Drupal\block\BlockAccessControlHandler::checkAccess
But the bigger problem is apparently that the context doesn't work if this isn't executed on the actual page, we need to get this into \Drupal\page_manager\EventSubscriber\RouteParamContext::onPageContext() somehow.
Comment #4
amy.h commentedComment #5
berdirThis is a pretty huge conceptual problem with page variant access and I'm not sure how to deal with it.
The simple fact is that almost anything you can do only works on the actual page you're looking at. anything that requires context that's somehow from the path simply doesn't work and causes all kinds of bugs.
For example, during menu rebuilding, if you have menu links that point to /some/path and there you have variants that have some access checks, based on the path, or in this case the provided node, than those contexts will not work on another page, where you are rebuilding the menu tree. That means the links will be created with base:some/path and not with the route, and then active trail and many other things will not work.
I experimented a bit with pushing the current request object onto the request stack but couldn't fully get it working and of course that's inside symfony code..
Comment #6
bap72190 commentedAny progress on this Berdir? Trying to debug this and was wondering where you were looking at. I am uploading this patch as a mean to start debugging this issue, this seems to have fixed the edit menu pages, but I am not sure if it will cause permissions issues elsewhere, if it will, please let me know and ill continue to debug on it.
Comment #7
wim leersIs this a bug in Page Manager or in the context system that it uses?
Comment #8
m.abdulqader commentedI get the same bug and try/catch solved the problem.
Comment #9
mparker17Also ran into this problem when I tried to create a custom route (pointing to a form) that shares the first part of its path with a Page Manager page.
Noticed that the patch contained tabs (instead of spaces) so I've converted them to spaces and generated a new patch.
Comment #11
mparker17Apparently, at the time of writing, the 8.x-1.x-dev branch itself is failing with the same errors: https://www.drupal.org/node/2272255/qa
So I assume that means the failure is unrelated to this patch.
Going to try to see if I can mark the issue as "needs review" but temporarily cancel the test run.
Comment #12
qwaygon commentedhi all, this issue bugging me out a lot as im huge panels(page_manager) fun and here is what i found
first, to replicate you need to have some page (via page manager) that takes "some" context from URL (most commonly its a node)
second - you need to use that context in Access check (you will see why) for this page
third - you need to invoke path validation for this page on a different URL
in my case it was
page_manager: 8.x-4.0-beta2
current url: /sg_common/test
access check: bundle filter
invoked code:
at this point you should reliebly get
Drupal\Component\Plugin\Exception\ContextException: Required contexts without a value: node. in Drupal\Core\Plugin\Context\ContextHandler->applyContextMapping() (line 112 of core/lib/Drupal/Core/Plugin/Context/ContextHandler.php).firts thing i found interesting is that this code actually runs twice on the page, once its returns valid node context and second one its a WSOD with some debug_backtrace i found that its caused by
first time execution path goes through
second time (this is why you need to use context in access to trigger error)
then i found that when event to collect context is triggered and handled by
request stack is differnt
in first invocation its tip is '/node/2' but in second its '/sg_common/test' and ofc attempting to extract node argument form the second route is a failure, so between those two lines someone operate on request Stack service
looking for suspects i found that
contains
but it pushes one level just before, so in fact THIS what cause proper context creation and not the inproper one.
so the catual problem is that page_manager uses current request from stack, but nobody pushes it there :/ last function i have found info about request that wasnt in core was
there is no Request object there so i build it from $route_match best i can :/
and push it before access check function and pop it after so whole thing looks like in the patch attached.
for me it not only fixes WSOD but also starts returning valid access info and i finaly can filter my menu links, hope this will help page_manager's team find a proper fix to this issue as mine seems a little bit too straightforward. (i also didnt see 8.x-4.x-dev to test against)
Comment #14
m.lebedev commented#12 works for me. Thanks
Comment #15
scottsperry commented#12 worked for me as well
Comment #16
geek-merlinLooks like patch still applies.
Comment #17
liam morlandRe-roll of #12.
This is working for me.
The Drupal 10 failures also exist on the dev branch.
Comment #18
jghyde commented#12 works with D9.3.22. It also solved my page_manager problem related to contexts and controllers matching.
Comment #19
jghyde commentedWhen upgrading from 9.1.2 to 9.3.22 received this error caused by page_manager when attempting
drush updb.Applying this patch at #17 solved the issue.
(I am in Drupal upgrade prison with a large site. Hopefully this will get indexed into Google and help any others in same boat).
Comment #20
hydra commentedWe first used this patch as well and it seemed to fix our problem. But manipulating the request this way will lead to a loss of information on the route in some circumstances which will result in other, unfixable issues.
For us the solution was to use page managers PAGE_CONTEXT Event to add our missing context which looked kinda like this for our missing group context:
Comment #21
geek-merlin@Hydra #20:
> But manipulating the request this way will lead to a loss of information on the route in some circumstances which will result in other, unfixable issues.
This is a bit foggy and not productive in getting this further. Can you elaborate?
Comment #22
geek-merlinPatch #17 applies cleanly on current core 10.2.2 and fixes the issue for me.
Code looks good and reasonable, and i can not see or guess what the #20 objection may mean.
(#17 manipulates a separate request and pushes / pops it onto the stack.)
This fix MAY be affected by the related long-standing core issue though.EDIT: It is not.Setting NW for an automated test, which might look like this:
- Create a page with a context, use the context to control page access
- Create a $url for that page and call $url->access()
Comment #23
hydra commented@geek-merlin - it's a while ago I faced this issue. But as far as I can remember (and my git history tells me) we had a local task (tab) added to the group tasks which pointed to a page_manager page route. That page of course has the group context configured and used a custom condition plugin to determine the access. That condition used the group context to check for some information related to the group in order to grant or deny access. In combination with this patch, this resulted in an error on the local task access check level - kinda related to what berdir described what could happen to menu links in #5
Removing the patch and adding the event subscriber with the provided code in #20 solved that for us and solved the original issue we used the patch for in the first place.
Comment #25
geek-merlin#23: Thanks. Nothing that can be reproduced though.
Comment #26
geek-merlinLooked a bit deeper into this and found that the logic of #17 is not enough in many cases.
Rolled a MR that is way more robust and fixes the issue for me. Still needs tests though.
Comment #27
geek-merlinMade a simplification, then squashed.
Patch url: https://git.drupalcode.org/project/page_manager/-/commit/02e263051d80646...
Comment #28
geek-merlinAh OK, this needs a rebase.
The related issue changed the code here a lot.
Maybe it solved this issue too? Let's see.
Comment #29
geek-merlinFinally i found some time to look into this thoroughly.
@berdir is right that the former fake-request approach is at least limited and possibly FUBAR.
So...
- created a SubRequestAccessChecker and fixed some core limitations.
- added tests, which are green.
- also test-only changes are red (nice we have that now!), which proves that the MR fixes the issue.
Nice.
Comment #30
geek-merlinSquashed commit for local application: https://git.drupalcode.org/project/page_manager/-/commit/d3764a67c50e69e...
Comment #31
andypostLooks great by skimming!
btw why test-only fails? https://git.drupalcode.org/issue/page_manager-2837833/-/jobs/3186611
Comment #32
andypostMeantime different approach started since #2613044-123: Requests are pushed onto the request stack twice, popped once
Also some workaround via #3390193: Add a drupalGet() method to KernelTestBase
Comment #33
geek-merlin@andypost #31
> Looks great by skimming!
Nice you looked into it and like it too! I think the approach has quite some potential.
Can you give a code review (make all corrections you think fit) and help move this forward?
#32: Looks enigmatic to me... wrong issue?
Comment #34
geek-merlinLet's handle #2613044: Requests are pushed onto the request stack twice, popped once then.
Comment #35
geek-merlinSquashed: https://git.drupalcode.org/project/page_manager/-/commit/5a62749ad8bf170...
Comment #36
geek-merlinYep, that works for me on a complex site.
Comment #37
geek-merlinComment #38
geek-merlinAh, request context must be fixed too.
Comment #41
tibezh commentedI can confirm that the issue merge request fixes the issue, but we have fails in PHPUnit. Looks like it is related to smth else.