Views entity operations lack cacheability support, resulting in incorrect dropbuttons

Created on 18 April 2015, about 9 years ago
Updated 31 January 2024, 5 months ago

Problem/Motivation

In order to be able to cache the rendered output of views result rows, we need support for it when we render entity operation links, see the https://qa.drupal.org/pifr/test/1022293 test failure.

Steps to reproduce

  • I added a new content type.
  • Role X has "edit own" permissions for this content type, but no other node permissions except view published content.
  • User A and User B both have Role X.
  • Admin user creates two nodes of the new content type, and makes User A the author of one node, and User B the author of the other node.
  • View is created to display all nodes of the new content type; the view includes the Operations Links field.
  • When Admin is logged in, the operations links (Edit, Delete) are available in the view for both nodes (which is the expected behavior).
  • User A logs in, and the Edit link is available in the view only for the node that has User A as author (which is the expected behavior).
  • User B logs in, and the Edit link is available in the view only for the node that has User A as author, when it should be for User B as author.

Taken from #2653690: Operations links field in Views fails between users with "edit own" user permissions β†’

Proposed resolution

Add an access key to each entity operation which contains an AccessResult object to determine whether the user has access to the operation. Use this to apply access to the rendered link, and bubble cacheability from the AccessResult object to the link.

Remaining tasks

Patch review

πŸ› Bug report
Status

Needs work

Version

11.0 πŸ”₯

Component
EntityΒ  β†’

Last updated 1 minute ago

Created by

πŸ‡©πŸ‡ͺGermany dawehner

