Files
coolify/app/Livewire/Project/Application/Source.php
T
Andras Bacsai 88efc0716e fix: enforce resource team isolation and preserve ClickHouse data
Scope resource permissions and related records to the owning team across
team switches, cloning, deployments, and resource operations.

Merge ClickHouse volume mounts, preserve anonymous data volumes on stop,
and require adopting current data before starting or restarting.

Fix file storage deletion checks and add regression coverage.
2026-10-05 14:23:04 +02:00

191 lines
6.2 KiB
PHP

<?php
namespace App\Livewire\Project\Application;
use App\Models\Application;
use App\Models\GithubApp;
use App\Models\GitlabApp;
use App\Models\PrivateKey;
use App\Rules\ValidGitBranch;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Source extends Component
{
use AuthorizesRequests;
public Application $application;
#[Locked]
public $privateKeys;
#[Validate(['nullable', 'string'])]
public ?string $privateKeyName = null;
#[Locked]
public ?int $privateKeyId = null;
#[Validate(['required', 'string'])]
public string $gitRepository;
#[Validate(['required', 'string', new ValidGitBranch])]
public string $gitBranch;
#[Validate(['nullable', 'string', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9._\-\/]*$/'])]
public ?string $gitCommitSha = null;
#[Locked]
public $sources;
public function mount()
{
try {
$this->syncData();
$this->getPrivateKeys();
$this->getSources();
} catch (\Throwable $e) {
handleError($e, $this);
}
}
public function updatedGitRepository()
{
$this->gitRepository = trim($this->gitRepository);
}
public function updatedGitBranch()
{
$this->gitBranch = trim($this->gitBranch);
}
public function updatedGitCommitSha()
{
$this->gitCommitSha = trim($this->gitCommitSha);
}
private function syncData(bool $toModel = false): void
{
if ($toModel) {
$this->validate();
$this->application->update([
'git_repository' => $this->gitRepository,
'git_branch' => $this->gitBranch,
'git_commit_sha' => $this->gitCommitSha,
'private_key_id' => $this->privateKeyId,
]);
// Refresh to get the trimmed values from the model
$this->application->refresh();
$this->syncData(false);
} else {
$this->gitRepository = $this->application->git_repository;
$this->gitBranch = $this->application->git_branch;
$this->gitCommitSha = $this->application->git_commit_sha;
$this->privateKeyId = $this->application->private_key_id;
$this->privateKeyName = data_get($this->application, 'private_key.name');
}
}
private function getPrivateKeys()
{
$this->privateKeys = PrivateKey::where('team_id', $this->application->team()->id)->get()->reject(function ($key) {
return $key->id == $this->privateKeyId;
});
}
private function getSources()
{
$this->sources = $this->application->team()->sources()->filter(function ($source) {
if ($source->id === $this->application->source_id
&& $source->getMorphClass() === $this->application->source_type) {
return false;
}
if ($source instanceof GithubApp) {
return ! is_null($source->app_id);
}
if ($source instanceof GitlabApp) {
return $source->isConnected();
}
return true;
})->sortBy('name');
}
public function setPrivateKey(int $privateKeyId)
{
try {
$this->authorize('update', $this->application);
$key = PrivateKey::where('team_id', $this->application->team()->id)->findOrFail($privateKeyId);
$this->privateKeyId = $key->id;
$this->syncData(true);
$this->getPrivateKeys();
$this->application->refresh();
$this->privateKeyName = $this->application->private_key->name;
$this->dispatch('success', 'Private key updated!');
$this->dispatch('configurationChanged');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submit()
{
try {
$this->authorize('update', $this->application);
if (str($this->gitCommitSha)->isEmpty()) {
$this->gitCommitSha = 'HEAD';
}
$this->syncData(true);
$this->dispatch('success', 'Application source updated!');
$this->dispatch('configurationChanged');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function changeSource($sourceId, $sourceType)
{
try {
$this->authorize('update', $this->application);
$allowedSourceTypes = [GithubApp::class, GitlabApp::class];
abort_unless(in_array($sourceType, $allowedSourceTypes, true), 404);
$teamId = $this->application->team()->id;
$source = $sourceType::query()
->where(fn ($query) => $query->where('team_id', $teamId)->orWhere('is_system_wide', true))
->findOrFail($sourceId);
$this->application->update([
'source_id' => $source->id,
'source_type' => $sourceType,
]);
$this->dispatch('configurationChanged');
['repository' => $customRepository] = $this->application->customRepository();
$repository_project_id = null;
if ($sourceType === GithubApp::class) {
$repository = githubApi($source, "repos/{$customRepository}");
$repository_project_id = data_get($repository, 'data.id');
} elseif ($sourceType === GitlabApp::class) {
if ($source->isConnected()) {
$encoded = urlencode($customRepository);
$project = gitlabApi($source, "/projects/{$encoded}");
$repository_project_id = data_get($project, 'data.id');
}
}
if (isset($repository_project_id) && $this->application->repository_project_id !== $repository_project_id) {
$this->application->repository_project_id = $repository_project_id;
$this->application->save();
}
$this->application->refresh();
$this->getSources();
$this->dispatch('success', 'Source updated!');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}