Problem/Motivation
entity_jsonapi_entity_filter_access does not provide accurate/sufficient AccessResults. We are considering the main logic:
elseif ($conditions->count() === 1 || $conditions->getConjunction() === 'OR') {
$published_key = $entity_type->getKey('published');
$owner_key = $entity_type->getKey('owner');
foreach ($conditions->getConditions() as $condition) {
if (!($condition instanceof Condition)) {
// Nested condition groups imply logic that is too complex to be mapped.
return [];
}
if ($published_key && $condition->getField() === $published_key && $condition->getOperator() === '=' && (string) $condition->getValue() === '1') {
$result[JSONAPI_FILTER_AMONG_PUBLISHED] = $allowed;
}
elseif ($owner_key && $condition->getField() === $owner_key && $condition->getOperator() === '=' && $condition->getValue() === $account->id()) {
$result[JSONAPI_FILTER_AMONG_OWN] = $allowed;
}
else {
// Unsupported condition, no access can be granted.
return [];
}
}
}
There are two problems, both centered around if (!($condition instanceof Condition)), and a third in the return in the final else branch.
Problem 1 - The early return
This condition is being checked in the context of an OR group, per elseif ($conditions->count() === 1 || $conditions->getConjunction() === 'OR'). The early return in the condition essentially turns it into an AND group by preventing checks on any standard conditions that appear after the given condition and dropping any AccessResults added to $result for prior $conditions in the loop.
Problem 2 - Lack of handling for the view own unpublished permission
The assumption that "Nested condition groups imply logic that is too complex to be mapped." is flawed in this case. This permission results in a condition group containing exactly 2 conditions that check the owner and publish state. These are known cases and can be handled explicitly.
Problem 3 - Unsupported condition, no access can be granted.
This is similar to problem 1. By returning here, we are turning this into an AND group and discarding any previously determined AccessResults.
Steps to reproduce
- Ensure that the authenticated role does not have the "view own unpublished $entity_type" permission, and request a published entity of that type. The request will succeed and return data about the entity.
- Grant the authenticated user role the "view own unpublished $entity_type" permission and request the same entity as before. The response will be an empty 204 response.
Proposed resolution
- Remove both instances of
return []instances, allowing us to gracefully loop over all conditions and return all$resultsafterward. - Add handling specifically for the view own unpublished permission.
Issue fork entity-3349758
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
mrweiner commentedComment #3
mrweiner commentedComment #5
mrweiner commentedComment #6
mrweiner commentedUpdating the issue title and description since the scope is larger than just the permission noted originally.
Comment #7
mrweiner commentedUpdate proposed resolution per MR implementation.
Comment #8
benstallings commentedClaude Code says:
This is a real bug fix — users with "view own unpublished" permissions were silently denied JSON:API filter access.
Concerns
1. Type safety in array_reduce callbacks — The closures type-hint Condition $condition as the second parameter, but the items in a ConditionGroup can be either Condition or ConditionGroup instances. If a deeply nested group is present (unlikely but possible), this would cause a TypeError. The function should either check instanceof Condition first or not type-hint the parameter.
2. Unused $published_key in $has_owner_condition closure — The use clause imports $published_key but it's never referenced. Same for $account not being used in $has_unpublished_condition... wait, $account is unused in the second closure. Both closures import all three variables but only use a subset. This is harmless but sloppy.
3. JSONAPI_FILTER_AMONG_OWN for unpublished — Mapping "own unpublished" to JSONAPI_FILTER_AMONG_OWN is correct per the JSON:API spec constants, since the user can only see their own entities (even though they're unpublished ones). This matches what would happen for the simple owner_key = $account->id() condition.
Verdict
Good bug fix for a real JSON:API access issue. The type-hint concern in the array_reduce callbacks (#1) is the most substantive — it could cause a fatal error on unexpected condition structures. The unused variables in closures (#2) are minor cleanup. Worth merging after addressing the type safety issue.
Comment #9
benstallings commented