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.
- Fresh install drupal8.5.x,jsonapi,devel,basic_auth
- Create two authenticated user: jim and odin
- Give "View own unpublished content" and "Article: Create new content"permission to authenticated user
- Use jim to create one unpublished article
- Clear cache
- use odin GET /jsonapi/node/article through postman, we get empty result.
{ "data": [], "jsonapi": { "version": "1.0", "meta": { "links": { "self": "http://jsonapi.org/format/1.0/" } } }, "links": { "self": "http://cache.n8/jsonapi/node/article" }, ... }
- Then use jim to GET /jsonapi/node/article through postman, the data is still empty
{ "data": [], "jsonapi": { "version": "1.0", "meta": { "links": { "self": "http://jsonapi.org/format/1.0/" } } }, "links": { "self": "http://cache.n8/jsonapi/node/article" }, ... }
- Clear cache
- Use jim to GET /jsonapi/node/article through postman we can get right result.
{ "data": [ { "type": "node--article", "id": "32023099-4805-4be1-921a-9e03dd622561", "attributes": { "nid": 1, "uuid": "32023099-4805-4be1-921a-9e03dd622561", "vid": 2, "langcode": "en", "revision_timestamp": "2018-06-30T15:30:17+00:00", ... }
Comment | File | Size | Author |
---|---|---|---|
#20 | 2982770-20.patch | 4.08 KB | nassaz |
#14 | 2982770-14.patch | 761 bytes | nassaz |
Comments
Comment #2
lawxen CreditAttribution: lawxen at Sparkpad commentedComment #3
lawxen CreditAttribution: lawxen at Sparkpad commentedComment #4
Wim LeersUnpublished to verify this is not a security vulnerability. It sounds like it is one.
Comment #5
Wim LeersBug confirmed. The root cause is that the response contains a
user.permissions
cache context only, not auser
cache context. This is why the response does not vary correctly between users odin and jim. Fortunately, it's not a security vulnerability AFAICT. So, republishing.But why doesn't it? JSON API does correctly call entity access. But if jim created the unpublished article, and odin is the first to access it (to prime the cache), then an unfortunate combination of circumstances occurs in
\Drupal\node\NodeAccessControlHandler::checkAccess()
:When odin first accesses it, the if-test evaluates to FALSE, and we evaluate node grants instead to determine access. Access is then denied, which is good, but it doesn't vary by
user
. It only varies byuser.permissions
. This leads to a perfectly cacheable response, so Dynamic Page Cache will cache it and … also serve it to any other user with the same set of permissions, which includes jim!However, when jim (the owner) first access it, the if-test evaluates to TRUE. Access is granted, which is good, and the access result varies by
user
, as it should! In this case however, Dynamic Page Cache won't cache the response, because it deems responses varying byuser
not worthy of caching, since there's potentially millions of variations to cache (when there are millions of users).Combined, this means that the owner's response will never be cached, but any non-owner's request will be. And after a non-owner has requested this, and the response is cached, then the same cached response will be sent to the owner. Users who aren't supposed to be able to access this data are never able to, but users who are supposed to have access to the data lose access as soon as any user who isn't has requested it!
The solution is trivial:
Comment #6
Wim LeersComment #7
lawxen CreditAttribution: lawxen at Sparkpad commented@Wim Leers
The actual situation I met is group module's group_content, I just use core to reproduce the bug, so the patch won't solve our problem, I'll try to reproduced it on other entity type.
Comment #8
lawxen CreditAttribution: lawxen at Sparkpad commentedI find this issue also happens on group_content, eck, commerce_order.
because group_content is complicated, so let's focus on node, eck, commerce_order first.
Same reproduced method: Enable the "view own ... " permission
Comment #10
lawxen CreditAttribution: lawxen at Sparkpad commented@Wim Leers
This issue is much more serious when it meet jsonapi's pager.
If user A request an endpoint and return empty in the first page. It will make user B's request get empty too. I have written an reproduce steps previously:
Use odin GET /jsonapi/node/article?page%5Boffset%5D=50&page%5Blimit%5D=50
We can get one article
Comment #11
lawxen CreditAttribution: lawxen at Sparkpad commented#5 Very detailed analysis. I solve our problem of group_content by the same way of #6
Maybe many contribute module should do the similar change.
@Wim Leers Besides, thanks for the so many guidance for me in drupal.org. I learned a lot from you.
Comment #12
Wim LeersEverything in #10 sounds like just another consequence of the root cause I explained in #5. I completely agree the consequences can be big!
#11: adding
->cachePerUser()
is not the solution that is always correct. It's important to look at the access control logic. It makes sense in the case ofNodeAccessControlHandler::checkAccess()
because it's first checking something that is true per user. In case of the Group module, there are severe known issues with cacheability, see #2952714: Group module's GroupAccessResult::allowedIfHasGroupPermission(s)() does not include cacheability.I'm glad that you're finding it helpful! ❤️👍 I also want to thank you for creating such excellent bug reports, and helping to dig deeper to find the root cause, and then fix it properly! 👏 5 JSON API issues already in the past 12 months!
Comment #13
nassazFirst you have to give permission "Administer content" to Jim to create unpulished content.
After analysis, I can confirm that there is a problem of cache dependencies, and indeed it is necessary to add a context user but it's not the good place (cf. next patch)
Comment #14
nassazCheck this patch.
Comment #16
nassazComment #17
Wim Leers@ntijani Thanks for working on this! It's good to have my conclusion verified. The patch in #14 is identical to the one I posted in #6 though. It'd be great if you could work on making tests pass! :)
Comment #18
nassaz@Wim Leers thank you for your message.
You made the modification in the class "NodeAccessControlHandler" which is particular, I think it must be put in the method access() of the class "NodeGrantDatabaseStorage", i will try to make tests pass.
:)
Comment #19
chipway CreditAttribution: chipway at Chipway commentedRemoved DevDaysLisboa to find it in the same place.
Comment #20
nassazUpdate.
Comment #30
Sara101 CreditAttribution: Sara101 commentedThis issue seems old and abandoned but is similar to a problem I'm facing.
So admin users have permissions to view unpublished content.
If the admin user unpublished content and an unauthenticated user was first to visit the jsonapi node endpoint, then the unauthenticated user would not load content as expected.
But…
If the Admin user is the first to visit the jsonapi node endpoint after unpublishing the node, then unauthenticated users visiting same endpoint can also see the unpublished data until I manually clear cache(not expected)