Problem/Motivation

Discovered in #2930028: Comprehensive JSON API integration test coverage phase 1: for every entity type, individual resources only.

\Drupal\jsonapi\Controller\EntityResource::createIndividual():

    $entity->save();

    // Build response object.
    $response = $this->getIndividual($entity, $request, 201);
…
    return $response;

and

\Drupal\jsonapi\Controller\EntityResource::patchIndividual():

    $entity->save();
    return $this->getIndividual($entity, $request);

with \Drupal\jsonapi\Controller\EntityResource::getIndividual() doing this:

    $entity_access = $entity->access('view', NULL, TRUE);
    if (!$entity_access->isAllowed()) {
      throw new EntityAccessDeniedHttpException($entity, $entity_access, '/data', 'The current user is not allowed to GET the selected resource.');
    }
…

which means that even if you're allowed to POST or PATCH an entity, you still need explicit GET permission for this entity type.

This is confusing.

Proposed resolution

Core's REST module uses a different approach: when you can POST or PATCH an entity, you also without exception receive the serialization of the entity you just created or modified.

Arguably, JSON API's approach is safer/stricter … but it's important that it behaves similarly to core's REST module. On top of that, the current implementation violates the spec (http://jsonapi.org/format/#crud-creating), because even though the resource (entity) was created, you are not receiving it in the response!

Remaining tasks

Do not check GET (view) access after successfully PATCHing or POSTing.

User interface changes

None.

API changes

None.

Data model changes

CommentFileSizeAuthor
#2 2942255-2.patch858 byteswim leers

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

Status: Active » Needs review
StatusFileSize
new858 bytes
wim leers’s picture

Title: Even when successfully POSTing or PATCHing an entity, you can still get a 403 response when you cannot GET it! » Spec Compliance: Even when successfully POSTing or PATCHing an entity, you can still get a 403 response when you cannot GET it!
gabesullice’s picture

Status: Needs review » Reviewed & tested by the community

Nice catch.

wim leers’s picture

This is now hard-blocking Comment test coverage in #2930028-50: Comprehensive JSON API integration test coverage phase 1: for every entity type, individual resources only because it's POSTing an unpublished comment (which perhaps needs to be approved by a site admin first), and due to this bug the POST request receives a 403 response because only published comments can be read.

In other test cases, I could work around it in #2930028 because I could just grant extra permissions. I can't in this case, because it's not a matter of permissions, but entity data!

So, going to commit this now…

  • Wim Leers committed 4241def on 8.x-1.x
    Issue #2942255 by Wim Leers, gabesullice: Spec Compliance: Even when...
e0ipso’s picture

I'm sorry I'm late to the party, but I think that the previous behavior was the classic mailbox behavior. You can put, but you cannot see the result of the put. This is common in file sharing systems. Is that not something we should honor?

wim leers’s picture

I understand your reasoning, but the spec is pretty clear about it. Let me repeat what I wrote in the IS:

Arguably, JSON API's approach is safer/stricter … but it's important that it behaves similarly to core's REST module. On top of that, the current implementation violates the spec (http://jsonapi.org/format/#crud-creating), because even though the resource (entity) was created, you are not receiving it in the response!

e0ipso’s picture

it's important that it behaves similarly to core's REST module.

While I agree on the principle, I wouldn't like this to be a law.

because even though the resource (entity) was created, you are not receiving it in the response!

The spec also says that you should get a response with /data/type to a GET request. Implicit to that it is that if an error occurs, you need to handle it. I am confident that the spec doesn't mean to say that you need to return inaccessible resources when you do a patch/post request.

If you are not convinced by this, I suggest opening a support request in the JSON API forum, and hold off a release of this commit until we get more clarity.

wim leers’s picture

While I agree on the principle, I wouldn't like this to be a law.

Absolutely!

The spec also says that you should get a response with /data/type to a GET request. Implicit to that it is that if an error occurs, you need to handle it.

Can you rephrase this? I don't understand the point you're trying to make, sorry :(

I am confident that the spec doesn't mean to say that you need to return inaccessible resources when you do a patch/post request.

Hm, you're right that the spec is generally very bad about anything access-related. But isn't it logical that you can access the entity that you just created? You know what data was sent, right? And if there's any computed fields with potentially sensitive data, field access would still prevent those from being included in the response!

e0ipso’s picture

Can you rephrase this? I don't understand the point you're trying to make, sorry :(

All I meant is that the spec assumes that you need to handle errors and exceptions when they occur, even if they are not explicitly mentioned in the particular operation. http://jsonapi.org/format/#errors-processing hints that:

A server MAY choose to stop processing as soon as a problem is encountered.

e0ipso’s picture

But isn't it logical that you can access the entity that you just created?

You can access whatever the access system says. If the system says you cannot view an entity after creating it… oh well. In that case we can either throw a 403 (because the post-create operation is not accessible) or respond with a 204.

gabesullice’s picture

If a POST request did not include a Client-Generated ID and the requested resource has been created successfully, the server MUST return a 201 Created status code.

If a POST request did include a Client-Generated ID and the requested resource has been created successfully, the server MUST return either a 201 Created status code and response document (as described above) or a 204 No Content status code with no response document.

I see no wiggle room here. The MUST is definitive. Either we have a client generated ID or we don't. If we don't have a client-generated ID, we must return the resource per our own documentation:

[When] "Drupalisms" cannot be reconciled with the specification, the module will always choose the implementation most faithful to the specification.

We may omit it only when we have a client-generated ID. We could omit the resource if access is denied at that point, but then we'd have to pass around that information. So we might as well just respond with 201 and call it a day.

A server MAY return 403 Forbidden in response to an unsupported request to create a resource

A 403 implies a bad request. Assuming we have a client-generated ID, returning a 403 where the exact same request with new credentials would result in a 409 conflict breaks HTTP semantics. Which we must not do:

A server MUST prepare responses, and a client MUST interpret responses, in accordance with HTTP semantics.

Finally, if we are to interpret access denied as an "error", then we must undo the successful save and return 403. A request cannot partially succeed:

A request MUST completely succeed or fail (in a single “transaction”).

wim leers’s picture

Status: Fixed » Active

Let's move this back to Active so we don't accidentally forget to reach a conclusion here.

wim leers’s picture

Status: Active » Needs review

Needs review is probably more accurate.

wim leers’s picture

More than a month has passed. How are we feeling about this?

wim leers’s picture

Status: Needs review » Fixed

No feedback for >2 months. I'm going to close this.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.