diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index da94521a8..2ef95ce8b 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -290,7 +290,11 @@ public function domains_by_server(Request $request) if (is_null($teamId)) { return invalidTokenResponse(); } - $uuid = $request->get('uuid'); + $server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first(); + if (is_null($server)) { + return response()->json(['message' => 'Server not found.'], 404); + } + $uuid = $request->query('uuid'); if ($uuid) { $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $uuid)->first(); if (! $application) { @@ -301,7 +305,9 @@ public function domains_by_server(Request $request) } $projects = Project::where('team_id', $teamId)->get(); $domains = collect(); - $applications = $projects->pluck('applications')->flatten(); + $applications = $projects->pluck('applications')->flatten()->filter(function ($application) use ($server) { + return $application->destination?->server?->id === $server->id; + }); $settings = instanceSettings(); if ($applications->count() > 0) { foreach ($applications as $application) { @@ -341,7 +347,9 @@ public function domains_by_server(Request $request) } } } - $services = $projects->pluck('services')->flatten(); + $services = $projects->pluck('services')->flatten()->filter(function ($service) use ($server) { + return $service->server_id === $server->id; + }); if ($services->count() > 0) { foreach ($services as $service) { $service_applications = $service->applications; @@ -354,7 +362,8 @@ public function domains_by_server(Request $request) })->filter(function (Stringable $fqdn) { return $fqdn->isNotEmpty(); }); - if ($ip === 'host.docker.internal') { + $serviceIp = $server->ip; + if ($serviceIp === 'host.docker.internal') { if ($settings->public_ipv4) { $domains->push([ 'domain' => $fqdn, @@ -370,13 +379,13 @@ public function domains_by_server(Request $request) if (! $settings->public_ipv4 && ! $settings->public_ipv6) { $domains->push([ 'domain' => $fqdn, - 'ip' => $ip, + 'ip' => $serviceIp, ]); } } else { $domains->push([ 'domain' => $fqdn, - 'ip' => $ip, + 'ip' => $serviceIp, ]); } } diff --git a/app/Livewire/ActivityMonitor.php b/app/Livewire/ActivityMonitor.php index 370ff1eaa..85ba60c33 100644 --- a/app/Livewire/ActivityMonitor.php +++ b/app/Livewire/ActivityMonitor.php @@ -55,7 +55,18 @@ public function hydrateActivity() return; } - $this->activity = Activity::find($this->activityId); + $activity = Activity::find($this->activityId); + + if ($activity) { + $teamId = data_get($activity, 'properties.team_id'); + if ($teamId && $teamId !== currentTeam()?->id) { + $this->activity = null; + + return; + } + } + + $this->activity = $activity; } public function updatedActivityId($value) diff --git a/tests/Feature/ActivityMonitorCrossTeamTest.php b/tests/Feature/ActivityMonitorCrossTeamTest.php new file mode 100644 index 000000000..7e4aebc2f --- /dev/null +++ b/tests/Feature/ActivityMonitorCrossTeamTest.php @@ -0,0 +1,67 @@ +team = Team::factory()->create(); + $this->user = User::factory()->create(); + $this->team->members()->attach($this->user->id, ['role' => 'owner']); + + $this->otherTeam = Team::factory()->create(); +}); + +test('hydrateActivity blocks access to another teams activity', function () { + $otherActivity = Activity::create([ + 'log_name' => 'default', + 'description' => 'test activity', + 'properties' => ['team_id' => $this->otherTeam->id], + ]); + + $this->actingAs($this->user); + session(['currentTeam' => ['id' => $this->team->id]]); + + $component = Livewire::test(ActivityMonitor::class) + ->set('activityId', $otherActivity->id) + ->assertSet('activity', null); +}); + +test('hydrateActivity allows access to own teams activity', function () { + $ownActivity = Activity::create([ + 'log_name' => 'default', + 'description' => 'test activity', + 'properties' => ['team_id' => $this->team->id], + ]); + + $this->actingAs($this->user); + session(['currentTeam' => ['id' => $this->team->id]]); + + $component = Livewire::test(ActivityMonitor::class) + ->set('activityId', $ownActivity->id); + + expect($component->get('activity'))->not->toBeNull(); + expect($component->get('activity')->id)->toBe($ownActivity->id); +}); + +test('hydrateActivity allows access to activity without team_id in properties', function () { + $legacyActivity = Activity::create([ + 'log_name' => 'default', + 'description' => 'legacy activity', + 'properties' => [], + ]); + + $this->actingAs($this->user); + session(['currentTeam' => ['id' => $this->team->id]]); + + $component = Livewire::test(ActivityMonitor::class) + ->set('activityId', $legacyActivity->id); + + expect($component->get('activity'))->not->toBeNull(); + expect($component->get('activity')->id)->toBe($legacyActivity->id); +}); diff --git a/tests/Feature/DomainsByServerApiTest.php b/tests/Feature/DomainsByServerApiTest.php index 1e799bec5..ea799275b 100644 --- a/tests/Feature/DomainsByServerApiTest.php +++ b/tests/Feature/DomainsByServerApiTest.php @@ -16,11 +16,12 @@ $this->user = User::factory()->create(); $this->team->members()->attach($this->user->id, ['role' => 'owner']); - $this->token = $this->user->createToken('test-token', ['*'], $this->team->id); + session(['currentTeam' => $this->team]); + $this->token = $this->user->createToken('test-token', ['*']); $this->bearerToken = $this->token->plainTextToken; $this->server = Server::factory()->create(['team_id' => $this->team->id]); - $this->destination = StandaloneDocker::factory()->create(['server_id' => $this->server->id]); + $this->destination = StandaloneDocker::where('server_id', $this->server->id)->first(); $this->project = Project::factory()->create(['team_id' => $this->team->id]); $this->environment = Environment::factory()->create(['project_id' => $this->project->id]); }); @@ -53,7 +54,7 @@ function authHeaders(): array $otherTeam->members()->attach($otherUser->id, ['role' => 'owner']); $otherServer = Server::factory()->create(['team_id' => $otherTeam->id]); - $otherDestination = StandaloneDocker::factory()->create(['server_id' => $otherServer->id]); + $otherDestination = StandaloneDocker::where('server_id', $otherServer->id)->first(); $otherProject = Project::factory()->create(['team_id' => $otherTeam->id]); $otherEnvironment = Environment::factory()->create(['project_id' => $otherProject->id]); @@ -78,3 +79,45 @@ function authHeaders(): array $response->assertNotFound(); $response->assertJson(['message' => 'Application not found.']); }); + +test('returns 404 when server uuid belongs to another team', function () { + $otherTeam = Team::factory()->create(); + $otherUser = User::factory()->create(); + $otherTeam->members()->attach($otherUser->id, ['role' => 'owner']); + + $otherServer = Server::factory()->create(['team_id' => $otherTeam->id]); + + $response = $this->withHeaders(authHeaders()) + ->getJson("/api/v1/servers/{$otherServer->uuid}/domains"); + + $response->assertNotFound(); + $response->assertJson(['message' => 'Server not found.']); +}); + +test('only returns domains for applications on the specified server', function () { + $application = Application::factory()->create([ + 'fqdn' => 'https://app-on-server.example.com', + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $otherServer = Server::factory()->create(['team_id' => $this->team->id]); + $otherDestination = StandaloneDocker::where('server_id', $otherServer->id)->first(); + + $applicationOnOtherServer = Application::factory()->create([ + 'fqdn' => 'https://app-on-other-server.example.com', + 'environment_id' => $this->environment->id, + 'destination_id' => $otherDestination->id, + 'destination_type' => $otherDestination->getMorphClass(), + ]); + + $response = $this->withHeaders(authHeaders()) + ->getJson("/api/v1/servers/{$this->server->uuid}/domains"); + + $response->assertOk(); + $responseContent = $response->json(); + $allDomains = collect($responseContent)->pluck('domains')->flatten()->toArray(); + expect($allDomains)->toContain('app-on-server.example.com'); + expect($allDomains)->not->toContain('app-on-other-server.example.com'); +});