Closed (fixed)
Project:
JSON:API
Version:
8.x-2.x-dev
Component:
Code
Priority:
Normal
Category:
Task
Assigned:
Unassigned
Issue tags:
Reporter:
Created:
26 Mar 2018 at 13:45 UTC
Updated:
23 Oct 2018 at 10:54 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
gabesullice@e0ipso/@Wim Leers, any thoughts about this? This has come back up as part of my testing of collections.
My preference would be to follow the same logic as I laid out here: #2853066-13: Spec Compliance: Inaccessible collection/related resources surface errors: should be 200 with hypermedia + metadata
Comment #3
e0ipsoAccess can only be guaranteed at the entity level, the entity type definition is not necessarily enough. For me that's the last nudge into having each entity fail differently if necessary and succeed with the collection itself. So +1.
Comment #4
wim leers+1
#2853066: Spec Compliance: Inaccessible collection/related resources surface errors: should be 200 with hypermedia + metadata was pushed to 2.x. Should this be pushed to 2.x too? Or can/should we do it in 1.x, because this is something that currently just is broken/fails? Can it be done elegantly in 1.x, or would it be much easier to just do it all in 2.x?
Comment #5
wim leersComment #6
gabesulliceI can't see a way to do this in 1.x without wasting a ton of effort.
I'm not sure "postponing" is the right term, I imagine this will be resolved in one go, but we should leave this here to be sure it happens.
Comment #7
gabesulliceComment #8
wim leersCan you explain why #2949019: Untangle Relationship(Normalizer) and EntityReferenceFieldNormalizer is related?
Comment #9
gabesulliceSure.
This is my recollection of the issue, it's been a while since I was heads down in it:
EntityReferenceFieldNormalizerreturns aRelationshipobject, which is normalized byRelationshipNormalizer(which also utilizesRelationshipItemNormalizer).RelationshipItemNormalizerthen makes a call toJsonApiDocumentTopLevelNormalizerand passes that result when creating aRelationshipItemNormalizerValue. Later, in the actual top level normalizer value, a call togetIncludeis made to allRelationshipItemNormalizerValuesand that is put under the serializedincludesection.Along that winding path, any include-related access denied exceptions get lost (or are unable to be passed through) to the final document.
Comment #10
wim leersThanks! (Admittedly I don't see exactly where these issues intersect, but I do see how they're related. I'm sure that #9 will help when we work on this issue!)
Comment #11
wim leersI think that the patch at #2976108-5: Spec Compliance: Impossible to include related resources when relationship field is not in a request's sparse fieldset shows a pretty simple way for us to support this.
Comment #12
wim leersComment #15
gabesulliceThe above two commits restore the test coverage exemptions in place until this issue is resolved. They do not solve this issue.
Comment #16
wim leersActually, this was discovered not in #2953321: Comprehensive JSON API integration test coverage phase 5: nested includes and sparse field sets, but in #2956084: Impossible to raise an error when an `include` is requested for an inaccessible relationship field..
At least, that's the issue that made the
@todostart pointing to this issue. Because it was actually introduced in 165974 — for the 1.14 security release!Comment #17
wim leersI don't think this is postponed on any particular issue anymore?
Also, making this test/patch pass is AFAICT the goal.
Comment #18
wim leers@gabesullice told me in chat he thought that he thought/hoped #2976108: Spec Compliance: Impossible to include related resources when relationship field is not in a request's sparse fieldset could serve as an inspiration for how to implement this. And I apparently said something similar in #11. I'm happy to say that turned out to be true :)
The IS says:
I'm assuming it's the latter: an error added to
meta.errors.Assuming that, this patch is a starting point. The bubbling of
meta.errorsfrom deeper levels to the top level is a bit messy, because, to my great surprise, that is not something that is actually supported yet! :OComment #19
wim leersThis caused those failures. Fixing…
Comment #20
wim leersComment #21
wim leersAlso working to fix the remaining failures.
Comment #22
wim leersThis fixes most but not all failures.
Comment #23
gabesulliceThat's what I was poorly trying to get across in #9; it's caused so much pain in testing, as evidenced by all the removed
unsetsandforeachloops in #22's interdiff. I'm SO happy to see those deletions! :)Comment #24
gabesulliceI think this may need to pass through
ResourceType::getPublicName()unless$context['include']already went through the reverse process.I wish we had a
class InternalFieldName extends \SplStringandExternalFieldNameclass to help track these values and their transformations as they make their way through the codebase. What would you think of that (here, or as a followup)?I really need to use this pattern more often. I always forget about
NestedArray.Since
$previous_errorsis a list of errors, I think we want to push the rasterized value onto that array, not merge it into the list array. PHP--I think this is to ensure that nobody overrides the normalization process and adds their own metadata?
same as above.
❤️
😍
Comment #25
gabesulliceThe code above is part of the interdiff at #2972808-8: Comprehensive JSON API integration test coverage phase 6: POST/PATCH/DELETE of relationships and its counterpart should be removed here along with the other modified expectations.
Comment #26
wim leersThis fixes
NodeTest::getRelated()andNodeTest::getRelationships(). I'm not convinced this is what we want though, but there already is a pre-existing todo for that very seem reason, about the change this interdiff is making…Comment #28
wim leersI actually wanted to shift my focus from this to #2955020-17: Spec Compliance: JSON API's profile/extention (Fancy Filters, Drupal sorting, Drupal pagination, relationship arity) needs to be explicitly communicated, but given that this blocks #2972808: Comprehensive JSON API integration test coverage phase 6: POST/PATCH/DELETE of relationships, I spent some more time on it. For the first time
NodeTestwill pass all tests. Hopefully some others will too!Comment #30
wim leersBack on this.
Comment #31
wim leersThis fixes
TermTest::testGetIndividual().More in the morning.
Comment #33
wim leersQuoting #4:
Quoting #6:
So #6 moved this from 1.x to 2.x.
Yet this apparently is a blocker for #2972808: Comprehensive JSON API integration test coverage phase 6: POST/PATCH/DELETE of relationships, and that issue is against 1.x. So … I'm confused now.
Comment #34
gabesulliceI couldn't see a way to do this "without wasting a ton of effort"; however:
1) you've found a way to do this with a lot less effort than I imagined (I thought it'd practically be a rewrite of the normalizers)
2) that effort isn't wasted if it unblocks #2946537: Test coverage: Inclusion of intermediate resources when include is a multi-part relationship path (you linked #2972808 in #33 by mistake I think).
IOW, before it was a blocker for test coverage, the decision reached in #2853066: Spec Compliance: Inaccessible collection/related resources surface errors: should be 200 with hypermedia + metadata was to change the way errors are put in the document. That change would be in 2.x. So spending time to make this work properly in a fashion that we already had decided to replace seemed frivolous.
Comment #35
wim leersI didn't link to #2972808: Comprehensive JSON API integration test coverage phase 6: POST/PATCH/DELETE of relationships by mistake, I thought this was a blocker to that issue! Based on #2972808-8: Comprehensive JSON API integration test coverage phase 6: POST/PATCH/DELETE of relationships. But as I explained in #2972808-22: Comprehensive JSON API integration test coverage phase 6: POST/PATCH/DELETE of relationships, that was a misinterpretation on my side! Sorry :(
Thanks for explaining that this is a blocker, but not for #2972808, for #2946537: Test coverage: Inclusion of intermediate resources when include is a multi-part relationship path!
I am still digging into the 6 remaining failures. Man, they're hard to fix. I've fixed one more failure, I'll just post that rather than waiting for all of them to be fixed. This fixes
MediaTest::testGetIndividual().Comment #37
wim leersFix
FileTest::testGetIndividual()andCommentTest::testGetIndividual()in a similar way.Comment #39
wim leersAll this time, I was trying to get the expected
pointerto match the actual one. And that got me very deep into the details of the test coverage that automatically constructs expected responses for collections, and includes for collections; those expected responses are constructed by following the web of responses. This is understandably and necessarily complex.But then it hit me: I can just grant additional permissions. For some reason,
getIncludePermissions()was only being granted for includes for the individual test coverage, not for includes for the collection test coverage. This seems like a simple oversight.This should make all tests pass except
UserTest::testCollectionandEntityTest::testCollection().Comment #41
wim leersYay, down to 2 failures as expected! 🎉
EntityTest::testCollection()needed special care. I expected the fix to be similar to those in #39. But that didn't work out. I found the reason: its owner is user zero, the anonymous user.viewaccess to the anonymous user is always forbidden byUserAccessControlHandler. If #2843922: Show label of inaccessible entities ('view' access denied) when 'view label' access is allowed were already fixed, this could've worked.But the solution is simple: just don't make user zero the owner! That was a non-essential detail of the test coverage anyway.
Comment #43
wim leersThe last one:
UserTest::testCollection(). Not only are thepointers wrong, there's also a difference in error count! 7 versus 9.Applying the same pattern as #39 and #41 fixes the
pointers problem and in doing so, it reduces the number of errors, but the delta remains: 4 versus 6.The count delta is due to the sorted collection containing user zero, and the way the expected collection response is built; it is fetching related data (to fetch the expected includes) for every entity in the collection, even if the entity is not accessible. But if the entity is not accessible, then neither can its includes be accessible; otherwise that'd be information disclosure. In other words: things are working correctly and securely, but the expectations generated by the tests are wrong :) A simple if-test in the right place fixes this!
This should be green 🙏
Comment #44
gabesulliceThis makes complete sense. Good explanation.
Comment #46
wim leersIn #41,
UserTest::testCollection()failed. In #43, this is fixed, but nowUserTest::testGetIndividual()is failing. Why?This causes many additional fields to show up in
testGetIndividual(), which then causes thedoTestIncluded()→getExpectedIncludeResponse()→getExpectedGetIndividualResourceResponse()→getExpectedDocument()call chain to get a set of fields that is only a subset of what is now returned.Gah! 😭
Turns out we didn't even that
administer userspermission; the errors match exactly, they're just sorted differently! That's hopefully fixable by updating\Drupal\Tests\jsonapi\Functional\ResourceTestBase::sortErrors():)Comment #47
gabesulliceGREAT work! I'm sure that was tedious and hair-pulling to accomplish.
Can you address #24.3?
"Allow inaccessible includes to raise an error"
Would that be correct? If so, it's easier to understand.
A comment here would go a long way. As I understand it, you're doing this to change the pointer. Perhaps there should just be a method called
setPointeron the exception class.This is mentioned in #24.3
Can't this just be
access user profiles?My main concern with granting all these permissions to solve the test failures is that we're simply silencing the associated access "error" rather than asserting that it bubbled up correctly.
Comment #48
wim leersWhile working on this, I spotted a fair amount of duplication that we've accumulated, and which seriously complicated the process of getting to a green patch. This works at least for
UserTest::testGetIndividual(), let's see if it works for everything!I think there's more to be done still, but the functional overlap between
getExpectedIncludeResponse()anddecorateExpectedResponseForIncludedFields()is significant.I suspect
getExpectedRelatedResponses()andgetExpectedIncludedResourceResponse()are similarly overlapping.Note that this probably should happen in a separate issue, but having done so much digging in this issue, I didn't want this insight to get lost, and posting a separate patch will mean less (since this issue is improving a fair bit of our test coverage). If this is green, it should probably be omitted from this patch and spun off into its own issue. Unless @gabesullice thinks it belongs here.
Comment #49
wim leersI'm actually not at all convinced that
/data/relationships/…is the rightpointerhere.If it's for an include, the
pointershould probably be/includes?That being said… I don't think it makes sense to fix that here. That's going to be another massive undertaking.
Comment #50
wim leersAddressing all the reviews now!
#24:
\Drupal\jsonapi\Normalizer\JsonApiDocumentTopLevelNormalizer::expandContext()seems to confirm your suspicion. Because the variable is called$public_includes. But re-reading\Drupal\jsonapi\Context\FieldResolver::resolveInternalIncludePath()indicates the opposite! Therefore$context['includes']lists internal, resolved includes. This is also the only sensible thing here; otherwise we'd continuously be translating back and forth everywhere. It makes sense thatexpandContext()did this for us.Renamed the variable in
expandContext()to avoid future confusion.\Drupal\jsonapi\Normalizer\Value\JsonApiDocumentTopLevelNormalizerValue::rasterizeValue(). Seems to work fine? Perhaps I'm misunderstanding what you wrote?metathat is notmeta.errors. For examplemeta.openapi. If/when we start doing that, this assertion will fail, which will remind us to update this logic, and we'll be thankful to not have to figure this out all from scratch! It's a safeguard for our future selves :)#47:
Perhaps you could take that on? :)
Comment #52
wim leerscomposer/packagist are having a bad day:
Retesting.
Comment #53
gabesulliceThis is definitely tech debt we should pay down, but not here.
#50.24.3: I'm just very surprised that
HttpExceptionNormalizerValuereturns a list in every case. If you're confident that this is the case, then I am too :)I agree that
/includes/$host_field_namewould be ideal, but it's not worth addressing, as you said. Thepointerconcept in the spec is really meant for POST/PATCH request documents that fail validation constraints AFAICT.👌
Hoo boy, sure :S, I'll give it a go this afternoon :P
Comment #54
gabesulliceFirst, re-rolling after #2972808: Comprehensive JSON API integration test coverage phase 6: POST/PATCH/DELETE of relationships.
Comment #55
gabesulliceFixing an outdated method name from #2972808.
Comment #56
gabesulliceThis consolidates a bit of code duplication by putting includes decoration in the expected collection method.
Then, we introduce the code that will test an include collection without authentication. If it should be differ by user/permissions, we then test it again with appropriate permissions added.
Comment #57
gabesulliceThis should address some of the new errors that showed up after re-rolling.
I'm done for the weekend, so unassigning if someone wants to pick this back up. What's likely needed is for the same pattern to be applied to individual includes + any other errors that pop up.
Comment #59
gabesulliceStarting up again
Comment #61
gabesulliceAlright, looks like the we need to refine the caching expectations.
BlockContent* is probably still failing.
Comment #62
gabesulliceComment #63
gabesulliceWhoops, you can ignore #62.
Comment #66
gabesullice/me crosses fingers!
Comment #67
wim leers🤞 → 🤘
Comment #68
gabesulliceSweet! A review would be great @wim leers or @e0ipso :)
Comment #69
wim leers#55: 👍
#56 + #57:
Why only do this for this entity type? Why not do this in
ResourceTestBase?Refactoring/cleanup: 👍
So this is new test coverage:
Great!
But where is this causing the request to be unauthenticated, like the comment said?
##5:
I made the same remark in #49. I think we can leave that for a future issue? This is still a big step forward.
#66:
Nit: "included" vs "include"
Overall patch review:
We should replace these @todos with an issue link.
This is the key/central change that triggered all other changes.
Could also use a link to that same issue.
In here, bugfixes to how includes are rasterized; necessary to let includes bubble up meta errors!
I wrote:
And I think you added the cited new test code to test that. But does it actually test this? I don't think it does.
Again point to that same issue.
Comment #70
wim leersRebased #66. Two conflicts had to be resolved (one with #2962461: JsonApiDocumentTopLevelNormalizer is SerializerAware but doesn't get the serializer injected, one with #2948666: Remove JSON API's use of $context['cacheable_metadata'], both got committed today).
Comment #71
wim leers#69 still needs to be addressed.
Comment #72
wim leersComment #73
wim leersIt'd be great to get this done, so we can also close #2946537: Test coverage: Inclusion of intermediate resources when include is a multi-part relationship path.
Comment #74
gabesulliceSince #70, `meta.errors` has been removed in favor of "omissions". Also, the include process was rewritten in #2997600: Resolve included resources prior to normalization.
Both of those issue should have made this significantly easier, but it also means that #70 is no longer applicable and will be practically impossible to reroll.
This patch just enables commented out expectations.
Comment #76
wim leers👍
Makes sense.
Comment #77
gabesulliceThis gets things working more, but not perfectly.
The
vialink needs to be more specific.Comment #78
gabesulliceQueuing tests.
Comment #80
wim leers#77 actually doespass tests, but fails on a single CS violation!
Fixing that.
Comment #81
wim leersWhat can I say? This is now all that's necessary.
Compare that to #70. It's night and day!
But in #70, we're also adding
getIncludePermissions()and adding test coverage:Shouldn't we retain those things?
Comment #83
wim leersOops, I misinterpreted #77 — I thought it was passing tests, apparently it wasn't!
Comment #84
gabesulliceI should have assigned this to myself, it obviously still a WIP
Comment #85
gabesulliceThis should be closer, if not complete.
Comment #87
gabesulliceThis asserts that includes on LabelOnlyEntities can also raise an omission. (Yes, in case you were curious, it was super subtle and frustrating to debug 🙃)
Comment #89
gabesulliceAnother little fix.
Comment #91
wim leersI believe you!
Down to 2 failures :) Patch looks good! Just one nit for now:
s/for a/for an entity included via a/
Comment #92
gabesulliceThis should make the last tests pass.
The suggestion in #91 does not make sense. You actually might have access to the entity included via a relationship. The specific access issue is that you're not allowed to view the field which targets that entity. I tried to clarify the comment. (n.b., this kind of confusion is why I created #2997594: Introduce a more generic exception class).
Next comment should address #81 (or after some investigation, say why it should be addressed here).
Comment #93
gabesulliceD'oh!
Comment #94
gabesulliceThis restores the assertions introduced in #56 through #66, as @Wim Leers suggested in #81.
Comment #95
gabesulliceComment #97
gabesulliceThis fixes the changes in #94 to account for changes in the codebase since #66.
It also fixes the CS violations in #94.
Comment #98
gabesulliceI.. I think this issue might finally be coming to a close! 😲
Comment #99
wim leersMakes sense to move this logic into the helper, because the logic became more nuanced.
Wow 😵
Übernit: s/Grant/Grants/. Will fix on commit. Interdiff already attached.
Interesting that we only needed this for
taxonomy_term. In #66, we needed this for many more fields. Why is that?Point 3 is the only reason that this isn't RTBC and committed yet.
Comment #100
gabesulliceI spent some time looking into #99.3. I simply copied that from the interdiffs between #56 and #66. At one point it appeared to fix something. But I've removed it and tests pass locally. I don't know why it was needed, but it isn't any more. Removed.
I applied the #99 interdiff to this patch. Thanks!
Per #9, setting to RTBC.
Comment #101
wim leersI still want to verify this in detail before committing. Just to be 100% sure, and confirm my own understanding.
I expect to commit this tomorrow :)
Comment #103
wim leersReverted the README changes that accidentally sneaked in to #100.
This is a fix that as in the earlier patch iterations, introduced in #28. If you check
\Drupal\user\UserAccessControlHandler::checkFieldAccess(), you can see thatadminister usersis definitely the right one.So some change in the earlier patch iterations was causing
getIncludePermissions()to be used correctly, in HEAD its' being used incorrectly. I managed to reproduce that same flaw with the current patch iteration. But also with HEAD. We shouldn't block this issue on fixing a pre-existing bug. Created a new issue for that: #3005158: Nested include test coverage is inadequate wrt "include permissions".Comment #104
wim leersComment #105
wim leersSo this issue uncovered [#30051858]. But it also unblocked #2946537: Test coverage: Inclusion of intermediate resources when include is a multi-part relationship path!
Comment #106
gabesullice🎉🎉🎉🎉🎉🎉
> 100 comments, multiple patch approaches, numerous blockers and more than 6 months since the initial report. Finally!
Comment #107
wim leers:)