When I use the jsonapi.entity.to_jsonapi (EntityToJsonApi) service, I get an error:
Server error. The current route is malformed.
Introduced in #2874509: Provide service to simplify generating a JSON API representation of an entity in PHP code.
| Comment | File | Size | Author |
|---|---|---|---|
| #28 | interdiff-stuck.txt | 6.39 KB | wim leers |
| #22 | 2925043-22.patch | 5.66 KB | wim leers |
| #22 | interdiff-20-22.txt | 2.34 KB | wim leers |
| #20 | 2925043-20.patch | 5.25 KB | wim leers |
Comments
Comment #2
a.milkovskyComment #3
a.milkovskyThe working fix.
Explanation:
The resourceType should be set manually in the currentContext. Othewise it will be get from the current request later on.
Comment #4
e0ipsoThanks for the patch!
It would be awesome to get test coverage for this since we already introduced a regression on it once.
Comment #5
wim leersThese are exact mirrors of each other. That is … interesting.
I vaguely remember a discussion in some issue about removing the "context" service altogether. Do you remember where that was, @e0ipso?
Comment #6
wim leersComment #7
wim leersComment #8
wim leersComment #9
wim leersHonestly, this sort of bugginess is exactly what I described I feared in #2874509-26: Provide service to simplify generating a JSON API representation of an entity in PHP code.
I think we should change this code completely, and just do a subrequest, so that we don't need to simulate lots of things. Basically, do what #2852272-6: Make it possible to call jsonapi from within templates does.
Comment #10
e0ipsoHmm, I think the reasoning in the parent issue still applies. This is just a normalization of an entity, we don't need to pass it through the http kernel. The fact that there is a bug does not justify a /fliptable and rewrite the approach completely. This service is being used in production by websites often mentioned in DrupalCon keynotes, so I'm confident that it works to an extent.
The patch looks straight forward enough, but I'd like to understand why it stopped working. @a.milkovsky can you provide reprouction steps? Does this work with
jsonapi-8.x-1.2?Comment #11
a.milkovsky@e0ipso there are no special steps. I get the error when I use the service with a node object. My node has a few taxonomy reference fields, an image field, some text fields and paragraphs.
Code:
Unfortunately I have not tested with the version 1.2.
Comment #12
wim leersWhich reasoning is that? I didn't find any reasoning.
What is the problem with passing it through the HTTP kernel? A Symfony subrequest doesn't result in an actual HTTP request. It's a purely internal business. This is exactly the sort of thing that subrequests are intended to do.
Refactoring internals is not "/fliptable" IMO. I'm only proposing to make this service not need to know implementation details anymore (see
\Drupal\jsonapi\EntityToJsonApi::calculateContext()).I've attached a patch that shows the simplification this enables.
Comment #13
wim leers#12 simply means fewer code paths to maintain, and thus fewer bugs, fewer edge cases, fewer security vulnerabilities.
Comment #15
wim leersSpeaking of security vulnerabilities… there is an obvious one right here!
Unpublishing.
Comment #16
wim leersReported: https://security.drupal.org/node/162210.
Comment #17
wim leersTo make tests pass, you need this… and that's how I noticed this is a security vulnerability.
Comment #18
wim leersRepublishing this issue after it was decided in the security issue that this could be solved publicly.
This is what I wrote:
to which @dawehner responded:
to which I responded:
to which @mlhess responded:
We're now nearly two months further, with zero responses since that last one, so definitely okay to solve publicly.
Comment #19
wim leersTo clarify #18: field access is checked while normalizing fields, that still happens. It's entity access checking that does not happen during normalizing, that happens before (in
\Drupal\jsonapi\Controller\EntityResource::getIndividual()).Comment #20
wim leersRebased #17.
Comment #22
wim leersFix a failure.
Comment #24
wim leersStill 13 failures, but it used to be 2 failures within each of those 13 failing test classes, now it's just 1.
Comment #25
wim leersClosed #2948630: Error using EntityToJsonApi serialize method as a duplicate of this.
Comment #26
wim leersTo clarify: the security problem here lies in the fact that entity access is checked NOT during normalization (in
\Drupal\jsonapi\Normalizer\EntityNormalizer), but before normalization, in the controller (in\Drupal\jsonapi\Controller\EntityResource::getIndividual()).Comment #27
wim leersUnfortunately, if we fix this the way I'm currently proposing, then we need to also update the test coverage I added in #2930028: Comprehensive JSON API integration test coverage phase 1: for every entity type, individual resources only and that we're still expanding.
That means we need to do nasty things with mocking requests, which is almost as bad as the problem we're fixing. But that complexity would live in tests only, so that's definitely still an improvement.
However, for one of the bits of test coverage, we need to normalize an in-memory-but-not-stored modified entity. And the direction that I set out with this patch actually doesn't allow for that.
It'd seem that the easiest way forward is to do #2941685: Port parameter based resource config from REST module first, because it removes the whole
CurrentContextconcept, which will allow the test coverage to not use thejsonapi.entity.to_jsonapiservice, at which point the direction in my patches above is completely doable!IOW:
jsonapi.entity.to_jsonapifor at least one assertion.Comment #28
wim leersComment #29
gabesullice#2941685: Port parameter based resource config from REST module landed. I believe that was the blocking issue.
TBH, @Wim Leers, I don't fully understand the gist of this issue. I've tried to read the issue summary and your comments to get a sense of it, but I'm afraid I'm still not completely following.
I'm pretty certain my lack of understanding results from a different perspective on why this service exists.
I think your view on it is that: it's a way to get the proper JSON API response for a given entity. Is that accurate?
My perspective on it is that it's a way to get a JSON API spec compliant serialization of an entity.
At first glance, those two perspectives probably seem like a distinction without a difference. But I think they the end up informing a lot of decisions.
For example, this service was introduced so that one site could create a representation of an entity and POST it to another JSON API server. That entity could be in-memory only. Thus, one can't do a subrequest because the entity doesn't exist in the database on either server.
IOW, it's more about our funky serializer than it is about providing a service to synthesize responses.
That difference also informs the aforementioned security issue. If this is about getting a JSON API response then one would expect access to be checked because for a given user the response might be a 403 error document and for another user it might be 200. However, if this is about normalization/serialization of any given entity, it's a non-issue because the service's intended purpose is just to perform a data transformation.
Comment #30
wim leersThat's indeed what #2874509: Provide service to simplify generating a JSON API representation of an entity in PHP code said in the very start.
I don't see this mentioned anywhere in that issue. AFAICT they're trying to send existing content to a 3rd party server.
… which would make this untrue. And this then leads me to one of my key concerns with
jsonapi.entity.to_jsonapi: we implement the same logic twice. Rather than reusing what already exists! Implementing this using a subrequest instead allows us to reduce the maintenance burden and completely eradicate subtle differences.Let's assume this is true. Then we ought to remove the
linksportion of the generated JSON API representation, because it currently is invalid, which also means it's an invalid JSON API representation. As #2930028: Comprehensive JSON API integration test coverage phase 1: for every entity type, individual resources only discovered:(This is an example of such a subtle difference.)
I'm fine with that reasoning, if it is applied consistently. Currently, the
jsonapi.entity.to_jsonapiservice does not apply entity access checks, but it does apply field access checks (see\Drupal\jsonapi\Normalizer\FieldNormalizer::normalize()). Which of course is misleading. If it's meant as a pure "transform this to that, regardless of access control", then we need to be much more explicit about it, and have test coverage for it.The above is my POV on A) the
jsonapi.entity.to_jsonapiservice, B) how that service would work ideally.Since this service is available, I started using it in #2930028: Comprehensive JSON API integration test coverage phase 1: for every entity type, individual resources only. And in doing so, I ran into the "in-memory entity" problem (described in #27, and you cited it in your comment). But to be perfectly honest, I think that the test coverage should NOT use the
jsonapi.entity.to_jsonapiservice. Especially because AFAIK we're all agreed in #2952293: Branch next major: version 2, requiring Drupal core >=8.5 that this service should be moved tojsonapi_extras. Therefore the test coverage should not rely on it either.Of course, if people want to continue using
jsonapi.entity.to_jsonapidespite the flaws I've pointed out above, they're free to do so.So, recapping to determine next steps:
CurrentContextaltogether, hence the bugfix in #3 is no longer relevant — the original bug has been fixed!jsonapi.entity.to_jsonapi+CurrentContextto fundamentally changingjsonapi.entity.to_jsonapi's implementation to address the flaws I've described. But per #2952293: Branch next major: version 2, requiring Drupal core >=8.5, doing that is no longer relevant.jsonapi.entity.to_jsonapi, and hence paving the path forjsonapi.entity.to_jsonapibeing moved tojsonapi_extras. But I think it'd be better to do that in a separate issue… done: #2958504: Stop using jsonapi.entity.to_jsonapi service in ResourceTestBase!… which means we can close this too. Tentatively doing so!