{"record":{"id":"1e02dc496fd8f57a","repo":"passbolt/passbolt_api","slug":"the-permissions-data-must-be-an-array","errorCode":null,"errorMessage":"The permissions data must be an array.","messagePattern":"The permissions data must be an array\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Service/Permissions/PermissionsUpdatePermissionsService.php","lineNumber":78,"sourceCode":"     * @param \\App\\Utility\\UserAccessControl $uac The operator.\n     * @param string $aco The type of entity\n     * @param string $acoForeignkey The target entity id\n     * @param array|null $data The permissions to update\n     * @return \\App\\Model\\Dto\\EntitiesChangesDto\n     * @throws \\Cake\\Http\\Exception\\BadRequestException If the permissions passed\n     * @throws \\Exception If something unexpected occurred\n     */\n    public function updatePermissions(\n        UserAccessControl $uac,\n        string $aco,\n        string $acoForeignkey,\n        ?array $data = []\n    ): EntitiesChangesDto {\n        $entitiesChanges = new EntitiesChangesDto();\n\n        foreach ($data as $rowIndex => $row) {\n            if (!is_array($row)) {\n                throw new BadRequestException(__('The permissions data must be an array.'));\n            }\n            if (!is_int($rowIndex)) {\n                throw new BadRequestException(__('The permissions data array keys must be integers.'));\n            }\n            $permissionId = Hash::get($row, 'id', null);\n\n            // A new permission is provided when no id is found in the raw data.\n            if (is_null($permissionId)) {\n                $permission = $this->addPermission($uac, $rowIndex, $aco, $acoForeignkey, $row);\n                $entitiesChanges->pushAddedEntity($permission);\n            } else {\n                // If a property delete is found and set to true, then delete the permission.\n                // Otherwise update it.\n                $permission = $this->getPermission($rowIndex, $acoForeignkey, $permissionId);\n                $delete = Hash::get($row, 'delete');\n                if ($delete) {\n                    $permission = $this->deletePermission($permission);\n                    $entitiesChanges->pushDeletedEntity($permission);","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/src/Service/Permissions/PermissionsUpdatePermissionsService.php#L60-L96","documentation":"BadRequestException thrown by PermissionsUpdatePermissionsService::updatePermissions when an element of the $data array passed for bulk permission updates is not itself an array. Each row must be an associative array describing a permission change (id, type, delete flags).","triggerScenarios":"Calling updatePermissions($user, $resourceId, $changesIds, $data) where some entries of $data are scalars or null — e.g. JSON payload like {\"changes\": [\"abc\"]} or a flat list of ids instead of row objects.","commonSituations":"API clients sending a plain array of permission ids when the endpoint expects rows with id/type; malformed JSON where a row was serialized as a string; frontend sending null entries after filtering.","solutions":["Ensure every element of $data is an associative array, e.g. [{\"id\": \"uuid\", \"type\": 7}, ...].","Validate/normalize the payload client-side before sending (map rows to objects).","Catch BadRequestException and return a message clarifying the expected changes shape.","Filter out null/scalar entries before calling updatePermissions if the source list is untrusted."],"exampleFix":"// before\n$service->updatePermissions($uac, $resourceId, $changesIds, [$permId]); // scalar row\n\n// after\n$rows = array_map(fn ($id) => ['id' => $id, 'type' => PermissionsTable::OWNER], $permIds);\n$rows = array_filter($rows, 'is_array');\n$service->updatePermissions($uac, $resourceId, $changesIds, $rows);","handlingStrategy":"validation","validationCode":"// PHP\nforeach ($data as $row) {\n    if (!is_array($row)) {\n        throw new BadRequestException(__('Each permission change must be an object with id/type.'));\n    }\n}","typeGuard":"function isPermissionChangeRows(mixed $data): bool {\n    return is_array($data) && array_reduce($data, fn ($ok, $r) => $ok && is_array($r), true);\n}","tryCatchPattern":"try {\n    $dto = $service->updatePermissions($uac, $resourceId, $changesIds, $data);\n} catch (\\Cake\\Http\\Exception\\BadRequestException $e) {\n    return $this->respondWithError(400, __('Malformed permission changes payload: {0}', $e->getMessage()));\n}","preventionTips":["Send permission changes as an array of objects, never bare ids or strings.","Validate the JSON schema of the changes payload on the client before submit.","Coerce/sanitize entries (drop nulls/scalars) before calling updatePermissions."],"tags":["permissions","bad-request","input-validation","api"],"backgroundTag":"invalid-argument-value","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"}