mirror of
https://github.com/coollabsio/coolify.git
synced 2026-09-26 17:30:46 -04:00
fix(api): return invalid token for database import endpoints
Import upload/create/show now return invalidTokenResponse() when the access token has no team, matching the rest of the database API.
This commit is contained in:
@@ -35,29 +35,80 @@ class DatabasesController extends Controller
|
||||
use Concerns\HandlesDatabaseImportsApi;
|
||||
use Concerns\HandlesTagsApi;
|
||||
|
||||
#[OA\Post(path: '/databases/{uuid}/imports/uploads', operationId: 'upload-database-import', summary: 'Upload database import', security: [['bearerAuth' => []]], tags: ['Databases'], responses: [new OA\Response(response: 201, description: 'Upload completed'), new OA\Response(response: 422, ref: '#/components/responses/422')])]
|
||||
#[OA\Post(
|
||||
path: '/databases/{uuid}/imports/uploads',
|
||||
operationId: 'upload-database-import',
|
||||
summary: 'Upload database import',
|
||||
security: [['bearerAuth' => []]],
|
||||
tags: ['Databases'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'UUID of the database.', schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 201, description: 'Upload completed'),
|
||||
new OA\Response(response: 422, ref: '#/components/responses/422'),
|
||||
]
|
||||
)]
|
||||
public function upload_import(Request $request, string $uuid): JsonResponse
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
$database = $teamId === null ? null : queryDatabaseByUuidWithinTeam($uuid, $teamId);
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$database = queryDatabaseByUuidWithinTeam($uuid, $teamId);
|
||||
|
||||
return $database ? $this->uploadDatabaseImport($request, $database, $teamId) : response()->json(['message' => 'Database not found.'], 404);
|
||||
}
|
||||
|
||||
#[OA\Post(path: '/databases/{uuid}/imports', operationId: 'create-database-import', summary: 'Import database backup', requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/DatabaseImportRequest')), security: [['bearerAuth' => []]], tags: ['Databases'], responses: [new OA\Response(response: 202, description: 'Import queued'), new OA\Response(response: 409, description: 'Import already active'), new OA\Response(response: 422, ref: '#/components/responses/422')])]
|
||||
#[OA\Post(
|
||||
path: '/databases/{uuid}/imports',
|
||||
operationId: 'create-database-import',
|
||||
summary: 'Import database backup',
|
||||
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/DatabaseImportRequest')),
|
||||
security: [['bearerAuth' => []]],
|
||||
tags: ['Databases'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'UUID of the database.', schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 202, description: 'Import queued'),
|
||||
new OA\Response(response: 409, description: 'Import already active'),
|
||||
new OA\Response(response: 422, ref: '#/components/responses/422'),
|
||||
]
|
||||
)]
|
||||
public function create_import(Request $request, string $uuid): JsonResponse
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
$database = $teamId === null ? null : queryDatabaseByUuidWithinTeam($uuid, $teamId);
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$database = queryDatabaseByUuidWithinTeam($uuid, $teamId);
|
||||
|
||||
return $database ? $this->startDatabaseImport($request, $database, $teamId, 'api.databases.imports.show', ['uuid' => $uuid]) : response()->json(['message' => 'Database not found.'], 404);
|
||||
}
|
||||
|
||||
#[OA\Get(path: '/databases/{uuid}/imports/{activity_id}', operationId: 'get-database-import', summary: 'Get database import status', security: [['bearerAuth' => []]], tags: ['Databases'], responses: [new OA\Response(response: 200, description: 'Import status'), new OA\Response(response: 404, ref: '#/components/responses/404')])]
|
||||
#[OA\Get(
|
||||
path: '/databases/{uuid}/imports/{activity_id}',
|
||||
operationId: 'get-database-import',
|
||||
summary: 'Get database import status',
|
||||
security: [['bearerAuth' => []]],
|
||||
tags: ['Databases'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'UUID of the database.', schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'activity_id', in: 'path', required: true, description: 'Import activity ID.', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: 'Import status', content: new OA\JsonContent(ref: '#/components/schemas/DatabaseImportStatus')),
|
||||
new OA\Response(response: 404, ref: '#/components/responses/404'),
|
||||
]
|
||||
)]
|
||||
public function show_import(Request $request, string $uuid, int $activity_id): JsonResponse
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
$database = $teamId === null ? null : queryDatabaseByUuidWithinTeam($uuid, $teamId);
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$database = queryDatabaseByUuidWithinTeam($uuid, $teamId);
|
||||
|
||||
return $database ? $this->showDatabaseImport($database, $teamId, $activity_id) : response()->json(['message' => 'Database not found.'], 404);
|
||||
}
|
||||
|
||||
@@ -20,19 +20,64 @@ class ServiceDatabasesController extends Controller
|
||||
{
|
||||
use Concerns\HandlesDatabaseImportsApi;
|
||||
|
||||
#[OA\Post(path: '/services/{uuid}/databases/{database_uuid}/imports/uploads', operationId: 'upload-service-database-import', summary: 'Upload service database import', security: [['bearerAuth' => []]], tags: ['Service databases'], responses: [new OA\Response(response: 201, description: 'Upload completed'), new OA\Response(response: 422, ref: '#/components/responses/422')])]
|
||||
#[OA\Post(
|
||||
path: '/services/{uuid}/databases/{database_uuid}/imports/uploads',
|
||||
operationId: 'upload-service-database-import',
|
||||
summary: 'Upload service database import',
|
||||
security: [['bearerAuth' => []]],
|
||||
tags: ['Service databases'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', description: 'Service UUID.', required: true, schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'database_uuid', in: 'path', description: 'Service database UUID.', required: true, schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 201, description: 'Upload completed'),
|
||||
new OA\Response(response: 422, ref: '#/components/responses/422'),
|
||||
]
|
||||
)]
|
||||
public function upload_import(Request $request): JsonResponse
|
||||
{
|
||||
return $this->withImportDatabase($request, fn (ServiceDatabase $database, int $teamId) => $this->uploadDatabaseImport($request, $database, $teamId));
|
||||
}
|
||||
|
||||
#[OA\Post(path: '/services/{uuid}/databases/{database_uuid}/imports', operationId: 'create-service-database-import', summary: 'Import service database backup', requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/DatabaseImportRequest')), security: [['bearerAuth' => []]], tags: ['Service databases'], responses: [new OA\Response(response: 202, description: 'Import queued'), new OA\Response(response: 409, description: 'Import already active'), new OA\Response(response: 422, ref: '#/components/responses/422')])]
|
||||
#[OA\Post(
|
||||
path: '/services/{uuid}/databases/{database_uuid}/imports',
|
||||
operationId: 'create-service-database-import',
|
||||
summary: 'Import service database backup',
|
||||
requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/DatabaseImportRequest')),
|
||||
security: [['bearerAuth' => []]],
|
||||
tags: ['Service databases'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', description: 'Service UUID.', required: true, schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'database_uuid', in: 'path', description: 'Service database UUID.', required: true, schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 202, description: 'Import queued'),
|
||||
new OA\Response(response: 409, description: 'Import already active'),
|
||||
new OA\Response(response: 422, ref: '#/components/responses/422'),
|
||||
]
|
||||
)]
|
||||
public function create_import(Request $request): JsonResponse
|
||||
{
|
||||
return $this->withImportDatabase($request, fn (ServiceDatabase $database, int $teamId) => $this->startDatabaseImport($request, $database, $teamId, 'api.service-databases.imports.show', ['uuid' => $request->route('uuid'), 'database_uuid' => $database->uuid]));
|
||||
}
|
||||
|
||||
#[OA\Get(path: '/services/{uuid}/databases/{database_uuid}/imports/{activity_id}', operationId: 'get-service-database-import', summary: 'Get service database import status', security: [['bearerAuth' => []]], tags: ['Service databases'], responses: [new OA\Response(response: 200, description: 'Import status'), new OA\Response(response: 404, ref: '#/components/responses/404')])]
|
||||
#[OA\Get(
|
||||
path: '/services/{uuid}/databases/{database_uuid}/imports/{activity_id}',
|
||||
operationId: 'get-service-database-import',
|
||||
summary: 'Get service database import status',
|
||||
security: [['bearerAuth' => []]],
|
||||
tags: ['Service databases'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', description: 'Service UUID.', required: true, schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'database_uuid', in: 'path', description: 'Service database UUID.', required: true, schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'activity_id', in: 'path', description: 'Import activity ID.', required: true, schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: 'Import status', content: new OA\JsonContent(ref: '#/components/schemas/DatabaseImportStatus')),
|
||||
new OA\Response(response: 404, ref: '#/components/responses/404'),
|
||||
]
|
||||
)]
|
||||
public function show_import(Request $request): JsonResponse
|
||||
{
|
||||
return $this->withImportDatabase($request, fn (ServiceDatabase $database, int $teamId) => $this->showDatabaseImport($database, $teamId, (int) $request->route('activity_id')));
|
||||
@@ -41,7 +86,11 @@ class ServiceDatabasesController extends Controller
|
||||
private function withImportDatabase(Request $request, callable $callback): JsonResponse
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
$service = $teamId === null ? null : $this->resolveService($request, $teamId);
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
|
||||
$service = $this->resolveService($request, $teamId);
|
||||
$database = $service ? $this->resolveServiceDatabase($request, $service) : null;
|
||||
|
||||
return $database ? $callback($database, $teamId) : response()->json(['message' => 'Service database not found.'], 404);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
use App\Actions\Database\StartDatabaseImport;
|
||||
use App\Http\Middleware\ApiAbility;
|
||||
use App\Http\Middleware\EnsureTokenBelongsToCurrentTeamMember;
|
||||
use App\Models\AuditEvent;
|
||||
use App\Models\Environment;
|
||||
use App\Models\InstanceSettings;
|
||||
@@ -131,3 +133,32 @@ test('returns only a team and resource scoped import activity', function () {
|
||||
$activity->save();
|
||||
$this->withHeaders($this->headers)->getJson("/api/v1/databases/{$database->uuid}/imports/{$activity->id}")->assertNotFound();
|
||||
});
|
||||
|
||||
test('returns invalid token when the access token team is not a member team', function (string $method, string $path) {
|
||||
$this->withoutMiddleware([
|
||||
EnsureTokenBelongsToCurrentTeamMember::class,
|
||||
ApiAbility::class,
|
||||
]);
|
||||
|
||||
$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()]);
|
||||
$foreignTeam = Team::factory()->create();
|
||||
$plainTextToken = 'no-team';
|
||||
$token = $this->user->tokens()->create([
|
||||
'name' => 'imports-foreign-team',
|
||||
'token' => hash('sha256', $plainTextToken),
|
||||
'abilities' => ['deploy', 'read'],
|
||||
'team_id' => $foreignTeam->id,
|
||||
]);
|
||||
|
||||
$this->withHeaders(['Authorization' => 'Bearer '.$token->id.'|'.$plainTextToken])
|
||||
->{$method}(sprintf($path, $database->uuid))
|
||||
->assertBadRequest()
|
||||
->assertJson([
|
||||
'message' => 'Invalid token.',
|
||||
'docs' => 'https://coolify.io/docs/api-reference/authorization',
|
||||
]);
|
||||
})->with([
|
||||
'upload' => ['postJson', '/api/v1/databases/%s/imports/uploads'],
|
||||
'create' => ['postJson', '/api/v1/databases/%s/imports'],
|
||||
'show' => ['getJson', '/api/v1/databases/%s/imports/1'],
|
||||
]);
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Middleware\ApiAbility;
|
||||
use App\Http\Middleware\EnsureTokenBelongsToCurrentTeamMember;
|
||||
use App\Models\Environment;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
@@ -26,9 +30,6 @@ beforeEach(function () {
|
||||
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
|
||||
});
|
||||
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceDatabase;
|
||||
|
||||
test('validates service database imports and binds database to service', function () {
|
||||
$service = Service::factory()->create(['environment_id' => $this->environment->id, 'server_id' => $this->server->id, 'destination_id' => $this->destination->id, 'destination_type' => $this->destination->getMorphClass(), 'docker_compose_raw' => "services:\n postgres:\n image: postgres:17\n"]);
|
||||
$database = ServiceDatabase::create(['uuid' => (string) Str::uuid(), 'name' => 'postgres', 'service_id' => $service->id, 'image' => 'postgres:17']);
|
||||
@@ -40,3 +41,33 @@ test('validates service database imports and binds database to service', functio
|
||||
$otherService = Service::factory()->create(['environment_id' => $this->environment->id, 'server_id' => $this->server->id, 'destination_id' => $this->destination->id, 'destination_type' => $this->destination->getMorphClass(), 'docker_compose_raw' => "services: {}\n"]);
|
||||
$this->withHeaders($this->headers)->postJson("/api/v1/services/{$otherService->uuid}/databases/{$database->uuid}/imports", ['source' => 'server', 'path' => '/tmp/a.sql'])->assertNotFound();
|
||||
});
|
||||
|
||||
test('returns invalid token when the access token team is not a member team', function (string $method, string $suffix) {
|
||||
$this->withoutMiddleware([
|
||||
EnsureTokenBelongsToCurrentTeamMember::class,
|
||||
ApiAbility::class,
|
||||
]);
|
||||
|
||||
$service = Service::factory()->create(['environment_id' => $this->environment->id, 'server_id' => $this->server->id, 'destination_id' => $this->destination->id, 'destination_type' => $this->destination->getMorphClass(), 'docker_compose_raw' => "services:\n postgres:\n image: postgres:17\n"]);
|
||||
$database = ServiceDatabase::create(['uuid' => (string) Str::uuid(), 'name' => 'postgres', 'service_id' => $service->id, 'image' => 'postgres:17']);
|
||||
$foreignTeam = Team::factory()->create();
|
||||
$plainTextToken = 'no-team';
|
||||
$token = $this->user->tokens()->create([
|
||||
'name' => 'imports-foreign-team',
|
||||
'token' => hash('sha256', $plainTextToken),
|
||||
'abilities' => ['deploy', 'read'],
|
||||
'team_id' => $foreignTeam->id,
|
||||
]);
|
||||
|
||||
$this->withHeaders(['Authorization' => 'Bearer '.$token->id.'|'.$plainTextToken])
|
||||
->{$method}("/api/v1/services/{$service->uuid}/databases/{$database->uuid}/{$suffix}")
|
||||
->assertBadRequest()
|
||||
->assertJson([
|
||||
'message' => 'Invalid token.',
|
||||
'docs' => 'https://coolify.io/docs/api-reference/authorization',
|
||||
]);
|
||||
})->with([
|
||||
'upload' => ['postJson', 'imports/uploads'],
|
||||
'create' => ['postJson', 'imports'],
|
||||
'show' => ['getJson', 'imports/1'],
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user