mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-15 18:15:49 +00:00
refactor: simplify remote process chain and harden ActivityMonitor
- Inline PrepareCoolifyTask and CoolifyTaskArgs into remote_process(), removing two single-consumer abstraction layers - Add #[Locked] attribute to ActivityMonitor $activityId property - Add team ownership verification in ActivityMonitor.hydrateActivity() with server_uuid fallback and fail-closed default - Store team_id in activity properties for proper scoping - Update CLAUDE.md to remove stale reference - Add comprehensive tests for activity monitor authorization Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
c8efbf107a
commit
3e0d48faea
7 files changed
+155
-131
No files matched your search
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\CoolifyTask;
|
||||
|
||||
use App\Data\CoolifyTaskArgs;
|
||||
use App\Jobs\CoolifyTask;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
/**
|
||||
* The initial step to run a `CoolifyTask`: a remote SSH process
|
||||
* with monitoring/tracking/trace feature. Such thing is made
|
||||
* possible using an Activity model and some attributes.
|
||||
*/
|
||||
class PrepareCoolifyTask
|
||||
{
|
||||
protected Activity $activity;
|
||||
|
||||
protected CoolifyTaskArgs $remoteProcessArgs;
|
||||
|
||||
public function __construct(CoolifyTaskArgs $remoteProcessArgs)
|
||||
{
|
||||
$this->remoteProcessArgs = $remoteProcessArgs;
|
||||
|
||||
if ($remoteProcessArgs->model) {
|
||||
$properties = $remoteProcessArgs->toArray();
|
||||
unset($properties['model']);
|
||||
|
||||
$this->activity = activity()
|
||||
->withProperties($properties)
|
||||
->performedOn($remoteProcessArgs->model)
|
||||
->event($remoteProcessArgs->type)
|
||||
->log('[]');
|
||||
} else {
|
||||
$this->activity = activity()
|
||||
->withProperties($remoteProcessArgs->toArray())
|
||||
->event($remoteProcessArgs->type)
|
||||
->log('[]');
|
||||
}
|
||||
}
|
||||
|
||||
public function __invoke(): Activity
|
||||
{
|
||||
$job = new CoolifyTask(
|
||||
activity: $this->activity,
|
||||
ignore_errors: $this->remoteProcessArgs->ignore_errors,
|
||||
call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish,
|
||||
call_event_data: $this->remoteProcessArgs->call_event_data,
|
||||
);
|
||||
dispatch($job);
|
||||
$this->activity->refresh();
|
||||
|
||||
return $this->activity;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Data;
|
||||
|
||||
use App\Enums\ProcessStatus;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
/**
|
||||
* The parameters to execute a CoolifyTask, organized in a DTO.
|
||||
*/
|
||||
class CoolifyTaskArgs extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $server_uuid,
|
||||
public string $command,
|
||||
public string $type,
|
||||
public ?string $type_uuid = null,
|
||||
public ?int $process_id = null,
|
||||
public ?Model $model = null,
|
||||
public ?string $status = null,
|
||||
public bool $ignore_errors = false,
|
||||
public $call_event_on_finish = null,
|
||||
public $call_event_data = null
|
||||
) {
|
||||
if (is_null($status)) {
|
||||
$this->status = ProcessStatus::QUEUED->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Component;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
@@ -10,6 +12,7 @@ class ActivityMonitor extends Component
|
||||
{
|
||||
public ?string $header = null;
|
||||
|
||||
#[Locked]
|
||||
public $activityId = null;
|
||||
|
||||
public $eventToDispatch = 'activityFinished';
|
||||
@@ -57,25 +60,47 @@ class ActivityMonitor extends Component
|
||||
|
||||
$activity = Activity::find($this->activityId);
|
||||
|
||||
if ($activity) {
|
||||
$teamId = data_get($activity, 'properties.team_id');
|
||||
if ($teamId && $teamId !== currentTeam()?->id) {
|
||||
if (! $activity) {
|
||||
$this->activity = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$currentTeamId = currentTeam()?->id;
|
||||
|
||||
// Check team_id stored directly in activity properties
|
||||
$activityTeamId = data_get($activity, 'properties.team_id');
|
||||
if ($activityTeamId !== null) {
|
||||
if ((int) $activityTeamId !== (int) $currentTeamId) {
|
||||
$this->activity = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->activity = $activity;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: verify ownership via the server that ran the command
|
||||
$serverUuid = data_get($activity, 'properties.server_uuid');
|
||||
if ($serverUuid) {
|
||||
$server = Server::where('uuid', $serverUuid)->first();
|
||||
if ($server && (int) $server->team_id !== (int) $currentTeamId) {
|
||||
$this->activity = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($server) {
|
||||
$this->activity = $activity;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->activity = $activity;
|
||||
}
|
||||
|
||||
public function updatedActivityId($value)
|
||||
{
|
||||
if ($value) {
|
||||
$this->hydrateActivity();
|
||||
$this->isPollingActive = true;
|
||||
self::$eventDispatched = false;
|
||||
}
|
||||
// Fail closed: no team_id and no server_uuid means we cannot verify ownership
|
||||
$this->activity = null;
|
||||
}
|
||||
|
||||
public function polling()
|
||||
|
||||
Reference in new issue
Block a user