Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
It was a horrible idea to drop the node access optimization. https://drupal.org/node/2207893#comment-8579829
Proposed resolution
Bring it back.
Remaining tasks
User interface changes
API changes
Comment | File | Size | Author |
---|---|---|---|
#77 | interdiff.txt | 2.27 KB | dawehner |
#77 | menu_node_access-2250315-77.patch | 12.45 KB | dawehner |
#72 | interdiff.txt | 3.13 KB | dawehner |
#72 | menu_node_access-2250315-72.patch | 11.46 KB | dawehner |
#70 | menu_node_access-2250315-70.patch | 11.47 KB | dawehner |
Comments
Comment #1
dawehnerInitial implementation.
Comment #3
dawehnerThere needs to be a module exists check.
Comment #5
dawehnerFixed the failures.
Comment #7
dawehnerThis needs to respect #460408: Cannot administer menu item/link if it points to an unpublished node
Comment #9
dawehnerdoh!
Comment #10
John_B CreditAttribution: John_B commentedThe decision to remove the optmization is referred to here https://drupal.org/node/2207893#comment-8579829
I did a quick test as follows: make admin users other than user 1. Make 5 nodes. Log in as second admin user and copy cookie ID & value. Run an apache bench test as that logged-in user by passing the user's cookie ID and value to ab as follows
ab -n 400 -c 10 -C 'SESS<cookie id>=<cookie value>' http://example8.dev/admin/content
.WITHOUT PATCH
Concurrency Level: 10
Time taken for tests: 227.651 seconds
Complete requests: 400
Failed requests: 0
WITH PATCH
Concurrency Level: 10
Time taken for tests: 184.360 seconds
Complete requests: 400
Failed requests: 0
The patch looks OK to me, and the site works with the patch. I am hesitating to mark as RBTC without someone more experienced looking at it. If no else one does look at it, I may return to it.
Comment #11
dawehnerI am pretty sure that using ab is not always the right tool. Especially in this case I don't know what you try to test on admin/content, but there should not be node menu links on there
Comment #12
John_B CreditAttribution: John_B commentedI wasn't sure best way to test, and am I not even sure performance testing is really needed. I am willing to retest if someone advises on design of test. Otherwise maybe this is best left to someone else to review.
Comment #13
webchickAdding a reference to the place where this was decided (thanks, John_B in #10!) because the issue summary is... lacking in details. ;P
I don't quite follow the logic of the referenced comment myself. Almost every site is going to have custom nodes for page like About Us, ToS, etc. and almost every site is going to put those custom nodes (and others) into Primary/Secondary Links menus, meaning most sites will incur the performance penalty. Book module is a pretty minor use case of this in the grand scheme of things, so not sure why the optimization is reserved for that module only, which I can only ever recall seen used on Drupal.org (which doesn't even use node access) on all the many dozens of Drupal sites I've ever had backend access to.
Comment #14
catch#1805054: Cache localized, access filtered, URL resolved, and rendered menu trees and/or #1869476: Convert global menus (primary links, secondary links) into blocks would mean that the access checks and in fact the rendered HTML for those menus will be cached. This also has the advantage that it will cover access checks for things like Views. I haven't profiled views router access in 8.x but it's the worst offender in 7.x for menu access checks.
For primary/secondary menus, you have a finite menu size linking to maybe 5-10 nodes at most - at least if it's to FAQ/About pages.
With block/render caching implemented, the node_access() checks would happen only on a cache miss. Then this issue would mean that instead of loading 5-10 nodes on a cache miss, we do a node access query on them, which is probably still an optimization but not nearly the same as it is in Drupal 7 where there is no render caching of menus so those checks have to happen every request.
Book module needs this more because it has menus with dozens if not hundreds of nodes in them. That's 1. Why I agreed to remove this in the first place 2. why I don't think this issue is critical (given the other two are).
Comment #15
dawehnerAt least for things like views, we just use the routing access level and never actually load the view, so for example most of the time we just set a _permission flag onto the route and are done.
Well, at least webflo tried to use 10k entries in menues, which worked pretty fine in D7 but does not work at all in D8.
Comment #16
catchThat's a big improvement for Views access.
How is the 10k entries in menus different from something like https://drupal.org/project/taxonomy_menu which wouldn't be affected either way by this?
Comment #17
johnvIn D7, the problem is that every object in the menu is loaded to check the access, resulting in increased memory usages and lower page loading times.
This has no impact if you are only showing nodes, since there is a built-in exception for nodes, but it has problems with Views displays or large TaxonomyMenu trees.
See below issue for D7 use cases (in the summary) and a patch-in-progress:
#1978176: Build menu_tree without loading so many objects
Just warning you to not trap into this pitfall again for D8.
Comment #18
dawehner@catch
I guess we want to allow modules to pre-filter the entries much like the special case of node does at the moment. Not sure whether an event is the best approach?
Comment #19
xjm9: menu_tree_access-2250315-9.patch queued for re-testing.
Comment #21
catch@dawehner. Pre-filtering seems reasonable yes.
Comment #22
pwolanin CreditAttribution: pwolanin commentedCan we postpone this issue until: #2256521: [META] New plan, Phase 2: Implement menu links as plugins, including static admin links and views, and custom links with menu_link_content entity, all managed via menu_ui module
Comment #23
dawehner@pwolanin
Given that noone will review this patch anyway we could also just postpone it, but it IS important that this issue is marked as critial,
at least for now
Comment #24
dawehnerThis is an actual regression compared to Drupal 7.
Comment #26
dawehnerFixed it
Comment #27
pwolanin CreditAttribution: pwolanin commentedgetUrlObject() may load the entity to get the description, so we should both optimize that code a bit to be like:
and add back dedicated methods to get the route name and parameters - this will also be important for our fix for the Account menu link
Comment #28
dawehnerLet's ensure that the entity is not loaded
Comment #29
Wim LeersThis looks great!
Below this, I'd add a line saying that this should run before the
checkAccess()
manipulator, because this takes over part ofcheckAccess()
's workload and does it much more efficiently.This tests a tree with only node links. I think it should only be a subset (so we can verify non-node links are unmodified), and with at least one node link nested below a non-node link.
Why only set
->access = TRUE
on nodes that are accessible to the current user?Why not also set
->access = FALSE
on nodes that are inaccessible to the current user?checkAccess()
test coverage already specifically asserts that if a link already has->access
set, that no access checking performed anymore by it for that link. So test-coverage wise, I think we're complete, with the above modification.Question: doesn't
checkNodeAccess()
blindly assume that any published link is accessible to any user? What ifhook_entity_access()
hooks impose further restrictions? And what if thenode.view
route is altered to have more requirements besides_entity_access: 'node.view'
?Why doesn't this optimization break those scenarios?
If this lands before #2301317: MenuLinkNG part4: Conversion, we'll also have to use this manipulator wherever else
menu.default_tree_manipulators:checkAccess
is used.Comment #30
dawehnerGood idea!
Just to be clear, I just readded the functionality which existed in core since Drupal 6 probably in this way.
As far as I understand the node access system it is based upon grants, so you can just allow someone to look at a noe, not deny it via query based access check.
If you alter the routes, good look with dealing with access checking on REST. For entities you should use the internal entity based methods. On the other hand as written in node.api.php hook_node_access() is not garantued to fire on listings of nodes like it is already the case for views.
I would consider a menu list also partly as a list of nodes.
Comment #32
pwolanin CreditAttribution: pwolanin commentedSo, one reason why we took this out of 8.x is that users were denied seeing their own unpubished nodes in the tree - we need to decide what behavior is correct.
In 8.x there are tests that they can see them. In 7.x, they are not expected to be able to see them (and also in book module in 8.x they can't see them in the book block).
Comment #33
pwolanin CreditAttribution: pwolanin commentedre-roll for HEAD changes
Comment #34
pwolanin CreditAttribution: pwolanin commentedAdd missing dependency in the service definition - may fix some test fails.
Re: earlier comments, I think we should mark these FALSE if they are not accessible? I'm worried we are going to be doubling the work for every inaccessible node.
Comment #36
Wim LeersAFAICT that's not entirely correct. The node access system consists of two access systems layered on top of each other. The first is the entity/node access system that allows
hook_entity_access()
andhook_node_access()
hooks to returns TRUE/FALSE/NULL. If they all returned NULL (i.e. none of them had an opinion), and then it falls back to the node grant system.(See
NodeAccessController::access()
, this may callEntityAccessController::access()
, which may callNodeAccessController::checkAccess()
, which in turn may call the node grants system.)Comment #37
dawehnerWell, for listings the situation is different. I hope we don't actually have to start supporting that.
Comment #38
Wim Leers#37: Can you clarify? I don't understand why you refer to listings?
Comment #39
dawehnerWell, listings (entity list, views (i also consider menu links as listing)) don't call entity_access() given
that otherwise things like pagers won't work. As a result of this it should be safe to use the grant system here.
Comment #40
Wim LeersOh wow, I did not know that!
I can imagine why they don't consider entity access when determining which entities should live on which page. But upon *rendering*, the entities are surely access checked, aren't they? It'd otherwise be easy to make a Drupal site where sensitive information was being leaked.
Do we have any documentation surrounding this? It feels like we're missing the necessary facts to have a proper discussion about this.
Comment #41
pwolanin CreditAttribution: pwolanin commented@Wim Leers - if I have a node listing page and I make a View of it, I don't expect that entity access checks run again after the SQL-based check before showing the title.
So, for book module we use a pure node access query. In D6 and D7 we did the same for menu links to nodes. If we don't think the node grant system is sufficient for this use case, then we we should just close this issue. The biggest reason we went away from it was to handle unpublished nodes. I can imagine special casing those and re-checking access as a compromise.
Comment #42
dawehnerI can't give you an actual pointer but fact is that in D7 this was the case. The grant system was used for queries. https://www.drupal.org/node/955088#comment-4880430 describes a couple
of problems you have when you cannot use the grant system.
Comment #43
dawehnerLet's test my patch, it should be green.
Comment #44
pwolanin CreditAttribution: pwolanin commentedSo, I don't think this is sufficient, but we can debate the point. Do we want people to see their own unpublished nodes? I think that's important for avoiding "lost content"
Comment #45
Wim LeersFrom
node.api.php
:That section can be found in
node.module
, and explains it in more detail. The most important part there is this:In other words: I was able to confirm what dawehner said in #39.
But… doesn't this mean we're (still) treating all other entity types as second-class citizens? Doesn't this mean we simply cannot render access-restricted listings of Commerce Product entities, for example? Isn't this highly problematic? Shouldn't this be the same system for all entities?
Comment #46
dawehnerWell, noone stepped up and generalized the node grants system to entities. In Drupal 7 used also just a pure query altering based approach, it seemed to work for them.
There are also reasons why commerce product entities used node for it's display.
Comment #47
BerdirEvery entity type can use query altering, Entity Query automatically adds a tag. Commerce thingies can and do implement that.
The only thing that is node-specific is the grants system/node_access table, which is used instead of a random mix of query alters from different modules.
There is an issue to make that generic, but it did not happen and every entity type can decide to implement something like that themself if they want to.
Comment #48
dawehnerYeah I do agree, we have that behavior in multiple places already (not on the normal frontpage view, though).
Comment #49
jibranI think it is like this
$route_parameters = $element->link->getRouteParameters();
$nid = $route_parameters['node'];
it should be
'';
notarray();
Comment #50
dawehnerWell, we require PHP 5.4, so we can use its capabilities, don't we?
Good spot!
Comment #51
jibranThanks for the changes. I'll let @Berdir and @Wim Leers RTBC this.
And it'll save us tons of memory :P
Comment #52
dawehnerWell, potentially yes, but I would never assume anything about how things are done internally in PHP, see http://fabien.potencier.org/article/48/the-php-ternary-operator-fast-or-not as example.
Comment #53
pwolanin CreditAttribution: pwolanin commentedSo, I think the correct logic is a little more complicated here?
The code in NodeAccessController::checkAccess() seems to imply that the access check will depend on the current language in the case of unpublished nodes - that the author of a translation may be able to see that but not the original node if it's unpublished.
Comment #54
dawehnerSo we somehow need a way to get the current language of content entities, I guess
$this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
is the way to go?Comment #55
pwolanin CreditAttribution: pwolanin commentedFor a first pass we need to exclude uid 0
I'm curious how that language check works if the base table just has one langcode
Comment #56
dawehnerThis is the wonderful world of language fallbacks. I am quite confused that entity query does not deal with that problem yet.
Comment #57
catchDo you know which issue allowed seeing own unpublished nodes in menus? I personally am not convinced that's worth the complexity here.
Comment #58
dawehnerThere was none ... given that the optimization is basically a 1to1 port of what was possible before. Note: This own unpublishing nodes feature will destroy cacheablity potentially.
Comment #59
pwolanin CreditAttribution: pwolanin commented@dawehner - not true. In Drual 7 you are *not* allowed to see them. In Drupal 8, you *are* allowed and there is a test for it.
The change happened someplace more recent than this commit:
commit 477c06c413fbb180165ffa5cdddb4b1d196cd56f
Author: Alex Pott
Date: Sun May 26 13:18:10 2013 -0700
Maybe I'l have to run a bisect to find it
Comment #60
dawehnerRight, but its not the case and never was for menus.
Comment #61
dawehnerI didn't took the time to figure out the exact commit, but this "view own unpublished nodes" was in before 2010 (see commit c10220f9)
Given that I think it is fine to assume that the optimization can be done without taken into account the unpublished status (unless for admins) for the optimization.
Note: As we don't set the access for the unbublished nodes, those are handled by entity_access() directly.
Comment #62
pwolanin CreditAttribution: pwolanin commented@dawehner - so I guess we take the hit of an additional check for any unpublished nodes in the menu? Seems like something that could be an unexpected performance drag on a site (e.g. if it has a menu with dozens of unpublished nodes in it)
Comment #63
dawehnerThis was never a problem in D7. I doubt that people have huge menus with a lot of unpublished nodes in there, though.
Feel free to work on this on a follow up, sadly we don't have a solution yet for proper language fallback as far as I know, so
the current patch is an order of magnitude better than HEAD.
Comment #64
pwolanin CreditAttribution: pwolanin commented@dawehner - you are misinterpreting the Drupal 7 code - it set all node links to access FALSE by default:
Comment #65
dawehnerI think we don't have to support the node published case in all details if the 'node_access' subsystem does not do it out of the box.
Comment #67
dawehnerReroll and fixing of the problem in the test itself. The other test seems to be just a random failure,
Comment #69
pwolanin CreditAttribution: pwolanin commentedminor typo:
readd -> re-add
Let's reverse this condition so the if handles the positive case (it's easier to understand):
Comment #70
dawehnerThank you for your feedback.
Sure. Here is a reroll.
Comment #71
Wim LeersThree nitpicks and one minor thing, after that I'll RTBC.
s/an/a/
s/effective/effectively/
The
parent
information does not match the tree, which makes this very confusing to understand.Please change either so they're consistent.
s/gets/are/
Comment #72
dawehnerThis should be it.
Comment #73
Wim LeersIndeed it is.
Comment #74
dawehnerCool, thank you!
Comment #75
webchickSince catch committed the original patch that removed this optimization, and since it's about, well, optimization. :) Assigning to catch.
Comment #76
catchOne nitpick, one question:
The comment doesn't quite match the code.
Admins get to both see unpublished nodes and ignore other access checks.
Non-admins get both access checks and the published condition.
With the comment just about the ->accessCheck(FALSE) it looked for a second like this was allowing the viewing of unpublished nodes, but it has no effect on that.
Would also be good to add an explicit comment that we're ignoring the 'view own unpublished nodes' permission here to avoid a per-user caching requirement.
How difficult would it be to add to the test to confirm that the usual access check on the links isn't also performed here?
Comment #77
dawehnerI hope the new documentation covers each aspect.
Sure let's improve the test coverage here.
Comment #78
Wim LeersComment #79
catchThat's great. Committed/pushed to 8.0.x, thanks!
Comment #81
Wim Leers