When included entities for a request run into a "denied" access check, they are currently represented in the response's Compound Document as an "include" object with a "meta" key. This passes on the information that the entity was not accessible. However, according to the JSON API spec, all Resource Objects (which includes included objects) MUST have "type" and "id" top-level members.

The absence of these keys causes errors in Ember applications that utilize JSON API when communicating with Drupal, and likely other frameworks as well.

Proposed resolution

Output the "type" and "id" top-level members when serializing included Entities that failed access checks.

Comments

hampercm created an issue. See original summary.

hampercm’s picture

hampercm’s picture

Assigned: Unassigned » hampercm
StatusFileSize
new6.07 KB

Test-only patch

wim leers’s picture

Status: Active » Needs review
e0ipso’s picture

Status: Needs review » Needs work

Setting back to Needs work since we expect tests to fail.

hampercm’s picture

Assigned: hampercm » Unassigned

I'm having a difficult time figuring out how to implement this cleanly. Any ideas?

wim leers’s picture

I don't know the JSON API normalizers remotely well enough to give any advice at all. Pinged e0ipso on IRC.

hampercm’s picture

Assigned: Unassigned » hampercm

Giving this a 2nd try...

hampercm’s picture

Assigned: hampercm » Unassigned

I still can't figure out a fix for this.

hampercm’s picture

Another thing to consider: is this even the right approach for access-denied errors on included/related objects? I definitely see the advantage to reporting that an included object was unavailable, but having the included object in the response, even though that object that has none of its attributes available, will likely cause problems in external applications. For example, for an Ember.js app, I'd need to customize its JSON API adapter to throw out a 403'ed object, so the app doesn't try to continue using it and fail due to not having any attributes to work with. Likely, I'd just error out of whatever operation I was trying to accomplish, so a partial failure would be equivalent to a complete failure.

As an alternative, I could see the JSON API module returning a failure response if any of the includes were inaccessible. This is one possible interpretation of the JSON API spec:

If a server is unable to identify a relationship path or does not support inclusion of resources from a path, it MUST respond with 400 Bad Request.

( see http://jsonapi.org/format/#fetching-includes )
In this case, it would be a (conditional) lack of support for including resources from that path. The error object in the response could point to the include query parameter that caused the failure using its source => parameter element. See http://jsonapi.org/format/#error-objects

Thoughts?

e0ipso’s picture

That makes quite a lot of sense. I think this included behavior was an artifact of the normalization and the inclusion process.

If anything I think that we should just exclude any entity from the included pool and add an entry in the errors section of the metadata. Additionally we'll need to make sure that we maintain full linkage, excluding any possible children entities from the tree. This may or may not happen already with the current implementation.

e0ipso’s picture

Title: Included entities that have access denied must still provide "type" and "id" » Included entities that have access denied must comply with the spec
wim leers’s picture

It sounds like the spec is ambiguous in this respect. Should we not discuss this with the JSON API spec authors to A) clarify this, B) make the spec more specific?

hampercm’s picture

Yes, this is definitely an area where the spec is vague, or left up to the implementation's discretion. I'd be happy to open an issue on the official spec's GitHub.

Note that a response can't contain both a data and an error top-level member, so the response would need to be either a clear failure or a successful response without any error info.

e0ipso’s picture

Note that a response can't contain both a data and an error top-level member, so the response would need to be either a clear failure or a successful response without any error info.

To avoid that violation the Partial Success specification adds the errors inside of the meta section. We are doing that already for collections, so we should treat includes the same way.

wim leers’s picture

This discussion made me think of the 'view label' entity access operation — created #2843922: Show label of inaccessible entities ('view' access denied) when 'view label' access is allowed for that.

hampercm’s picture

@e0ipso Includes are being treated with the Partial Success spec on the current HEAD. However, the id and type members, which are required by spec, are not currently added. That's what this issue was originally written to address. Implementing that fix seemed like it would be difficult and messy, so I had suggested a different approach--dropping Partial Success--and my reasoning behind it in #10.

e0ipso’s picture

However, the id and type members, which are required by spec

This is only if we add them in the included section. What I meant in #11.

If anything I think that we should just exclude any entity from the included pool and add an entry in the errors section of the metadata

So, that means:

  1. Do not include any reference to any non-accessible entity in the included section of the payload
  2. For each non-accessible entity add an error object to the meta > errors section of the payload. In there, we need no type nor id to comply with the spec, if I'm not mistaken.
hampercm’s picture

Assigned: Unassigned » hampercm

@e0ipso got it! #18 makes sense. I'll look into implementing that.

hampercm’s picture

Assigned: hampercm » Unassigned

I'm still having trouble figuring out the normalization code.

e0ipso’s picture

Status: Needs work » Needs review
StatusFileSize
new3.81 KB

I'm proposing this.

The patch will give this output for a request that tries to include 3 related articles, but 2 of them are unpublished.

GET /jsonapi/node/article/d6ad5f38-056c-44b0-9377-7a6006a76dca?_format=api_json&fields[node--article]=nid,field_related&include=field_related&XDEBUG_SESSION_START=PHPSTORM HTTP/1.1
Host: d8dev.local

Outputs:

