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
Comment #2
wim leersI think you're saying you want 404 error responses instead of 403?
Comment #3
e0ipsoIf 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?
Comment #4
holist commentedI 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.
Comment #5
wim leersJSON: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 inentity.api.php.Comment #6
wim leersGiven the week of silence, it seems that #5 answered your question :)
Comment #7
holist commentedWim 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.
Comment #8
wim leersDid you implement one or more of the hooks mentioned in https://www.drupal.org/node/3036792?
Comment #9
holist commentedYes 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.
Comment #10
wim leersI didn't see #9 until now, sorry.
Given what you wrote in #9:
What do you mean exactly by "is still listed" then? Do you mean:
?
Comment #11
gabesullice@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.
Comment #12
wim leersI 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?
Comment #13
holist commented@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.
Comment #14
wim leersOkay. In that case, I'd suggest adding an additional
_permission: 'foobar'route requirement to thejsonapi.my_entity--my_bundle.collectionroute. That'd result in a 403 for users without that permission.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.
This would require filtering, and as soon as filtering is involved, no counts are revealed — see #11.
Comment #15
holist commented@Wim Leers ah now I see, thank you!