{"record":{"id":"4bb90f49c3e44ed7","repo":"passbolt/passbolt_api","slug":"multiple-has-access-filters-are-not-supported","errorCode":null,"errorMessage":"Multiple has-access filters are not supported.","messagePattern":"Multiple has-access filters are not supported\\.","errorType":"exception","errorClass":"Cake\\Http\\Exception\\BadRequestException","httpStatus":400,"severity":"warning","filePath":"src/Controller/Users/UsersIndexController.php","lineNumber":134,"sourceCode":"                $computedFindIndexOptions\n            );\n        }\n\n        $this->paginate($users);\n        $this->success(__('The operation was successful.'), $users);\n    }\n\n    /**\n     * @throws \\Cake\\Http\\Exception\\ForbiddenException if user doesn't have access to the resource requested by the filter\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if multiple has-access filters are requested\n     * @param array $options from\n     * @return void\n     */\n    public function assertHasAccess(array $options): void\n    {\n        if (isset($options['filter']['has-access']) && count($options['filter']['has-access'])) {\n            if (count($options['filter']['has-access']) > 1) {\n                throw new BadRequestException(__('Multiple has-access filters are not supported.'));\n            }\n            $resourceId = $options['filter']['has-access'][0];\n            $service = new UserHasPermissionService();\n            if (!$service->check(PermissionsTable::RESOURCE_ACO, $resourceId, $this->User->id())) {\n                throw new ForbiddenException(__('This operation is not allowed for this user.'));\n            }\n        }\n    }\n}\n","sourceCodeStart":116,"sourceCodeEnd":144,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/src/Controller/Users/UsersIndexController.php#L116-L144","documentation":"BadRequestException (HTTP 400) from UsersIndexController::assertHasAccess: the `filter[has-access]` query parameter was supplied with more than one resource id. The endpoint only supports checking access against a single resource per request.","triggerScenarios":"GET /users.json?filter[has-access]=<uuid1>,<uuid2> (or repeated has-access filters) — i.e. any request where the has-access filter array contains 2+ entries.","commonSituations":"Client code passing an array of resource ids assuming multi-resource support; UI selecting multiple resources and forwarding all ids; combining comma-joined values into the filter.","solutions":["Issue one request per resource id, each with a single has-access value.","Change client code to send only the first/selected resource id, or loop over ids.","If bulk checks are needed, batch client-side across multiple single-id requests."],"exampleFix":"// before\nconst ids = ['res-1', 'res-2'];\nawait api.getUsers({ filter: { 'has-access': ids } });\n// after\nconst results = await Promise.all(ids.map(id =>\n  api.getUsers({ filter: { 'has-access': [id] } })\n));","handlingStrategy":"validation","validationCode":"function assertSingleHasAccess(filter) {\n  const ids = filter?.['has-access'];\n  if (Array.isArray(ids) && ids.length > 1) throw new Error('has-access accepts at most one resource id');\n}","typeGuard":null,"tryCatchPattern":"try { await api.getUsers({ filter: { 'has-access': ids } }); } catch (e) { if (e.code === 400 && /Multiple has-access/.test(e.message)) { return Promise.all(ids.map(id => api.getUsers({ filter: { 'has-access': [id] } }))); } throw e; }","preventionTips":["Enforce one-resource-per-request semantics in client filter builders.","Deduplicate and cap the has-access filter before sending.","Handle 400 responses by falling back to per-id requests."],"tags":["bad-request","query-parameters","filters","http-400"],"backgroundTag":"invalid-query-parameter","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}