mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 18:52:49 +00:00
The executions list polled with the task stored as a model, so a poll after the delete failed to load it and showed a 404. Load the task by id on each request and skip the refresh when it is gone. Return true from delete so the confirmation modal does not refresh the deleted task. Closes #11814 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
126 lines
4.5 KiB
PHP
126 lines
4.5 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Project\Shared\ScheduledTask\Executions;
|
|
use App\Livewire\Project\Shared\ScheduledTask\Show;
|
|
use App\Models\Application;
|
|
use App\Models\Environment;
|
|
use App\Models\InstanceSettings;
|
|
use App\Models\Project;
|
|
use App\Models\ScheduledTask;
|
|
use App\Models\Server;
|
|
use App\Models\StandaloneDocker;
|
|
use App\Models\Team;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Gate;
|
|
use Livewire\Attributes\Locked;
|
|
use Livewire\Livewire;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
$this->withoutVite();
|
|
InstanceSettings::forceCreate(['id' => 0]);
|
|
|
|
$this->attacker = User::factory()->create();
|
|
$this->attackerTeam = Team::factory()->create();
|
|
$this->attacker->teams()->attach($this->attackerTeam, ['role' => 'owner']);
|
|
|
|
$this->victim = User::factory()->create();
|
|
$this->victimTeam = Team::factory()->create();
|
|
$this->victim->teams()->attach($this->victimTeam, ['role' => 'owner']);
|
|
|
|
// Attacker team gets a real server/project/env/app so Show can mount + authorize
|
|
$this->server = Server::factory()->create(['team_id' => $this->attackerTeam->id]);
|
|
$this->destination = StandaloneDocker::where('server_id', $this->server->id)->first();
|
|
$this->project = Project::factory()->create(['team_id' => $this->attackerTeam->id]);
|
|
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
|
|
$this->application = Application::factory()->create([
|
|
'environment_id' => $this->environment->id,
|
|
'destination_id' => $this->destination->id,
|
|
'destination_type' => $this->destination->getMorphClass(),
|
|
]);
|
|
|
|
$this->actingAs($this->attacker);
|
|
session(['currentTeam' => $this->attackerTeam]);
|
|
});
|
|
|
|
describe('ScheduledTask locked properties', function () {
|
|
test('Show component task property has Locked attribute', function () {
|
|
$property = new ReflectionProperty(Show::class, 'task');
|
|
$attributes = $property->getAttributes(Locked::class);
|
|
|
|
expect($attributes)->not->toBeEmpty();
|
|
});
|
|
|
|
test('Show component resource property has Locked attribute', function () {
|
|
$property = new ReflectionProperty(Show::class, 'resource');
|
|
$attributes = $property->getAttributes(Locked::class);
|
|
|
|
expect($attributes)->not->toBeEmpty();
|
|
});
|
|
|
|
test('Executions component task id property has Locked attribute', function () {
|
|
$property = new ReflectionProperty(Executions::class, 'taskId');
|
|
$attributes = $property->getAttributes(Locked::class);
|
|
|
|
expect($attributes)->not->toBeEmpty();
|
|
});
|
|
|
|
test('Executions component selected execution property has Locked attribute', function () {
|
|
$property = new ReflectionProperty(Executions::class, 'selectedExecution');
|
|
$attributes = $property->getAttributes(Locked::class);
|
|
|
|
expect($attributes)->not->toBeEmpty();
|
|
});
|
|
});
|
|
|
|
describe('ScheduledTask cross-team access', function () {
|
|
test('Executions rejects mounting another team task id', function () {
|
|
$victimTask = ScheduledTask::factory()->create([
|
|
'team_id' => $this->victimTeam->id,
|
|
'command' => 'echo top-secret-victim-command',
|
|
]);
|
|
|
|
Livewire::test(Executions::class, ['taskId' => $victimTask->id])
|
|
->assertStatus(404);
|
|
});
|
|
|
|
test('Show policy denies updating another team task', function () {
|
|
$victimTask = ScheduledTask::factory()->create([
|
|
'team_id' => $this->victimTeam->id,
|
|
'name' => 'victim-task',
|
|
'command' => 'echo original-victim-command',
|
|
]);
|
|
|
|
expect(
|
|
Gate::forUser($this->attacker)->allows('update', $victimTask)
|
|
)->toBeFalse();
|
|
});
|
|
|
|
test('Show policy denies deleting another team task', function () {
|
|
$victimTask = ScheduledTask::factory()->create([
|
|
'team_id' => $this->victimTeam->id,
|
|
'name' => 'victim-task',
|
|
'command' => 'echo original-victim-command',
|
|
]);
|
|
|
|
expect(
|
|
Gate::forUser($this->attacker)->allows('delete', $victimTask)
|
|
)->toBeFalse();
|
|
});
|
|
|
|
test('Show policy allows updating own team task', function () {
|
|
$ownTask = ScheduledTask::factory()->create([
|
|
'team_id' => $this->attackerTeam->id,
|
|
'application_id' => $this->application->id,
|
|
'name' => 'own-task',
|
|
'command' => 'echo original-command',
|
|
]);
|
|
|
|
expect(
|
|
Gate::forUser($this->attacker)->allows('update', $ownTask)
|
|
)->toBeTrue();
|
|
});
|
|
});
|