passbolt/passbolt_api · error · Cake\Http\Exception\ForbiddenException
This operation is not allowed for this user.
Error message
This operation is not allowed for this user.
What it means
UsersIndexController::assertHasAccess throws this ForbiddenException when the authenticated user requests GET /users.json?filter=has-access=<resourceId> but UserHasPermissionService::check determines the user has no permission entry on that resource. The has-access filter restricts the user index to users sharing access to a given resource, so a caller without access to that resource is denied entirely.
Solutions
- Verify the authenticated user actually has a permission row on the resource (check the permissions table for aco_foreign_key = resourceId and aro_foreign_key = userId) before calling the endpoint.
- First fetch the resources the user can access via GET /resources.json and use one of those ids in the has-access filter.
- Re-check the resource id: it must be a valid existing resource UUID; a deleted resource yields no permission match.
- If the user should have access, grant them a permission on the resource (share endpoint) or log in as a user with access.
Example fix
// before
await fetch('/users.json?filter=has-access=' + staleResourceId);
// after
const resources = (await fetch('/resources.json')).body;
if (resources.some(r => r.id === resourceId)) {
await fetch('/users.json?filter=has-access=' + resourceId);
} Defensive patterns
Strategy: try-catch
Validate before calling
// before calling, ensure user shares the resource
const resources = await api.get('/resources.json?contain[permissions]=1');
if (!resources.some(r => r.id === resourceId)) {
throw new Error(`User has no access to resource ${resourceId}`);
} Try / catch
try {
const users = await api.get(`/users.json?filter=has-access=${resourceId}`);
} catch (e) {
if (e.status === 403 && /not allowed for this user/.test(e.message)) {
// fall back to resources the user does have access to
}
} Prevention
- Resolve resource ids from GET /resources.json rather than hardcoding them.
- Check the permissions table/UI that the user shares the resource before querying has-access.
- Handle revocation gracefully: a permission removed elsewhere can invalidate previously working filters.
When it happens
Trigger: GET /users.json?filter=has-access=<resource-id> where (a) the resource id is valid but the authenticated user has no permission (no user/owner/read/update share) on it, (b) the resource id belongs to a deleted resource, or (c) the user passes another user's resource id they do not share.
Common situations: Clients building resource-sharing UIs with a stale or mistyped resource id; a user whose permission on the resource was just revoked while their session/UI still references it; API scripts iterating resources where some are no longer shared with them; tests using a fixture resource id belonging to another user.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Access restricted to administrators.
- Only admin can create or update organization settings.
- Only admin can create or update subscription information.
- Only administrators are allowed to create/update MFA…
- Only administrators are allowed to create/update MFA…
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/4f7c1f9a9665473f.
Report an issue: GitHub.
Appendix: source
Thrown at src/Controller/Users/UsersIndexController.php:139
$this->success(__('The operation was successful.'), $users);
}
/**
* @throws \Cake\Http\Exception\ForbiddenException if user doesn't have access to the resource requested by the filter
* @throws \Cake\Http\Exception\BadRequestException if multiple has-access filters are requested
* @param array $options from
* @return void
*/
public function assertHasAccess(array $options): void
{
if (isset($options['filter']['has-access']) && count($options['filter']['has-access'])) {
if (count($options['filter']['has-access']) > 1) {
throw new BadRequestException(__('Multiple has-access filters are not supported.'));
}
$resourceId = $options['filter']['has-access'][0];
$service = new UserHasPermissionService();
if (!$service->check(PermissionsTable::RESOURCE_ACO, $resourceId, $this->User->id())) {
throw new ForbiddenException(__('This operation is not allowed for this user.'));
}
}
}
}
View on GitHub (pinned to 31c1bbc10f)