Live updates comments and jobs are added and updated live.
  • VDC

    Related to the Views in Drupal Core initiative.

  • Contributed project blocker

    It denotes an issue that prevents porting of a contributed project to the stable version of Drupal due to missing APIs, regressions, and so on.

  • Triaged core major

    There is consensus among core maintainers that this is a major issue. Only core committers should add this tag.

Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • @acbramley opened merge request.
  • Status changed to Needs review over 1 year ago
  • πŸ‡¦πŸ‡ΊAustralia acbramley

    Moving to an MR, rebased against 10.1.x. First pushed a failing functional test and will follow with the rest of the patch. Also updated the IS.

  • Status changed to Needs work over 1 year ago
  • The Needs Review Queue Bot β†’ tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

    Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

    Consult the Drupal Contributor Guide β†’ to find step-by-step guides for working with issues.

  • Status changed to Needs review over 1 year ago
  • πŸ‡¦πŸ‡ΊAustralia acbramley

    Missed changing the version.

  • Status changed to Needs work over 1 year ago
  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    Hiding files as fix has been moved to MR 3556
    Moving to NW as there appears to be 1 failure in it though.

    Did not review yet

  • πŸ‡ΈπŸ‡ͺSweden TwoD Sweden

    We already have cache metadata for the operations indirectly through their URLs, but most list builders [and the Views Operations field] simply throw it away after evaluating the access condition (at operations build time), so the context of when an operation could be valid or not is lost.

    I think this is what the failing test is actually hinting about, a responsibility creep. To fix the failing test I would have assumed the access control handler for the "disable" operation was not returning the proper [user] context. However, adding it there has no effect at all on whether the test passes or fails since that data is thrown away by the list builder when it check if access is currently allowed.
    (We try to avoid early rendering, maybe we should also try to avoid early access evaluation in similar situations?)

    I ran into this when debugging specifically why our cache contexts added to custom entity access control handlers seemed to have odd effects despite the value of the context(s) we included was different for different users, even after applying the latest patch - because contrib's entity module's list builder has the same flaw as core's list builders currently do.

    If the list builders (and Views' Operations field) always added the operations instead of evaluating access at the point of building the operation links, and the links preprocess template hook performs the actual access evaluation (storing the result in $link['#access'] and merging in the cache metadatada into $link['#cache']), we'd be all good.

    This would keep access control handlers as the ones responsible for adding the correct access related cache metadata to their responses instead of also having list builders juggle it around, and relieve them of even having to do the operations access checks they do now - they would not have to care about it all about which operations are valid or not.

    For now I've had to resort to always include a custom cache context (which varies with anything that could affect access for the different groups of users we have) in the links preprocess hook, otherwise the operations access in the lists/views we have would not get rebuilt at all. If it worked as described above (and entity module always included all of its operations) I would not have had to do anything in our site specific code because the cache metadata from the access handler would have been propagated there instead.

    I'll take a stab at refactoring the branch to show what I mean.

  • First commit to issue fork.
  • last update about 1 year ago
    29,434 pass, 1 fail
  • last update 12 months ago
    Custom Commands Failed
  • last update 12 months ago
    29,435 pass, 8 fail
  • last update 12 months ago
    29,440 pass
  • Status changed to Needs review 12 months ago
  • πŸ‡ΈπŸ‡ͺSweden TwoD Sweden

    Now that all tests finally pass, I should write some notes on the changes outside the implementation of #108 itself.

    There were at least two major issues discovered while working on this which could potentially be extracted and handled on their own.
    I tried searching the issue queue for existing patches and came up blank, but maybe someone knows if they have already been covered elsewhere.

    The renderer throws away cache metadata from access results if they are not allowed. This, obviously, prevents something like #108 from working; we need that metadata to be bubbled the same was as if there was a cache hit/miss when access is granted. Without this the new ::testEntityOperationsCacheability() fails because the user cache context is dropped.

    +++ b/core/lib/Drupal/Core/Render/Renderer.php
    @@ -233,6 +233,14 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
           if ($elements['#access'] instanceof AccessResultInterface) {
             $this->addCacheableDependency($elements, $elements['#access']);
             if (!$elements['#access']->isAllowed()) {
    +          // Abort, but bubble new cache metadata from the access result.
    +          $context = $this->getCurrentRenderContext();
    +          if (!isset($context)) {
    +            throw new \LogicException("Render context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain()/renderRoot() or #lazy_builder/#pre_render instead.");
    +          }
    +          $context->push(new BubbleableMetadata());
    +          $context->update($elements);
    +          $context->bubble();
               return '';
             }
           }
    

    (Added null to the annotated return types of ::getCurrentRenderContext() as that was missing.)

    The content translation manager sometimes looks for existing translations on the wrong revision.
    This was exposed by ContentTranslationRevisionTranslationDeletionTest::testOverview() failing because the 'Add' operation was now missing for Italian on line 142. They still had access to the translate form if the current interface language was either Italian or French, but not in English. While the Add operation [correctly?] points to the Italian version of the translation form it being added depended on an access check performed on the "active" revision - which already has an Italian translation - so the link no longer show up because the URL is inaccessible.

    The test performs these steps:
    (This is iteration 2 of the test, with the editor role. Iteration 1 passes because access checks are bypassed.)

    1. Create an English published node "Test 2.1 EN"
      Verify translations can be added.
    2. Add an Italian draft "Test 2.2 IT".
      Verify the draft can not be deleted because it's unpublished.
    3. Publish the Italian translation "Test 2.3 IT"
      Verify it can be deleted.
    4. Create an English draft "Test 2.4 EN".
      Verify Italian translation still exists.
    5. Delete the Italian translation. Verify the 'Add' operation for Italian reappears.
    6. Publish the English draft "Test 2.6 EN".
      Verify the Italian translation does not reappear.
    7. Add an Italian published translation "Test 2.t IT".
      Verify it can be deleted.
    8. Create an Italian draft "Test 2.8 IT".
      Verify it can not be deleted.
    9. Delete the Italian draft.
      Verify it can be deleted again.
    10. Delete the Italian translation.
      Verify it can be added again.

    When the Italian translation is deleted in step 5 the English draft "Test 2.4 EN" is the latest revision and no longer has a translation. The access manager gets its argument from the route match for the translation route, which has load_latest_revision = TRUE, but it loads the latest "active" revision - which the entity repository says is the last one which was translation affected.
    The last English revision which was translation affected (titled "Test 2.1 EN") still had "Test 2.3 IT" as a translation, as the deletion created a new revision without any translations affected.
    ContentTranslationController::add() deals with this by explicitly loading the latest revision, so we can do the same thing in the translation access control manager and it correctly sees that there is no Italian translation there and allows the 'Add' operation.

    The controller does however cause a similar issue elsewhere by adding the translation to the entity it loads (after the access control manager has allowed the route). If you have the language switcher block enabled the links there are now access checked via the same manager, and it now sees the newly added translation (which isn't considered "new" since an Italian translation did exist earlier) and prevents the links from showing.
    Adding a clone to the controller prevents polluting the entity instance in the context, and the subsequent access checks then see the true "current" state of the entity and lets the language switcher links show.

    Other changes made to make tests pass include adding _access: 'TRUE' to the <current> route, which caused a problem in CommentNewIndicatorTest<code> because <code>CommentLinkBuilder uses <current> for the "comment-new-comments" hidden jump link and renders it as a link list.
    I can't think of any reason why this would not be safe off the top of my head, but there's also no other test which directly depends on it it being either allowed or denied.

    A few places in FunctionsTest had similar issues with the access checks but some routes could be switched to existing ones with correct access requirements or replaced with a new one.

  • Status changed to Needs work 12 months ago
  • The Needs Review Queue Bot β†’ tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

    This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

    Consult the Drupal Contributor Guide β†’ to find step-by-step guides for working with issues.

  • πŸ‡¬πŸ‡§United Kingdom catch

    Splitting those out sounds good and nice one tracking them down!

  • πŸ‡ΈπŸ‡ͺSweden TwoD Sweden

    Thanks, working on that now.

    Do you know what's up with the bot? This is my first time I see it so trying to figure out what it's trying to tell me.
    The patch applies fine against 11.1.x which it wants to merge into, but I guess it's testing against 11.x?

  • last update 11 months ago
    29,882 pass
  • Status changed to Needs review 11 months ago
  • πŸ‡¦πŸ‡ΊAustralia acbramley

    I've rebased the MR onto 11.x, hopefully it went well 🀞

  • Status changed to RTBC 11 months ago
  • πŸ‡¨πŸ‡¦Canada jigarius MontrΓ©al

    Just tested it to be working fine with Drupal 10.0.9.

  • Open on Drupal.org β†’
    Environment: PHP 8.2 & MySQL 8
    last update 11 months ago
    Not currently mergeable.
  • Status changed to Needs work 11 months ago
  • πŸ‡¨πŸ‡¦Canada jigarius MontrΓ©al

    After leaving comment #117, I ran into a new issue. There seems to be a permission anomaly for certain operations. I am marking this as needs work.

    For example: With the changes proposed in the merge request at this point, I stop seeing the "Masquerade" operation for user entities even though I'm logged in as User 1. As soon as a I remove the patch, I start seeing it again. Additionally, I implemented a hook_entity_operation() in a custom module and the patch makes that custom operation disappear as well. Removing the patch brings it back.

  • πŸ‡ΈπŸ‡ͺSweden TwoD Sweden

    Yes, this will need some more rebase work once the issues mentioned in #115 are done.

    The changes to the Renderer have been committed to the 11.x branch, but not the 10.x or 10.1.x branches, so if you are using D10 you are likely missing the corresponding change from that issue - which is likely to cause problems like those you describe as cache metadata is lost in some situations.

  • last update 11 months ago
    29,947 pass
  • last update 9 months ago
    30,379 pass, 2 fail
  • Pipeline finished with Failed
    9 months ago
    #26969
  • Pipeline finished with Failed
    6 months ago
    #70289
  • Pipeline finished with Failed
    6 months ago
    #70318
  • Pipeline finished with Canceled
    6 months ago
    #70323
  • Pipeline finished with Failed
    6 months ago
    #70324
  • Pipeline finished with Failed
    6 months ago
    #70330
  • Pipeline finished with Success
    6 months ago
    #70336
  • Status changed to Needs review 6 months ago
  • πŸ‡¦πŸ‡ΊAustralia acbramley

    It'd be good to get some steps to reproduce #118 or the sample code for the custom operation. We are running this patch with many custom operations and all work as intended.

  • πŸ‡¦πŸ‡ΊAustralia acbramley

    For example: With the changes proposed in the merge request at this point, I stop seeing the "Masquerade" operation for user entities even though I'm logged in as User 1. As soon as a I remove the patch, I start seeing it again.

    I just debugged this and it's in fact correct. The masquerade operation's URL route is entity.user.masquerade

    That route has the _csrf_token requirement. That gets removed by the array_diff in AccessManager::check because there is no request, which the csrf_token requirement needs.

  • πŸ‡ΊπŸ‡ΈUnited States thefancywizard

    I created a 10.1.x-compatible patch from the merge request changes if anyone is in need.

  • πŸ‡¦πŸ‡ΊAustralia acbramley

    Hiding patches to avoid confusion.

  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    Appears to have gone through a few rounds of reviews

    All threads appear to have been addressed in the MR. Took a look at the MR and nothing stands out. But seems like a large change so will wait to see if anyone else chimes in.

  • Status changed to RTBC 6 months ago
  • πŸ‡¦πŸ‡ΊAustralia thursday_bw

    #117 set this to reviewed and tested by the community.
    #118 re-set this to needs work with a vague bug report.
    #121 spoke to the bug report and reported it as correct (meaning that 'works as expected' I believe).

    Therefore setting this back to 'Reviewed and tested by the community'.

    I have done some testing and can confirm that MR3556 as of 14 Jan 2024 on resolves the entiry operations problems reported on this issue, and also the username issues reported on Incorrect user linked on content page when switching users β†’ .

  • πŸ‡¨πŸ‡­Switzerland bircher πŸ‡¨πŸ‡Ώ

    Attaching also a patch for Drupal 10.2.x

  • πŸ‡­πŸ‡ΊHungary mxr576 Hungary

    Checked the latest state of MR, also RTBC++.

  • Pipeline finished with Failed
    5 months ago
    #82125
  • Pipeline finished with Failed
    5 months ago
    #82147
  • Pipeline finished with Running
    5 months ago
    #82152
  • Pipeline finished with Success
    5 months ago
    #82155
  • Status changed to Needs work 5 months ago
  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    Left some minor comments on the MR

    #85 asks the same question about sub-classes in contrib/custom code - I think we need an CR for that.

    Did we get to the bottom of #121?

  • Pipeline finished with Failed
    5 months ago
    Total: 642s
    #85687
  • Pipeline finished with Success
    5 months ago
    Total: 701s
    #85701
  • Pipeline finished with Failed
    4 months ago
    Total: 612s
    #101911
  • Pipeline finished with Failed
    4 months ago
    Total: 593s
    #101930
  • Pipeline finished with Success
    4 months ago
    Total: 585s
    #101941
  • Pipeline finished with Failed
    4 months ago
    Total: 570s
    #103704
  • Pipeline finished with Success
    4 months ago
    Total: 606s
    #103712
Production build 0.69.0 2024