passbolt/passbolt_api · error · BadRequestException
The permissions data must be an array.
Error message
The permissions data must be an array.
What it means
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).
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.
Example fix
// before $service->updatePermissions($uac, $resourceId, $changesIds, [$permId]); // scalar row // after $rows = array_map(fn ($id) => ['id' => $id, 'type' => PermissionsTable::OWNER], $permIds); $rows = array_filter($rows, 'is_array'); $service->updatePermissions($uac, $resourceId, $changesIds, $rows);
Defensive patterns
Strategy: validation
Validate before calling
// PHP
foreach ($data as $row) {
if (!is_array($row)) {
throw new BadRequestException(__('Each permission change must be an object with id/type.'));
}
} Type guard
function isPermissionChangeRows(mixed $data): bool {
return is_array($data) && array_reduce($data, fn ($ok, $r) => $ok && is_array($r), true);
} Try / catch
try {
$dto = $service->updatePermissions($uac, $resourceId, $changesIds, $data);
} catch (\Cake\Http\Exception\BadRequestException $e) {
return $this->respondWithError(400, __('Malformed permission changes payload: {0}', $e->getMessage()));
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- The identifier should be a valid UUID.
- Account recovery case must be a string.
- Few fields are missing for the V5.
- Invalid request. Keys are required for this change.
- Invalid request. New key or passwords are not required.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/1e02dc496fd8f57a.
Report an issue: GitHub.
Appendix: source
Thrown at src/Service/Permissions/PermissionsUpdatePermissionsService.php:78
* @param \App\Utility\UserAccessControl $uac The operator.
* @param string $aco The type of entity
* @param string $acoForeignkey The target entity id
* @param array|null $data The permissions to update
* @return \App\Model\Dto\EntitiesChangesDto
* @throws \Cake\Http\Exception\BadRequestException If the permissions passed
* @throws \Exception If something unexpected occurred
*/
public function updatePermissions(
UserAccessControl $uac,
string $aco,
string $acoForeignkey,
?array $data = []
): EntitiesChangesDto {
$entitiesChanges = new EntitiesChangesDto();
foreach ($data as $rowIndex => $row) {
if (!is_array($row)) {
throw new BadRequestException(__('The permissions data must be an array.'));
}
if (!is_int($rowIndex)) {
throw new BadRequestException(__('The permissions data array keys must be integers.'));
}
$permissionId = Hash::get($row, 'id', null);
// A new permission is provided when no id is found in the raw data.
if (is_null($permissionId)) {
$permission = $this->addPermission($uac, $rowIndex, $aco, $acoForeignkey, $row);
$entitiesChanges->pushAddedEntity($permission);
} else {
// If a property delete is found and set to true, then delete the permission.
// Otherwise update it.
$permission = $this->getPermission($rowIndex, $acoForeignkey, $permissionId);
$delete = Hash::get($row, 'delete');
if ($delete) {
$permission = $this->deletePermission($permission);
$entitiesChanges->pushDeletedEntity($permission);View on GitHub (pinned to 31c1bbc10f)