Align component data with resource permissions

This commit is contained in:
Andras Bacsai
2026-09-23 22:50:37 +02:00
parent 39cc12b780
commit 87b7c4ba98
5 changed files with 157 additions and 7 deletions
+3 -1
View File
@@ -426,7 +426,9 @@ class General extends Component
$this->customNginxConfiguration = $this->application->custom_nginx_configuration;
$this->isHttpBasicAuthEnabled = $this->application->is_http_basic_auth_enabled;
$this->httpBasicAuthUsername = $this->application->http_basic_auth_username;
$this->httpBasicAuthPassword = $this->application->http_basic_auth_password;
$this->httpBasicAuthPassword = auth()->user()->can('update', $this->application)
? $this->application->http_basic_auth_password
: null;
$this->watchPaths = $this->application->watch_paths;
$this->redirect = $this->application->redirect;
+12 -4
View File
@@ -60,7 +60,9 @@ class LogDrains extends Component
$this->server->settings->logdrain_newrelic_base_uri = $this->logDrainNewRelicBaseUri;
} else {
$this->isLogDrainNewRelicEnabled = $this->server->settings->is_logdrain_newrelic_enabled;
$this->logDrainNewRelicLicenseKey = $this->server->settings->logdrain_newrelic_license_key;
$this->logDrainNewRelicLicenseKey = auth()->user()->can('update', $this->server)
? $this->server->settings->logdrain_newrelic_license_key
: null;
$this->logDrainNewRelicBaseUri = $this->server->settings->logdrain_newrelic_base_uri;
}
}
@@ -74,7 +76,9 @@ class LogDrains extends Component
} else {
$this->isLogDrainAxiomEnabled = $this->server->settings->is_logdrain_axiom_enabled;
$this->logDrainAxiomDatasetName = $this->server->settings->logdrain_axiom_dataset_name;
$this->logDrainAxiomApiKey = $this->server->settings->logdrain_axiom_api_key;
$this->logDrainAxiomApiKey = auth()->user()->can('update', $this->server)
? $this->server->settings->logdrain_axiom_api_key
: null;
}
}
@@ -86,8 +90,12 @@ class LogDrains extends Component
$this->server->settings->logdrain_custom_config_parser = $this->logDrainCustomConfigParser;
} else {
$this->isLogDrainCustomEnabled = $this->server->settings->is_logdrain_custom_enabled;
$this->logDrainCustomConfig = $this->server->settings->logdrain_custom_config;
$this->logDrainCustomConfigParser = $this->server->settings->logdrain_custom_config_parser;
$this->logDrainCustomConfig = auth()->user()->can('update', $this->server)
? $this->server->settings->logdrain_custom_config
: null;
$this->logDrainCustomConfigParser = auth()->user()->can('update', $this->server)
? $this->server->settings->logdrain_custom_config_parser
: null;
}
}
+1
View File
@@ -43,6 +43,7 @@ class Sentinel extends Component
public function mount()
{
$this->authorize('viewSentinel', $this->server);
$this->syncData();
}
+6 -2
View File
@@ -286,11 +286,15 @@ class Show extends Component
$this->isSwarmWorker = $this->server->settings->is_swarm_worker;
$this->serverRole = $this->server->settings->effectiveServerRole()->value;
$this->isMetricsEnabled = $this->server->settings->is_metrics_enabled;
$this->sentinelToken = $this->server->settings->sentinel_token;
$this->sentinelToken = auth()->user()->can('update', $this->server)
? $this->server->settings->sentinel_token
: '';
$this->sentinelMetricsRefreshRateSeconds = $this->server->settings->sentinel_metrics_refresh_rate_seconds;
$this->sentinelMetricsHistoryDays = $this->server->settings->sentinel_metrics_history_days;
$this->sentinelPushIntervalSeconds = $this->server->settings->sentinel_push_interval_seconds;
$this->sentinelCustomUrl = $this->server->settings->sentinel_custom_url;
$this->sentinelCustomUrl = auth()->user()->can('update', $this->server)
? $this->server->settings->sentinel_custom_url
: null;
$this->isSentinelDebugEnabled = $this->server->settings->is_sentinel_debug_enabled;
$this->sentinelUpdatedAt = $this->server->sentinel_updated_at;
$this->serverTimezone = $this->server->settings->server_timezone;
@@ -0,0 +1,135 @@
<?php
use App\Livewire\Project\Application\General;
use App\Livewire\Server\LogDrains;
use App\Livewire\Server\Sentinel;
use App\Livewire\Server\Show;
use App\Models\Application;
use App\Models\Environment;
use App\Models\InstanceSettings;
use App\Models\Project;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
beforeEach(function () {
InstanceSettings::unguarded(fn () => InstanceSettings::firstOrCreate(['id' => 0]));
$this->team = Team::factory()->create(['show_boarding' => false]);
$this->member = User::factory()->create();
$this->admin = User::factory()->create();
$this->team->members()->attach($this->member, ['role' => 'member']);
$this->team->members()->attach($this->admin, ['role' => 'admin']);
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
$this->server->settings->updateQuietly([
'sentinel_token' => 'SENTINEL-MARKER-SECRET',
'sentinel_custom_url' => 'https://sentinel.example.test/MARKER-SECRET',
'logdrain_newrelic_license_key' => 'NEWRELIC-MARKER-SECRET',
'logdrain_axiom_api_key' => 'AXIOM-MARKER-SECRET',
'logdrain_custom_config' => 'CUSTOM-CONFIG-MARKER-SECRET',
'logdrain_custom_config_parser' => 'CUSTOM-PARSER-MARKER-SECRET',
]);
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
$destination = StandaloneDocker::query()->where('server_id', $this->server->id)->firstOrFail();
$this->application = Application::factory()->create([
'environment_id' => $this->environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
'http_basic_auth_password' => 'BASIC-MARKER-SECRET',
'is_http_basic_auth_enabled' => true,
'http_basic_auth_username' => 'admin',
'redirect' => 'no',
'static_image' => 'nginx:alpine',
'base_directory' => '/',
'build_pack' => 'nixpacks',
]);
});
function useSecretExposureUser(User $user, Team $team): void
{
test()->actingAs($user);
session(['currentTeam' => $team]);
}
test('server overview redacts the Sentinel token from a member snapshot', function () {
useSecretExposureUser($this->member, $this->team);
Livewire::test(Show::class, ['server_uuid' => $this->server->uuid])
->assertSuccessful()->assertSet('sentinelToken', '')->assertSet('sentinelCustomUrl', null)
->call('refresh')->assertSet('sentinelToken', '')->assertSet('sentinelCustomUrl', null);
$this->get(route('server.show', ['server_uuid' => $this->server->uuid]))
->assertSuccessful()->assertDontSee('SENTINEL-MARKER-SECRET')->assertDontSee('sentinel.example.test/MARKER-SECRET');
useSecretExposureUser($this->admin, $this->team);
Livewire::test(Show::class, ['server_uuid' => $this->server->uuid])
->assertSuccessful()->assertSet('sentinelToken', 'SENTINEL-MARKER-SECRET')
->assertSet('sentinelCustomUrl', 'https://sentinel.example.test/MARKER-SECRET');
});
test('log drain page redacts provider keys from a member snapshot', function () {
useSecretExposureUser($this->member, $this->team);
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
->assertSuccessful()->assertSet('logDrainNewRelicLicenseKey', null)->assertSet('logDrainAxiomApiKey', null)
->assertSet('logDrainCustomConfig', null)->assertSet('logDrainCustomConfigParser', null);
$this->get(route('server.log-drains', ['server_uuid' => $this->server->uuid]))
->assertSuccessful()->assertDontSee('NEWRELIC-MARKER-SECRET')->assertDontSee('AXIOM-MARKER-SECRET')
->assertDontSee('CUSTOM-CONFIG-MARKER-SECRET')->assertDontSee('CUSTOM-PARSER-MARKER-SECRET');
useSecretExposureUser($this->admin, $this->team);
Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid])
->assertSuccessful()->assertSet('logDrainNewRelicLicenseKey', 'NEWRELIC-MARKER-SECRET')
->assertSet('logDrainAxiomApiKey', 'AXIOM-MARKER-SECRET')
->assertSet('logDrainCustomConfig', 'CUSTOM-CONFIG-MARKER-SECRET')
->assertSet('logDrainCustomConfigParser', 'CUSTOM-PARSER-MARKER-SECRET');
});
test('application page redacts HTTP basic password from a member snapshot', function () {
useSecretExposureUser($this->member, $this->team);
Livewire::test(General::class, ['application' => $this->application])
->assertSuccessful()->assertSet('httpBasicAuthPassword', null);
$this->get(route('project.application.configuration', [
'project_uuid' => $this->project->uuid,
'environment_uuid' => $this->environment->uuid,
'application_uuid' => $this->application->uuid,
]))->assertSuccessful()->assertDontSee('BASIC-MARKER-SECRET');
useSecretExposureUser($this->admin, $this->team);
Livewire::test(General::class, ['application' => $this->application])
->assertSuccessful()->assertSet('httpBasicAuthPassword', 'BASIC-MARKER-SECRET');
});
test('the dedicated Sentinel page denies members and cross-team users', function () {
useSecretExposureUser($this->member, $this->team);
$this->get(route('server.sentinel', ['server_uuid' => $this->server->uuid]))->assertForbidden();
Livewire::test(Sentinel::class, ['server' => $this->server])->assertForbidden();
useSecretExposureUser($this->admin, $this->team);
$this->get(route('server.sentinel', ['server_uuid' => $this->server->uuid]))->assertSuccessful();
Livewire::test(Sentinel::class, ['server' => $this->server])
->assertSuccessful()->assertSet('sentinelToken', 'SENTINEL-MARKER-SECRET');
$outsider = User::factory()->create();
$outsideTeam = $outsider->teams()->first();
$outsideTeam->updateQuietly(['show_boarding' => false]);
useSecretExposureUser($outsider, $outsideTeam);
$this->get(route('server.show', ['server_uuid' => $this->server->uuid]))->assertNotFound();
$this->get(route('server.log-drains', ['server_uuid' => $this->server->uuid]))->assertNotFound();
$this->get(route('server.sentinel', ['server_uuid' => $this->server->uuid]))->assertNotFound();
$this->get(route('project.application.configuration', [
'project_uuid' => $this->project->uuid,
'environment_uuid' => $this->environment->uuid,
'application_uuid' => $this->application->uuid,
]))->assertNotFound();
});
test('model serialization keeps the three secrets hidden', function () {
expect(json_encode($this->server->toArray()))
->not->toContain('SENTINEL-MARKER-SECRET', 'NEWRELIC-MARKER-SECRET', 'AXIOM-MARKER-SECRET', 'CUSTOM-CONFIG-MARKER-SECRET', 'CUSTOM-PARSER-MARKER-SECRET', 'sentinel.example.test/MARKER-SECRET');
expect(json_encode($this->server->settings->toArray()))
->not->toContain('SENTINEL-MARKER-SECRET', 'NEWRELIC-MARKER-SECRET', 'AXIOM-MARKER-SECRET', 'CUSTOM-CONFIG-MARKER-SECRET', 'CUSTOM-PARSER-MARKER-SECRET', 'sentinel.example.test/MARKER-SECRET');
expect(json_encode($this->application->toArray()))->not->toContain('BASIC-MARKER-SECRET');
});