diff --git a/app/Livewire/Project/Service/StackForm.php b/app/Livewire/Project/Service/StackForm.php index cc6a994b36..d410ffeda1 100644 --- a/app/Livewire/Project/Service/StackForm.php +++ b/app/Livewire/Project/Service/StackForm.php @@ -135,7 +135,7 @@ class StackForm extends Component $lockedKeys = $this->lockedFieldKeys(); $this->fields = $this->fields->map(function ($field) use ($lockedKeys) { $field['isLocked'] = $lockedKeys->contains(data_get($field, 'key')); - if ($field['isLocked'] || ($this->isPasswordHiddenForMember && data_get($field, 'isPassword'))) { + if ($field['isLocked'] || $this->isPasswordHiddenForMember) { $field['value'] = null; } @@ -190,10 +190,12 @@ class StackForm extends Component // Use transaction to ensure atomicity - if parse fails, save is rolled back DB::transaction(function () { $this->service->save(); - $lockedKeys = $this->lockedFieldKeys(); - $this->service->saveExtraFields( - $this->fields->reject(fn ($field) => $lockedKeys->contains(data_get($field, 'key'))) - ); + if (auth()->user()->can('manageEnvironment', $this->service)) { + $lockedKeys = $this->lockedFieldKeys(); + $this->service->saveExtraFields( + $this->fields->reject(fn ($field) => $lockedKeys->contains(data_get($field, 'key'))) + ); + } $this->service->parse(); }); // Refresh and write files after a successful commit diff --git a/resources/views/livewire/project/service/stack-form.blade.php b/resources/views/livewire/project/service/stack-form.blade.php index 59405e03f1..4e0ecaf185 100644 --- a/resources/views/livewire/project/service/stack-form.blade.php +++ b/resources/views/livewire/project/service/stack-form.blade.php @@ -79,7 +79,7 @@ @if (data_get($field, 'isLocked')) - @elseif ($isPasswordHiddenForMember && data_get($field, 'isPassword')) + @elseif ($isPasswordHiddenForMember) @else masterPassword->fresh()->value)->toBe('$SERVICE_PASSWORD_64_DRIZZLE') ->and($lockedTarget->fresh()->value)->toBe('referenced-locked-secret'); }); + +function addVaultwardenDatabaseUrl(): void +{ + ServiceApplication::create([ + 'uuid' => (string) Str::uuid(), + 'service_id' => test()->service->id, + 'name' => 'vaultwarden', + 'image' => 'vaultwarden/server:latest', + ]); + test()->service->environment_variables()->create([ + 'key' => 'DATABASE_URL', + 'value' => 'postgresql://vw:vaultwarden-db-secret@db:5432/vw', + ]); +} + +it('hides extra-field values without a password flag from a member', function () { + addVaultwardenDatabaseUrl(); + $ownTeam = Team::factory()->create(); + $ownTeam->members()->attach($this->member, ['role' => 'owner']); + + $this->actingAs($this->member); + foreach ([$this->team, $ownTeam] as $sessionTeam) { + session(['currentTeam' => $sessionTeam]); + + $component = mountStackForm(); + expect($component->get('fields')['DATABASE_URL']['value'])->toBeNull() + ->and(json_encode($component->snapshot))->not->toContain('vaultwarden-db-secret') + ->and($component->html())->not->toContain('vaultwarden-db-secret') + ->and($component->html())->toContain('Hidden (only admins can view)'); + } +}); + +it('does not let a member save overwrite stored extra-field values', function () { + addVaultwardenDatabaseUrl(); + $this->actingAs($this->member); + session(['currentTeam' => $this->team]); + + mountStackForm() + ->set('name', 'renamed-by-member') + ->set('fields.DATABASE_URL.value', 'member-value') + ->call('submit'); + + expect($this->service->fresh()->name)->not->toBe('renamed-by-member') + ->and($this->service->environment_variables()->where('key', 'DATABASE_URL')->value('value')) + ->toBe('postgresql://vw:vaultwarden-db-secret@db:5432/vw') + ->and($this->masterPassword->fresh()->value)->toBe('drizzle-master-secret'); +}); + +it('shows extra-field values without a password flag to an owner of the service team', function () { + addVaultwardenDatabaseUrl(); + $this->actingAs($this->owner); + session(['currentTeam' => $this->team]); + + expect(mountStackForm()->get('fields')['DATABASE_URL']['value']) + ->toBe('postgresql://vw:vaultwarden-db-secret@db:5432/vw'); +});