fix(api): reject unknown fields on database import

Run validator fails() before adding extra-field errors so Laravel does not replace the message bag and accept undocumented properties.
This commit is contained in:
Andras Bacsai
2026-09-09 13:06:18 +02:00
parent 903b5d3a0d
commit 25e61deefd
2 changed files with 25 additions and 5 deletions
@@ -70,11 +70,14 @@ trait HandlesDatabaseImportsApi
'dump_all' => ['sometimes', 'boolean'],
'replace_existing' => ['sometimes', 'boolean'],
]);
foreach (array_diff(array_keys($payload), $allowed) as $field) {
$validator->errors()->add($field, 'This field is not allowed.');
}
if ($validator->fails() || $validator->errors()->isNotEmpty()) {
return response()->json(['message' => 'Validation failed.', 'errors' => $validator->errors()], 422);
$extraFields = array_diff(array_keys($payload), $allowed);
if ($validator->fails() || ! empty($extraFields)) {
$errors = $validator->errors();
foreach ($extraFields as $field) {
$errors->add($field, 'This field is not allowed.');
}
return response()->json(['message' => 'Validation failed.', 'errors' => $errors], 422);
}
try {
@@ -102,6 +102,23 @@ test('validates replace existing as a boolean', function () {
->assertJsonValidationErrors('replace_existing');
});
test('rejects unknown fields on standalone import', function () {
$database = StandalonePostgresql::create(['uuid' => (string) Str::uuid(), 'name' => 'db', 'postgres_user' => 'postgres', 'postgres_password' => 'password', 'postgres_db' => 'db', 'image' => 'postgres:17', 'status' => 'running', 'environment_id' => $this->environment->id, 'destination_id' => $this->destination->id, 'destination_type' => $this->destination->getMorphClass()]);
$activity = Activity::create(['log_name' => 'default', 'description' => 'queued', 'properties' => ['status' => 'queued']]);
$action = Mockery::mock(StartDatabaseImport::class);
$action->shouldReceive('handle')->andReturn($activity);
app()->instance(StartDatabaseImport::class, $action);
$this->withHeaders($this->headers)
->postJson("/api/v1/databases/{$database->uuid}/imports", [
'source' => 'server',
'path' => '/tmp/backup.sql',
'unknown_field' => 'nope',
])
->assertUnprocessable()
->assertJsonPath('errors.unknown_field.0', 'This field is not allowed.');
});
test('returns only a team and resource scoped import activity', function () {
$database = StandalonePostgresql::create(['uuid' => (string) Str::uuid(), 'name' => 'db', 'postgres_user' => 'postgres', 'postgres_password' => 'password', 'postgres_db' => 'db', 'image' => 'postgres:17', 'status' => 'running', 'environment_id' => $this->environment->id, 'destination_id' => $this->destination->id, 'destination_type' => $this->destination->getMorphClass()]);
$activity = Activity::create(['log_name' => 'default', 'description' => json_encode([['order' => 1, 'output' => 'restored', 'type' => 'stdout']]), 'properties' => ['team_id' => $this->team->id, 'type_uuid' => $database->uuid, 'operation' => 'database_import', 'status' => 'finished', 'exitCode' => 0]]);