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

  1. 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.
  2. First fetch the resources the user can access via GET /resources.json and use one of those ids in the has-access filter.
  3. Re-check the resource id: it must be a valid existing resource UUID; a deleted resource yields no permission match.
  4. 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

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


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)