Background:
In SA-CONTRIB-2018-081, we moved filter query param denormalization from JsonApiParamEnhancer and agreed to make a public followup to either validate the change or refactor things to be more consistent. This is that issue.
Proposed Solution
Move processing of the sort and page query parameters into EntityResource.
Stop using denormalizers for these parameters.
Outcome
Prerequisite for #2986169: [PP-1] Support sortable, paginated and filterable related resources.
Prerequisite for #2956414: Support mixed-bundle collections (e.g. `/jsonapi/node`)
Fewer normalizers using jsonapi_normalizer_do_not_use_removal_imminent
Less code to maintain/fewer concepts
Prior to commit, a patch for JSON:API Defaults will need to be provided.
| Comment | File | Size | Author |
|---|---|---|---|
| #28 | 3022531-28.patch | 88.36 KB | gabesullice |
| #28 | interdiff.txt | 604 bytes | gabesullice |
| #24 | 3022531-24.patch | 88.35 KB | wim leers |
| #24 | interdiff.txt | 532 bytes | wim leers |
| #22 | interdiff.txt | 6.82 KB | wim leers |
Comments
Comment #2
wim leersThanks for creating this issue. This was originally done in https://security.drupal.org/node/166860#comment-143269, where I wrote:
(That future public issue is this one.)
Comment #3
wim leersCame up again at #2986169-12: [PP-1] Support sortable, paginated and filterable related resources..
Comment #4
wim leersPointed #3031036: Why are sort and page denormalized in JsonApiParamEnhancer while filter is denormalized in the collection controller? here.
Comment #5
effulgentsia commentedWhile there may well be good reasons to move all those out of the enhancer, I'm confused by what made it a necessity for 'filter', so uploading this patch to see what tests fails.
Comment #6
effulgentsia commentedOr rather, this.
Comment #9
effulgentsia commentedOk, so the problem is that if you throw an exception from within a route enhancer, then that happens before any of the parameters/$defaults (that have been determined by this enhancer or any earlier routing code) have been transferred to $request attributes. That includes the '_route_object' attribute. Which means that exception handlers that have conditional logic based on which route was matched will process the exception as if no route was matched. Which in the case of #6, confuses
AuthenticationManager::defaultFilter()into treating theAccessDeniedHttpExceptionexception thrown due to lack of authorization to use a JSON:API filter as something that instead occurred due to a lack of authorization to use Basic Authentication (which is the authentication mechanism used by the test).I think this is a bug with
AccessAwareRouter, and will open a core issue to fix it.But in the meantime, doing what this issue suggests and moving the sort and page denormalization from the enhancer to where the filter one is seems sensible.
Comment #10
gabesulliceThis patch is primarily dumb moves. I'll call out the material logic changes in my next comment.
Comment #11
gabesulliceHere are all the significant changes that would be hard to pick out because of the file removals.
Instead of resolving the sort field during denormalization, we do it in EntityResource. Everything else is the same.
Instead of denormalizing, we're just using a static
createmethod. The only caveat is that we need to do field resolution inside this to avoid more drastic logical changes. Perhaps a followup could make those so that we don't need to pass the field resolver in here.Because the page parameter isn't being "enhanced during the routing phase, the domain object isn't on the request (just the raw query param). Instead, it gets passed in.
The if/else logic here was unnecessary. The parameter could not have been absent even before the refactor.
This is the only material change to the way the Filter object is generated.
Without this change we would have had to call
\Drupal::service('jsonapi.field_resolver')since it can't be injected. I think this is a small price to pay for the overall reduction in complexity.Comment #12
gabesulliceThere were some initial discussions about getting rid of these denormalizers in #2987608-32: Move deserialization from RequestHandler to JsonApiParamEnhancer but a separate issue was not created.
Comment #13
gabesulliceComment #14
gabesullicePrior to commit, a patch for JSON:API Defaults will need to be provided.
Comment #15
wim leersI'm fine with that. But I'm not sure it's a bug in there. The real problem here is not a bug in one place or the other, but the fact that our routing system design/architecture is not unambiguously defined. It's not clear whether this should or should not work!
Opening that core issue would spark the necessary discussion around this. For the scope of the security issue we could not get blocked on that though.
+1
#10: will review tomorrow. But … EIGHTY SIX KILOBYTES?!
Comment #16
gabesullicePatch for JSON:API Defaults ^
Comment #17
gabesulliceDefinitely read #11 first.
Comment #18
wim leersYeah, currently reading this on a mobile device, unfit for patch review. Will do so in the morning.
Great to see progress here in any case :)
@effulgentsia++
@gabesullice++
Comment #19
gabesulliceMarking this as related to #3022583: [META] Normalization System: clean up/speed up/provide schema because it further reduces the surface area of our
Normalizernamespace.Comment #20
effulgentsia commentedComment #21
wim leersThanks for #11, that was a big help :) I definitely like the massively increased clarity and simplicity in
EntityResource: less magic.21 files changed, 669 insertions, 1102 deletions.— I'm not surprised!👍 This all made sense thanks to #11.1 especially.
👍 This is now injected for the reason in #11.3. As a side effect, it also results in less magic because less reliance on semi-globals (IMHO
Requestis the new global in Drupal 8…)👍 Validation and constants are moved out of the former normalizers into the value objects that we're keeping. This means we're turning formerly anemic domain models into objects that contain their associated logic.
👍 Oh hah, this must always have been wrong then! Seems reasonable to change it here, unless somebody objects.
🤔 This … I was confused by.
😨 Apparently I helped make this happen in #2987608: Move deserialization from RequestHandler to JsonApiParamEnhancer, but I'm now quite confused why we did that. As long as we were doing denormalization of filter/page/sort params in a route enhancer (which we're getting rid of here), it sort of was internally consistent, which is a justification. But it still seems less than ideal, less than clear.
I'm relieved to see that at the end of #2987608-14: Move deserialization from RequestHandler to JsonApiParamEnhancer, I expressed this same concern. I'm less impressed by my former self's not seeing any problems with this, and actually being in favor of this 😔
In #2987608-16: Move deserialization from RequestHandler to JsonApiParamEnhancer, @gabesullice did a big refactor of the patch in that issue to introduce this. In hindsight, I think it might've been better to keep
EntityResource::deserialize()instead of this.Even though changing this is out of scope for this issue, I wonder what @effulgentsia thinks about this? Should we move this logic into
EntityResourceinstead, hence removing this service altogether? We could do that in a separate issue.🤔 Following up on the previous point. For now, I think
RequestBodyEnhancerwould be a much clearer name. Thoughts?👏 I hadn't even thought about this, but this madness just disappears, NICE! 👏
(This is where kernel tests fell disappointingly short: we had to replicate much/most of the implementation details, making the tests highly coupled to implementation details…)
👍 This test coverage was not lost, but moved.
👍 These tests are also much easier to understand now, wonderful!
👍 … another case where our existing tests were giving false assurances because they were relying on too much magic.
Actually, thanks to this refactor, we can convert this to a unit test! 👍 In fact, we can do that for all of these, except for
FilterTest, becauseFilteris the only one that has a dependency on another service. Done!Comment #22
wim leersI forgot #21's interdiff 😊
Comment #24
wim leersComment #25
wim leersConfirmed that #16 still works: both JSON:API Extras and its "Defaults" submodules pass tests with it. And it actually became simpler 😀
Comment #26
gabesullice🙏
❤️
🙏
What is this for?
After this has an answer^, this is RTBC!
Comment #27
wim leersRemove it and re-run that unit test. You'll see that PHPUnit complains about it because there were zero assertions :) It's obvious in hindsight: in the previous patches and in HEAD, we're only doing an assertion in case of an invalid input.
Comment #28
gabesulliceAh, makes sense. I cleared it up a little.
Comment #29
wim leersWFM!
Comment #30
wim leersI wrote in #25:
I just checked, and with this applied, https://www.drupal.org/project/consumer_image_styles/releases/8.x-3.x-dev also still passes tests, without any changes 👍
Comment #31
e0ipsoThanks for this! +1
Comment #33
wim leers