Files
Andras Bacsai 6f1905b7b0 fix(access): enforce member grants on more actions
- Require write access on the target environment to clone or move a resource
- Check read access on shared variables before a reference is added or resolved
- Limit team credentials, git sources, and imports of other backups to team-wide writers
- Require the delete level for backup, volume, storage, and task deletes
- Require create access for previews, new projects, and git sources
- Use the token team for the access scope, and check team-wide access on Livewire updates
- Require instance admin to toggle the API and MCP server
- Hide server pickers and broadcast details from members without access
2026-10-30 16:11:10 +01:00

199 lines
6.5 KiB
PHP

<?php
namespace App\Livewire\SharedVariables\Server;
use App\Livewire\Concerns\RemembersCanvasView;
use App\Models\Server;
use App\Models\SharedEnvironmentVariable;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
class Show extends Component
{
use AuthorizesRequests;
use RemembersCanvasView;
public Server $server;
public string $view = 'normal';
public ?string $variables = null;
protected $listeners = ['refreshEnvs' => 'refreshEnvs', 'saveKey' => 'saveKey', 'environmentVariableDeleted' => 'refreshEnvs'];
public function saveKey($data)
{
try {
$this->authorize('update', $this->server);
if (in_array($data['key'], ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])) {
throw new \Exception('Cannot create predefined variable.');
}
$found = $this->server->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->server->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'is_literal' => $data['is_literal'],
'comment' => $data['comment'] ?? null,
'type' => 'server',
'team_id' => $this->server->team_id,
]);
$this->server->refresh();
$this->getDevView();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function mount(?string $server_uuid = null)
{
$serverUuid = $server_uuid ?? request()->route('server_uuid');
$teamId = currentTeam()->id;
$server = Server::where('team_id', $teamId)->where('uuid', $serverUuid)->first();
if (! $server) {
return redirect()->route('dashboard');
}
$this->authorize('viewSharedVariables', $server);
$this->server = $server;
if ($this->prefersCanvasView('server_variables')) {
return $this->redirectRoute('shared-variables.server.canvas', ['server_uuid' => $server->uuid], navigate: true);
}
$this->getDevView();
}
public function switch()
{
$this->authorize('viewSharedVariables', $this->server);
$this->view = $this->view === 'normal' ? 'dev' : 'normal';
$this->getDevView();
}
public function getDevView()
{
$this->variables = $this->formatEnvironmentVariables($this->server->environment_variables->whereNotIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])->sortBy('key'));
}
private function formatEnvironmentVariables($variables)
{
$canViewValues = auth()->user()?->isAdminOfTeam($this->server->team_id) ?? false;
return $variables->map(fn ($item) => formatEnvVariableForDevView($item, $canViewValues))->join("\n");
}
public function submit()
{
// Parse before the save, so that an invalid "# coolify:" line changes nothing and keeps the edited text.
try {
$this->authorize('update', $this->server);
$variables = parseEnvDevView($this->variables, SharedEnvironmentVariable::DEV_VIEW_FLAG_DEFAULTS);
} catch (\Throwable $e) {
return handleError($e, $this);
}
try {
$this->handleBulkSubmit($variables);
$this->getDevView();
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->refreshEnvs();
}
}
private function handleBulkSubmit(array $variables)
{
$changesMade = DB::transaction(function () use ($variables) {
// Delete removed variables
$deletedCount = $this->deleteRemovedVariables($variables);
// Update or create variables
$updatedCount = $this->updateOrCreateVariables($variables);
return $deletedCount > 0 || $updatedCount > 0;
});
if ($changesMade) {
$this->dispatch('success', 'Environment variables updated.');
}
}
private function deleteRemovedVariables($variables)
{
$variablesToDelete = $this->server->environment_variables()
->whereNotIn('key', array_keys($variables))
->whereNotIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])
->get();
if ($variablesToDelete->isEmpty()) {
return 0;
}
$this->server->environment_variables()
->whereNotIn('key', array_keys($variables))
->whereNotIn('key', ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])
->delete();
return $variablesToDelete->count();
}
private function updateOrCreateVariables($variables)
{
$count = 0;
foreach ($variables as $key => $data) {
$value = is_array($data) ? ($data['value'] ?? '') : $data;
$comment = is_array($data) ? ($data['comment'] ?? null) : null;
// Skip predefined variables
if (in_array($key, ['COOLIFY_SERVER_UUID', 'COOLIFY_SERVER_NAME'])) {
continue;
}
$found = $this->server->environment_variables()->where('key', $key)->first();
if ($found) {
if (! $found->is_shown_once && ! $found->is_multiline && formatEnvValueForDevView($found->value) !== null) {
if ($found->value !== $value) {
$found->value = $value;
}
$found->comment = $comment;
$found->fill($data['settings']);
if ($found->isDirty()) {
$found->save();
$count++;
}
}
} else {
$this->server->environment_variables()->create([
'key' => $key,
'value' => $value,
'comment' => $comment,
'is_multiline' => false,
'is_literal' => false,
...$data['settings'],
'type' => 'server',
'team_id' => $this->server->team_id,
]);
$count++;
}
}
return $count;
}
public function refreshEnvs()
{
$this->server->refresh();
$this->getDevView();
}
public function render()
{
return view('livewire.shared-variables.server.show');
}
}