{
    "data": {
        "type": "node--article",
        "id": "d6ad5f38-056c-44b0-9377-7a6006a76dca",
        "attributes": {
            "nid": "2492"
        },
        "relationships": {
            "field_related": {
                "data": [
                    {
                        "type": "node--article",
                        "id": "674aa079-a9a5-4ea9-84a5-fcaa7ff89f3c"
                    },
                    {
                        "type": "node--article",
                        "id": "db15f994-d921-4776-ad26-44971db7f236"
                    },
                    {
                        "type": "node--article",
                        "id": "0cf799ee-f3c2-47b2-aefe-94a04e965cc6"
                    }
                ],
                "links": {
                    "self": "http://d8dev.local/jsonapi/node/article/d6ad5f38-056c-44b0-9377-7a6006a76dca/relationships/field_related?_format=api_json",
                    "related": "http://d8dev.local/jsonapi/node/article/d6ad5f38-056c-44b0-9377-7a6006a76dca/field_related?_format=api_json"
                }
            }
        },
        "links": {
            "self": "http://d8dev.local/jsonapi/node/article/d6ad5f38-056c-44b0-9377-7a6006a76dca?_format=api_json"
        }
    },
    "links": {
        "self": "http://d8dev.local/jsonapi/node/article/d6ad5f38-056c-44b0-9377-7a6006a76dca?_format=api_json&fields[node--article]=nid%2Cfield_related&include=field_related&XDEBUG_SESSION_START=PHPSTORM"
    },
    "included": [
        {
            "type": "node--article",
            "id": "0cf799ee-f3c2-47b2-aefe-94a04e965cc6",
            "attributes": {
                "nid": "2125"
            },
            "relationships": {
                "field_related": {
                    "data": [
                        {
                            "type": "node--article",
                            "id": "55da880f-6ab0-4b8d-bae0-b20fc1d3be53"
                        },
                        {
                            "type": "node--article",
                            "id": "c61ddb0a-862a-4bb9-8c65-d80008acda05"
                        }
                    ],
                    "links": {
                        "self": "http://d8dev.local/jsonapi/node/article/0cf799ee-f3c2-47b2-aefe-94a04e965cc6/relationships/field_related?_format=api_json",
                        "related": "http://d8dev.local/jsonapi/node/article/0cf799ee-f3c2-47b2-aefe-94a04e965cc6/field_related?_format=api_json"
                    }
                }
            },
            "links": {
                "self": "http://d8dev.local/jsonapi/node/article/0cf799ee-f3c2-47b2-aefe-94a04e965cc6?_format=api_json"
            }
        }
    ],
    "meta": {
        "errors": [
            {
                "title": "Forbidden",
                "status": 403,
                "detail": "Access checks failed for entity node:2475.",
                "links": {
                    "info": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4"
                },
                "code": 0
            },
            {
                "title": "Forbidden",
                "status": 403,
                "detail": "Access checks failed for entity node:2112.",
                "links": {
                    "info": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4"
                },
                "code": 0
            }
        ]
    }
}
e0ipso’s picture

I created a follow up to improve the contents of the error object: #2844130: Create a custom EntityAccessDeniedHttpException.

wim leers’s picture

But what about

Note that a response can't contain both a data and an error top-level member, so the response would need to be either a clear failure or a successful response without any error info.

Isn't #21 sneakily circumventing that by putting the errors in meta? I guess there's no other way? I guess errors = hard errors, and meta > errors = soft errors?

e0ipso’s picture

This is partially discussed in http://discuss.jsonapi.org/t/multi-status-responses-partial-success-in-p...

Short story, the spec would have you fail the whole request if one of the includes is not success (like one item being 403 in a collection or in a list of includes). So yeah, this is correct:

I guess there's no other way? I guess errors = hard errors, and meta > errors = soft errors?

wim leers’s picture

Thanks, that confirms my suspicion.

Leaving it to @hampercm to RTBC.

damienmckenna’s picture

the spec would have you fail the whole request if one of the includes is not success

Therefore, shouldn't the whole request fail instead of giving a partial response? Does this not mean that if the module is to follow the spec that this "needs work"?

e0ipso’s picture

@DamienMcKenna sorry for the confusion, but I meant that if we wanted to add the errors straight to the errors section in the top level we'd have to fail the whole request. I feel very strongly not to fail a request if one included element (via includes or i the collection) has access denied.

In any case this module is following the spec strictly with this patch applied.

e0ipso’s picture

Status: Needs review » Fixed

Given that:

  1. @hampercm is good with the approach
  2. This is consistent with the current implementation with collections
  3. @Wim Leers didn't have any code related suggestions
  4. This fixes the current API compliance issue

I'm going to merge this to unblock #2840677: Validate responses against the JSON API specification.

@DamienMcKenna, @hampercm please feel free to create a follow up ticket if you feel we should resolve this problem in some other way. I just want to bring includes and collections to the same situation. If we need to change the approach, it needs to change in both scenarios.

  • e0ipso committed 77c6362 on 8.x-1.x
    fix(Serialization) Included entities that have access denied must comply...
hampercm’s picture

Sorry, I had meant to review this yesterday, and got diverted. This looks good, though it omits the extra functional test I had added in #3, which seems important for validation. I'll file an issue to add that test.

Status: Fixed » Closed (fixed)

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