Closed (fixed)
Project:
JSON:API
Version:
8.x-2.x-dev
Component:
Code
Priority:
Normal
Category:
Bug report
Assigned:
Unassigned
Issue tags:
Reporter:
Created:
12 Jul 2018 at 18:54 UTC
Updated:
23 Aug 2018 at 13:44 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
gabesulliceThis is the most basic change of assumptions. I'm not sure that we have any scenarios in the tests where we have an accessible field but only inaccessible targeted resource though, so this might still pass.
Comment #3
gabesulliceComment #5
gabesulliceAwesome, so we do have a failing case already :)
Did some digging and this is caused by a term's
vidrelationship (the relationship to the terms vocabulary) and since we're running the test without a permission for viewing that config entity, we get the failure we want (for now).Comment #6
gabesulliceFurther refining the expectations. Since #2940339: Port reference field support for non-empty entity reference fields not pointing to an entity from #2543726, if we treat
relatedroutes like collection routes, we'll need to take virtual resource identifiers into consideration.Comment #7
gabesulliceNo sense in just *acting* like this is a collection, let's use the
getCollectioncode.This should work pretty much as expected multiple cardinality fields. But we'll need to do some tweaking for single cardinality fields. Remember, an empty collection is always
[], but a single value relationship that's empty isNULL, so that's going to need to be communicated somehow if we're gonna reuse that code.Comment #8
gabesulliceI forgot to include this in my last patch.
Comment #12
gabesulliceOkay, the meat of the changes to handle single-value collections in the normalizers. Let's see what's left.
Comment #14
wim leersI don't fully grok this.
Can you explain this using a concrete example?
Comment #15
gabesulliceSure!
Consider that you make an entity reference field on a node to nodes. That node can have multiple references. Let's call it
field_related_articles. You create articles A, B and C. You publish A and B.Now, from A, you add a reference to B and C.
You request
/jsonapi/node/article/A/field_related, the data you receive will be[B]because C is omitted because it's unpublished. If you then requested/jsonapi/node/articleyou'd get[A, B]. If you filtered out A, then the first response would be entirely indistinguishable from the second response if not for theselflinks. The related route is a resource collection.Now, the spec goes on to say:
In #2940339: Port reference field support for non-empty entity reference fields not pointing to an entity from #2543726, we established that the
relationshiproute can list relationship objects that are not represented at therelatedroute. If a term'sparentwere single value and referencing a "virtual" resource, what would it return? It should returnnull, because it's "an appropriate response when the requested URL is one that might correspond to a single resource, but doesn’t currently."Long story short, if we have a single value related route, we should return
null, because everywhere else in the API, we omit inaccessible resources. We don't return 403, even when all items have been omitted.getRelateddoesn't work like that though, it proxies the request for a single value related field togetIndividual, which then returns a 403, because the code thinks it's dealing with a direct request for that individual resource.Just like the collections route, the related route access should not be dependent on what resources are represented there. Regardless if its for a to-one or a to-many relationship.
Comment #16
wim leersThanks, that helps a lot 👌
This bit from the IS now makes perfect sense, and is a perfect summary:
Thanks, and please proceed!
Comment #17
gabesulliceOur include tests became invalid after this change because they use expected related responses to generate the expected results. Since this moves individual forbidden access errors from the top-level
errorsmember to themeta.errors, it wasn't picking them up correctly.This will still be failing because some cache info isn't yet handled.
Comment #19
gabesulliceThis makes everything that we put into
EntityCollectioncacheable.Comment #20
wim leersThis can just be
$this->setCacheability($entity);Comment #21
gabesulliceComment #23
gabesulliceMake sure that only the primary collection query is filtered by bundle. IOW, don't add a bundle filter to the related collection.
Cleaning up and addressing reviews next :)
Comment #24
gabesulliceComment #26
gabesulliceCode standards and #20 addressed.
Now, last remaining failure. Then self-review and un-assign :)
Comment #27
gabesulliceShould be green 🤞
Comment #28
gabesulliceSelf-review. Next patch to contain changes.
s/an/a
"This happens when node access applies to the query for example."
Is
array_values()necessary?This is the "big change" we're no longer saying a single-value related route is the same and
getIndividual. Everything else is fallout from this.This makes related routes pageable and filterable!... BUT, we shouldn't introduce a big feature like that without tests. I'm going to comment this out and make a followup. I'm in no hurry to have that done.
The entity list cache tag is not added to this one, that's because the list will only change when the host entity changes.
Copy same comment from above: "Ensure that any cacheability from query execution is applied."
This was moved so that it could be applied separately from the
relatedcollection query.This was moved so that it would apply only to the
getCollectionquery.We no longer need to return an entity/access tuple because LabelOnlyEntity and EntityAccessDeniedHttpException now carry their own cacheability.
This was a really subtle bug to track down. It only appeared when two or more includes were forbidden and would cause all but one include to go missing!
This is the change.
s/an/any
Comment #29
gabesullice1. Done.
2. Made it better, I think.
3. I ended up fixing the comments for
loadEntitiesWithAccess()to reflect that it no longer returns an entity/access tuple and I moved thearray_valuescall to it....
5. Created #2986169: [PP-1] Support sortable, paginated and filterable related resources..
...
7. Done.
13. Done.
Ready for a final review! 🎉
Comment #30
wim leers#28:
Thanks especially for points 4, 5 and 6!
Point 11 sounds like an absolute nightmare to track down! Well done! 👏
This patch is mostly just moving existing code around. Logic in
EntityResource::getCollection()andEntityResource::respondWithCollection()is being moved around to allow for code reuse byEntityResource::getRelated().Makes sense.
s/difference/distinction/ if we're changing these lines anyway.
We went from using
::referencedEntities()to still using that, but using it to perform a query.We didn't do that query before.
I'm not sure how to feel about that. Can you explain your rationale?
Then let's
extends \Drupal\Core\Http\Exception\CacheableAccessDeniedHttpExceptioninstead ofextends HttpException implements CacheableDependencyInterface. That's what #2929428: [>=8.5] Convert "throw new *HttpException" into "throw new Cacheable*HttpException" where possible is about, but this is then at least already one tiny step :)Same remark as in #20.
Let's document when/why this would not be the case, because "collection" pretty much implies "can contain multiple" for any reader.
👍
"cardinality" vs "multiple".
I like "cardinality" better.
👍
This is confusing. We're telling this constructor that the values do not represent a collection if it's a single-cardinality collection.
So really, at that point,
EntityCollectionis more like anEntityContainer?It's confusing that the variable name was changed, but the method name remains the same.
Nit: incomplete docblock.
Oh, these were missing before? Nice catch! 👏
Comment #31
wim leersDoesn't this also solve #2965056: Support `include` parameter on relationship routes (turns out it accidentally works on cold caches: fix + test this)?
Comment #32
gabesullice1. ✅
2. I realized the same thing near the end. We totally could just pass the results of
referencedEntities()into theEntityCollectionconstructor (after checking access ofc). That's mostly because it wasn't until the end that I decided that we shouldn't introduce filtering/sorting in this issue. So, what's the rationale at this point? It queues up #2986169: [PP-1] Support sortable, paginated and filterable related resources. as a four line change + tests:3. Good point. ✔️
4.
$entity_accessmust only implementAccessResultInterfce, butsetCacheability()requires the argument to implementCacheableDependencyInterface.5. I went ahead and aligned all the naming and docs to be in line the concepts/language of the spec, e.g.
EntityCollection->ResourceCollectionas in "related resource collection" (Cmd + F "resource collection" reveals numerous references on http://jsonapi.org/format/). The rename interdiff is separate from everything else.7. ✅
8. 👍
9. I hope this isn't confusing anymore because of #5. LMK if you still find it confusing.
10. Good point. Changed to
getAccessCheckedEntity(). Does that work?11. ✅
12. 👍
#31 Huh, didn't remember that that even existed. I tried to verify that this fixed it (because it would have) but it turns out that it's already working for related routes. I updated that issue title to reflect that it only applies to
relationshiproutes, which this does not fix.Comment #33
wim leersEntityCollection… meaning no impact! Then I can live with this change happening here.Interdiff review:
Oh, hah, Drupal core apparently also confuses "multiple" and "cardinality" :)
s/bool/int/
"resource collection" but it contains "entity" objects.
I don't think it's worth renaming this class right now.
Why different defaults?
Or, why even have defaults at all? Why not require explicitness instead?
Shouldn't we also assert that the cardinality is non-zero *and* not smaller than minus 1?
Only valid values are: -1, 1, 2, 3 … N
Comment #34
wim leersComment #35
gabesulliceDon't worry about it :) You're right.
1. Heh, yep.
2. ✅
3.
Sort of. It contains entities, but also our own LabelOnlyEntity wrapper, and now EntityAccessDeniedHttpExceptions too. At some point, I hope it will contain what will end up being resource identifier objects.
I'm confused:
I didn't include the rename in the patch, but I did include an interdiff that will apply to the patch if you want to include it on commit.
I'll admit, this has been a personal pet peeve of mine for a while... now seemed like an opportune time to get it done.
4. I bet you didn't realize this would be the biggest time sink of that review but it was! :P Turns out the
RelationshipNormalizerValuehas its own cardinality logic. So whenJsonApiDocumentTopLevelNormalizerValueis given any value other than1, it doubly wraps the array of values in a multiple cardinality field. I didn't try to resolve that conflict in this patch, it ended up opening up a can of worms. I did make it a required argument though.5. ✅
Comment #36
gabesulliceI forgot one property in the rename patch.
Comment #39
gabesulliceThis was kicked back because HEAD broke on the latest version of core.
Comment #42
gabesulliceThe fail in #35 is actually a bad assertion. 401 is the correct response code when the user is not authenticated and 403 is correct when the user is authenticated but not authorized to view a particular entity.
Comment #43
gabesulliceThis is just getting too big, making it super difficult to review.
I broke off an issue for the EntityAccessDeniedHttpException piece: #2986987: Convert EntityAccessDeniedHttpException into cacheable exception
Taking for myself to reroll a smaller patch.
Comment #44
gabesulliceAlright, here's the patch without that issue included (it is a blocker though).
Combined patch included as well.
Interdiff is between the last patch and the combined patch. What's in the interdiff? Getting rid of all the moving around that happened in order to make the query work, but that was removed in #35. Also, getting rid of the no longer necessary $request argument and response code arguments in the various EntityResource methods.
Comment #47
gabesulliceComment #49
gabesulliceCombined patch passed.
Comment #50
wim leersinterdiff-renamewasn't part of the patch yet 😳 Sorry!Marking postponed.
Comment #51
gabesulliceAlso want to get this in first: #2987206: Refactor `getEntityAndAccess` to return cacheable objects with access cacheability rather than an entity/access pair.
Comment #52
wim leersI just committed #2986987: Convert EntityAccessDeniedHttpException into cacheable exception.
Comment #53
wim leers#2987206: Refactor `getEntityAndAccess` to return cacheable objects with access cacheability rather than an entity/access pair. also just landed. Retesting #47's non-combined patch…
Comment #55
wim leers#47 doesn't apply because I asked in #2986987-11: Convert EntityAccessDeniedHttpException into cacheable exception.2 to descope it, and you did. I think we can do this patch without those changes.
Comment #56
gabesulliceRerolled for the reasons described in #55.
Comment #57
gabesulliceComment #58
wim leersSorry for the long silence — I was on vacation since the 24th!
This is now basically down to nits and making the set of changes as minimal as possible, to make it simpler for future readers to understand this change:
This is 99% just reformatting code.
The only actual change here is
->filterEmptyItems(). Is this a necessary change? If so, do we have test coverage for it?If not essential, should we defer this to a separate minor bugfix issue?
This again seems to be 100% identical. It'd be simpler to understand and hence commit this if these changes were omitted.
This is a bugfix 👍
Nit: Leftover that needs to be reverted.
Comment #59
wim leersNote: I fully expect to be able to commit this after just one more reroll! 🙏👌
Comment #60
gabesullice1 + 2: Done. I seem to remember
filterEmptyItems()being a necessary change because of acall ->getEntityTypeId() on NULLerror, but the two most likely test classes (CommentTest and TermTest) both pass locally, so we'll see if it's really necessary after testbot runs. Hopefully not :)3. Aye.
4. Good aye.
Comment #61
gabesulliceComment #62
wim leersMUCH better! Because much less change :)
Übernit: s/is for/for/
This comment is outdated. Can just be removed.
Other than those two doc nitpicks, this is RTBC! I fixed those docs nitpicks myself. Committing… 🎉
Comment #63
wim leersThis is actually unused. Removing this.
(There is one other CS violation, but that one is pre-existing.)
Comment #65
wim leersCommitted!
(I don't think @e0ipso would have wanted to give his blessing for this first, since this is a pure bugfix issue/patch, without significant architectural changes. The most significant changes are in the test coverage!)
Comment #66
wim leersThis also unblocked #2853066: Spec Compliance: Inaccessible collection/related resources surface errors: should be 200 with hypermedia + metadata, one of the last few spec compliance bugs! 🎉 As well as #2986169: [PP-1] Support sortable, paginated and filterable related resources., a feature request.