From 6872f63f5f43192a7d9fe6d68730130e05ad4595 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 7 Jul 2026 21:20:41 +0200 Subject: [PATCH] fix(api): audit moved resources --- bootstrap/helpers/api.php | 21 +++++++++++++--- tests/Feature/MoveResourceApiTest.php | 35 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php index dccfec4af..e430e7364 100644 --- a/bootstrap/helpers/api.php +++ b/bootstrap/helpers/api.php @@ -3,12 +3,15 @@ use App\Enums\BuildPackTypes; use App\Enums\RedirectTypes; use App\Enums\StaticImageTypes; +use App\Models\Environment; use App\Rules\ValidGitBranch; use App\Support\ValidationPatterns; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Gate; +use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; function getTeamIdFromToken() @@ -173,10 +176,10 @@ function sharedDataApplications() ]; } -function moveResourceToEnvironment(Request $request, $resource, string $resourceType, int $teamId): \Illuminate\Http\JsonResponse +function moveResourceToEnvironment(Request $request, $resource, string $resourceType, int $teamId): JsonResponse { - $validator = \Illuminate\Support\Facades\Validator::make($request->all(), [ + $validator = Validator::make($request->all(), [ 'environment_uuid' => 'required|string', ]); @@ -195,7 +198,7 @@ function moveResourceToEnvironment(Request $request, $resource, string $resource ], 422); } - $newEnvironment = \App\Models\Environment::ownedByCurrentTeamAPI($teamId) + $newEnvironment = Environment::ownedByCurrentTeamAPI($teamId) ->whereUuid($request->environment_uuid) ->first(); @@ -209,8 +212,20 @@ function moveResourceToEnvironment(Request $request, $resource, string $resource return response()->json(['message' => "$resourceType is already in this environment."], 400); } + $oldEnvironment = $resource->environment()->with('project')->first(); + $resource->update(['environment_id' => $newEnvironment->id]); + auditLog('api.'.str($resourceType)->lower()->value().'.moved', [ + 'team_id' => $teamId, + 'resource_uuid' => $resource->uuid, + 'resource_type' => str($resourceType)->lower()->value(), + 'from_project_uuid' => $oldEnvironment?->project?->uuid, + 'from_environment_uuid' => $oldEnvironment?->uuid, + 'to_project_uuid' => $newEnvironment->project->uuid, + 'to_environment_uuid' => $newEnvironment->uuid, + ]); + return response()->json([ 'message' => "$resourceType moved successfully.", 'uuid' => $resource->uuid, diff --git a/tests/Feature/MoveResourceApiTest.php b/tests/Feature/MoveResourceApiTest.php index bee517a54..29c868216 100644 --- a/tests/Feature/MoveResourceApiTest.php +++ b/tests/Feature/MoveResourceApiTest.php @@ -15,6 +15,7 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Http\Request; use Illuminate\Support\Facades\Gate; +use Illuminate\Support\Facades\Log; uses(RefreshDatabase::class); @@ -62,6 +63,40 @@ expect($application->environment_id)->toBe($this->targetEnvironment->id); }); + test('writes audit log when application is moved', function () { + $application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $auditChannel = Mockery::mock(); + $auditChannel->shouldReceive('info') + ->once() + ->with('api.application.moved', Mockery::on(function (array $context) use ($application) { + return $context['event'] === 'api.application.moved' + && $context['resource_uuid'] === $application->uuid + && $context['resource_type'] === 'application' + && $context['from_environment_uuid'] === $this->environment->uuid + && $context['to_environment_uuid'] === $this->targetEnvironment->uuid + && $context['from_project_uuid'] === $this->project->uuid + && $context['to_project_uuid'] === $this->targetProject->uuid; + })); + + Log::shouldReceive('channel')->with('audit')->andReturn($auditChannel); + Log::shouldReceive('warning')->andReturnNull(); + + $this->actingAs($this->user); + + $request = Request::create('/', 'POST', [ + 'environment_uuid' => $this->targetEnvironment->uuid, + ]); + + $response = moveResourceToEnvironment($request, $application, 'Application', $this->team->id); + + expect($response->getStatusCode())->toBe(200); + }); + test('returns 404 when application not found', function () { $response = $this->withHeaders([ 'Authorization' => 'Bearer '.$this->bearerToken,