Problem/Motivation

I have created a custom entity type, with appropriate entity access controls. I would like to allow users to view and manipulate entities they have access to per the access management via JSON API. However I have a requirement that users should not be able to gain information on how many entities there are on the system. The JSON API entity endpoints (/json/entity_type_id/bundle_id) disclose this information by returning "access denied" type of messages for each entity.

hook_jsonapi_entity_filter_access() does not affect the plain entity endpoints.

The response is something like this:

{
    "data": [],
    "jsonapi": {
        "version": "1.0",
        "meta": {
            "links": {
                "self": {
                    "href": "http://jsonapi.org/format/1.0/"
                }
            }
        }
    },
    "links": {
        "self": {
            "href": "http://mysite.lndo.site/jsonapi/my_entity/my_bundle"
        }
    },
    "meta": {
        "omitted": {
            "detail": "Some resources have been omitted because of insufficient authorization.",
            "links": {
                "help": {
                    "href": "https://www.drupal.org/docs/8/modules/json-api/filtering#filters-access-control"
                },
                "item:Klyr2un": {
                    "href": "http://mysite.lndo.site/jsonapi/my_entity/my_bundle/ad12e3d3-95cb-48eb-a11a-d0906591c2eb",
                    "meta": {
                        "rel": "item",
                        "detail": "The current user is not allowed to GET the selected resource. The 'view any unpublished content' permission is required."
                    }
                },
                "item:sIMXhH8": {
                    "href": "http://mysite.lndo.site/jsonapi/my_entity/my_bundle/697c2e6e-cb05-4bfa-961d-703a9901a4d2",
                    "meta": {
                        "rel": "item",
                        "detail": "The current user is not allowed to GET the selected resource. The 'view any unpublished content' permission is required."
                    }
                }
            }
        }
    }
}

Proposed resolution

There should be a way to hide entities that are not accessible to the user from the list of results. Maybe by modifying the list of results in a hook. What I would like the output to look like, if there are no items the user has access to:

{
    "data": [],
    "jsonapi": {
        "version": "1.0",
        "meta": {
            "links": {
                "self": {
                    "href": "http://jsonapi.org/format/1.0/"
                }
            }
        }
    },
    "links": {
        "self": {
            "href": "http://mysite.lndo.site/jsonapi/my_entity/my_bundle"
        }
    },
    "meta": {
        "omitted": {
            "detail": "Some resources have been omitted because of insufficient authorization.",
            "links": {
                "help": {
                    "href": "https://www.drupal.org/docs/8/modules/json-api/filtering#filters-access-control"
                }
            }
        }
    }
}

Comments

holist created an issue. See original summary.

wim leers’s picture

Status: Active » Postponed (maintainer needs more info)
Issue tags: +API-First Initiative

The JSON API entity endpoints (/json/entity_type_id/bundle_id) disclose this information by returning "access denied" type of messages for each entity.

I think you're saying you want 404 error responses instead of 403?

e0ipso’s picture

If you create a node as unpublished and then try to access it as an anonymous user, you'll get a 403 not a 404. JSON:API should be consistent in that regard. Do you agree with that?

holist’s picture

I would like the listing endpoint not to list the entities the user does not have access to.

What comes to accessing invididual entities via /jsonapi/entity_type_id/bundle_id/uuid, that should go through the regular access checks, and all the relevant access control handlers and hooks should be triggered with that, right? So whatever we want to return in those cases, can be handled there?

To clarify the logic: On the UI we actually are returning 403 for any attempts to access either entities that are non-existent or user does not have permission to access, but obviously don't list the entities user does not have access to.

wim leers’s picture

not to list the entities the user does not have access to.

JSON:API didn't decide this, #2471154: Anonymous user label can't be viewed and auth user labels are only accessible with 'access user profiles' permission did. If you want to change this, you'll have to override the logic in UserAccessControlHandler. You can do that using the "access"-related alter hooks documented in entity.api.php.

wim leers’s picture

Category: Feature request » Support request
Status: Postponed (maintainer needs more info) » Fixed

Given the week of silence, it seems that #5 answered your question :)

holist’s picture

Issue summary: View changes
Status: Fixed » Active

Wim Leers sorry for taking the time (and thanks for putting in the effort on this), I just didn't right away see what you meant with your comment. I now read that issue rather carefully through, and I'm thinking that maybe I don't understand the implications fully. #2471154: Anonymous user label can't be viewed and auth user labels are only accessible with 'access user profiles' permission was about viewing users and labels when the user does not have access to the actual entities, if I understand it correctly? I am having hard time applying that discussion to this issue.

To clarify: My example is not dealing with user entities, but my own custom entities. I have implemented an access control handler for the entity type that checks the relevant permissions. But when the access check returns forbidden, the entity is still listed, and though no information of it is displayed, the count of the entities is in this case considered information disclosure.

So what I still don't understand is where can I affect the listing of the entities, so that the ones that user does not have access to would be removed from there.

wim leers’s picture

Status: Active » Postponed (maintainer needs more info)

Did you implement one or more of the hooks mentioned in https://www.drupal.org/node/3036792?

holist’s picture

Yes I tried those hooks, but as I talked with Gabe Sullice on Slack we got to the conclusion that the hooks do not affect the list of entities, only the access checks of the individual entities.

wim leers’s picture

I didn't see #9 until now, sorry.

Given what you wrote in #9:

But when the access check returns forbidden, the entity is still listed, and though no information of it is displayed, the count of the entities is in this case considered information disclosure.

What do you mean exactly by "is still listed" then? Do you mean:

  • the UUID
  • the UUID + label
  • the UUID + label + all fields

?

gabesullice’s picture

@Wim Leers IIRC, @holist is talking about omission links disclosing the existence of the omitted entities.

AFAIK, the only way to prevent this is to ensure that those entities aren't in the query results to begin with.

The hooks from the sec release do not work for him because his request if not using a filter... We only run our hooks when there's a filter.


I think the only solution is to implement a query alter hook for the custom entity type to ensure that inaccessible entities are not returned.

wim leers’s picture

I see. In that case: we don't consider the existence of entities of a certain type to be a secret. If you can do filtering, you can deduce something. If you can't do filtering, you can't; you just get a bunch of UUIDs. We don't consider that information disclosure.

Why do you consider this information disclosure, @holist?

holist’s picture

@Wim Leers, we are building a system for subcontractor management for a client. The client considers this information to be business secrets. Being able to list the count of entities would disclose the number of subcontractors and data they have created on the system, thus revealing information about the scale of my client's business, and the subcontractors' business to each other. They operate in a highly competitive market where this kind of disclosure would give insight to the business of the client and could also undermine trust between the client and the subcontractors.

wim leers’s picture

Okay. In that case, I'd suggest adding an additional _permission: 'foobar' route requirement to the jsonapi.my_entity--my_bundle.collection route. That'd result in a 403 for users without that permission.

Being able to list the count of entities would disclose the number of subcontractors and data they have created on the system, thus revealing information about the scale of my client's business

Note that that count would include currently inactive subcontractors, or even subcontractors that no longer exist. In fact, creating bogus data could then be considered an intimidation and/or misinformation measure.

and the subcontractors' business to each other.

This would require filtering, and as soon as filtering is involved, no counts are revealed — see #11.

holist’s picture

Status: Postponed (maintainer needs more info) » Closed (works as designed)

@Wim Leers ah now I see, thank you!