Problem/Motivation
When normalizing JSON:API links, links to inaccessible URLs shouldn't be normalized. This matches the behavior of Drupal menues.
Proposed resolution
Allow Drupal\Core\Urls to return AccessResult objects. Use those objects in the LinkCollectionNormalizer
to omit inaccessible links and to capture the cacheability of that result.
User interface changes
None.
API changes
A new optional argument on the Drupal\Core\Url
It's possible that some links that were previously available in JSON:APIs HTTP output will no longer appear, however those links would have led to inaccessible resources anyway.
Data model changes
None.
Release notes snippet
TBD
Comment | File | Size | Author |
---|---|---|---|
#16 | drupal-jsonapi_link_access-3055889-16.patch | 4.08 KB | gabesullice |
#16 | interdiff-13-16.txt | 1.62 KB | gabesullice |
#13 | drupal-jsonapi_link_access-3055889-13.patch | 3.41 KB | gabesullice |
#11 | 3055889-11.patch | 2.1 KB | Spokje |
Issue fork drupal-3055889
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
gabesullicePostponed on #3014704: Expose API to allow links handling for entities from other modules
Comment #3
Wim LeersComment #4
gabesulliceI no longer believe that this issue should be postponed on #3014704: Expose API to allow links handling for entities from other modules.
Comment #6
gabesulliceConverting the parent issue into a related issue. This does not need to be coordinated by the meta issue any longer.
Comment #7
Wim LeersSo this would help
jsonapi_hypermedia
, see #3079664: Move cacheability bubbling complexity from LinkProviderManager to the overriding normalizer. Tagging .Comment #8
Wim LeersI think this is basically what this issue is proposing.
Two questions:
Link
actually respect the access result?Link
constructor (which isCacheableMetadata $cacheability
).?
Comment #9
Wim LeersThe only solution for #8.1 that I see is this.
Comment #11
SpokjeRe-roll of patch #9 against latest 9.1.x branch.
Comment #12
gabesulliceIt wouldn't be useful if it did. The solution in #9 makes it impossible for code that would otherwise provide a link to bubble the cacheability information upward in order to indicate "this user can't access this link because of {time of day}, but if {time of day} changes, allow me to recompute this." Therefore, I think it's desirable to be able to create inaccessible links.
Note that
MenuLinkTreeElement
already let's one create links with access results.However, our implementation doesn't necessarily have to work like that. We could create a new class called
InaccessibleLink
or similar if you don't like the idea of creating links with access results attached @Wim Leers.Comment #13
gabesulliceHere's what I'm thinking ☝️
Comment #16
gabesulliceComment #18
Wim Leers🤓 I'd rather explicitly inject the current user rather than relying on the
NULL
-falls-back-to-current-user-in-request-context behavior.Core went with with this implicit magic in many places because it couldn't break backwards compatibility, but we can be explicit within JSON:API.
I think that this is good, but I'd love to see the concrete impact on the normalized output.
When we add the necessary test coverage, that will become obvious of course! 👍
I think this means inaccessible links will simply be omitted, because we don't have anything collecting omittedd links. But … I don't remember the JSON:API implementation details well enough to know that for sure.
Comment #19
Wim LeersThis bit in particular is something that is IMHO an oversight/omission in the current state of affairs — this pattern exists everywhere else except here!
Comment #20
Wim Leers😍🙏
Great comment!
Comment #22
gabesulliceI rebased against the latest on 9.2.x, fixed the two test failures in #16 and addressed #18.
This reasoning in #16 was incorrect.
AccessResultNeutral
doesn't have any relation to whether a route will be access checked on the request or not. In that case, route definitions use_access: TRUE
, which means the route access check will return anAccessResultAllowed
.Comment #23
gabesulliceRecategorizing this as a bug report per #19.
Comment #24
gabesulliceNew test fails
Comment #25
gabesullice33290d67 should be carefully reviewed since it is touching a very important section of code.
The problem it solves is that some route access checks, like
_entity_access: user.update
, return anAccessResultNeutral
when a user isn't allowed to perform that operation. However,AccessManager->check()
also returnsAccessResultNeutral
when it can't determine access without a request object.This makes it impossible to distinguish between
and .The logic in the referenced commit attempts to resolve that ambiguity by explicitly allowing route access if it is impossible to get a usable access result without a request object.
The implication of that change is that some links/breadcrumbs may appear which were previously omitted because access was indeterminable. However, I think omitting those links/breadcrumbs in the first place could be considered a bug since it prevents site users from navigating to pages that they actually have access to.
Comment #26
gabesulliceComment #27
jibranSeems like a security improvement. How are we supposed to get a red/green run with MRs?
Comment #28
gabesulliceRebased, no changes ^
Comment #31
gabesulliceThanks for the reviews!
@jibran, a new MR was the best way I could think of to do R/G runs.
MR !227 is the green branch & MR !268 is the test only branch.
@larowlan, after you pointed it out, I saw that the
AccessManager
class was not the right place for that logic. I was stumped with where it did belong though, so I ended up pairing with @tim.plunkett. He questioned why there was even a case that all of a routes access checks didn't apply in the first place and challenged me to show that it was actually a legitimate use case. (credited him for that)When I dug into it, I realized that JSON:API's
RelationshipFieldAccess
access check can actually be refactored to avoid requiring the request in the first place. In fact, it was this access check that sent me down theAccessManager
rabbit hole in the first place since nearly all of JSON:API's tests began failing when practically everyrelated
link disappeared from its responses because checking access on them gaveAccessResultNeutral
results.Once I refactored that access check, it was able to return an explicit
AccessResultAllowed
, which meant that the conundrum I faced in #25 disappeared 🎉I created change record for the newly deprecated route access check.
Note that I couldn't deprecate the service with using the
deprecated:
YAML property since tagged services are automatically instantiated. I wasn't able to use the@deprecated
annotation either because some static analysis recognized that the service definition was referencing a deprecated class.Comment #32
gabesullice@tim.plunkett filed this related issue to discourage/prevent future implementers from only using route access checks which require a request. This would have helped steer JSON:API in the right direction from the start. I don't believe that these issues should block one another.
Comment #33
Wim Leers#25: wow, that commit is indeed rather scary and requires the highest level of scrutiny possible!
I was going to suggest the same that @larowlan already suggested a week ago: the risk in this behavior change seems to be too great. A new method with this specific behavior expectation baked in seems a safer option.
But … all that has become irrelevant thanks to @gabesullice pairing with @tim.plunkett apparently 😄🥳
#31 shows how much simpler and more local the changes became — now instead of a high-risk targeted change in infrastructure affecting every single request processed by Drupal, it became a change only affecting the JSON:API module, and instead of being complex it became a trivial refactoring!
When this reaches RTBC — and I think it is very close — then I think a
git diff -M5%
ofRelationshipRouteAccessCheck
would help see every reviewer how little this issue is actually changing 🤓👍Comment #34
Wim LeersAside of that "present" instead of "absent" typo, I think this is RTBC now!
Comment #35
gabesulliceComment #36
Wim LeersRTBC++
Comment #37
alexpottI probably would have fixed the two nits on commit but the bit about replacing a typehint with an assert needs more discussion. I can't see why that's necessary.
Comment #38
gabesulliceAnswer: yes. Apparently I'm a Drupal dinosaur.
Taking the liberty to go back to self-RTBC 'cause I'm only fixing a couple nits that would have been fixed on commit and I'm only reverting a change as recommended by @alexpott.
Comment #40
alexpott@gabesullice the ? is unnecessary... you could already pass NULL to access.... see https://3v4l.org/spMXd - I'm removing it because it is an unnecessary change. If you want to use the ? in new code fine... but then do
?AccountInterface $account
- the= NULL
is redundant.Crediting @larowlan and @jibran for code review.
Committed adf684d and pushed to 9.2.x. Thanks!
Fixed on commit.