refactor(auth): enforce authorization checks across livewire components
Add authorization checks to multiple Livewire components to ensure users have proper permissions before performing sensitive operations. This includes: - Adding AuthorizesRequests trait to components handling deployments, backups, services, and configuration uploads - Enforcing 'deploy', 'update', and 'manageBackups' authorization checks - Adding instance admin check for system upgrade operations - Improving database queries with team ownership scope - Moving backup trigger from component to button with new backupNow() method
This commit is contained in:
parent
cebef8e258
commit
68f81df0bb
10 changed files with 105 additions and 30 deletions
|
|
@ -45,6 +45,9 @@ public function back()
|
||||||
|
|
||||||
public function submitSearch()
|
public function submitSearch()
|
||||||
{
|
{
|
||||||
|
if (Auth::id() !== 0 && ! session('impersonating')) {
|
||||||
|
return redirect()->route('dashboard');
|
||||||
|
}
|
||||||
if ($this->search !== '') {
|
if ($this->search !== '') {
|
||||||
$this->foundUsers = User::where(function ($query) {
|
$this->foundUsers = User::where(function ($query) {
|
||||||
$query->where('name', 'like', "%{$this->search}%")
|
$query->where('name', 'like', "%{$this->search}%")
|
||||||
|
|
@ -55,6 +58,9 @@ public function submitSearch()
|
||||||
|
|
||||||
public function getSubscribers()
|
public function getSubscribers()
|
||||||
{
|
{
|
||||||
|
if (Auth::id() !== 0 && ! session('impersonating')) {
|
||||||
|
return redirect()->route('dashboard');
|
||||||
|
}
|
||||||
$this->inactiveSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', false)->count();
|
$this->inactiveSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', false)->count();
|
||||||
$this->activeSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->count();
|
$this->activeSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->count();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ public function show_debug()
|
||||||
public function force_start()
|
public function force_start()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$this->authorize('deploy', $this->application);
|
||||||
force_start_deployment($this->application_deployment_queue);
|
force_start_deployment($this->application_deployment_queue);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
|
@ -82,6 +83,11 @@ public function copyLogsToClipboard(): string
|
||||||
|
|
||||||
public function cancel()
|
public function cancel()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
|
$this->authorize('deploy', $this->application);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
$deployment_uuid = $this->application_deployment_queue->deployment_uuid;
|
$deployment_uuid = $this->application_deployment_queue->deployment_uuid;
|
||||||
$kill_command = "docker rm -f {$deployment_uuid}";
|
$kill_command = "docker rm -f {$deployment_uuid}";
|
||||||
$build_server_id = $this->application_deployment_queue->build_server_id ?? $this->application->destination->server_id;
|
$build_server_id = $this->application_deployment_queue->build_server_id ?? $this->application->destination->server_id;
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,14 @@
|
||||||
namespace App\Livewire\Project\Application;
|
namespace App\Livewire\Project\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Livewire\Attributes\Validate;
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Swarm extends Component
|
class Swarm extends Component
|
||||||
{
|
{
|
||||||
|
use AuthorizesRequests;
|
||||||
|
|
||||||
public Application $application;
|
public Application $application;
|
||||||
|
|
||||||
#[Validate('required')]
|
#[Validate('required')]
|
||||||
|
|
@ -51,6 +54,7 @@ public function syncData(bool $toModel = false)
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$this->authorize('update', $this->application);
|
||||||
$this->syncData(true);
|
$this->syncData(true);
|
||||||
$this->dispatch('success', 'Swarm settings updated.');
|
$this->dispatch('success', 'Swarm settings updated.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|
@ -61,6 +65,7 @@ public function instantSave()
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$this->authorize('update', $this->application);
|
||||||
$this->syncData(true);
|
$this->syncData(true);
|
||||||
$this->dispatch('success', 'Swarm settings updated.');
|
$this->dispatch('success', 'Swarm settings updated.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,18 @@ public function delete($password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function backupNow()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->authorize('manageBackups', $this->backup->database);
|
||||||
|
|
||||||
|
\App\Jobs\DatabaseBackupJob::dispatch($this->backup);
|
||||||
|
$this->dispatch('success', 'Backup queued. It will be available in a few minutes.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,15 @@
|
||||||
namespace App\Livewire\Project\Database;
|
namespace App\Livewire\Project\Database;
|
||||||
|
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class BackupExecutions extends Component
|
class BackupExecutions extends Component
|
||||||
{
|
{
|
||||||
|
use AuthorizesRequests;
|
||||||
|
|
||||||
public ?ScheduledDatabaseBackup $backup = null;
|
public ?ScheduledDatabaseBackup $backup = null;
|
||||||
|
|
||||||
public $database;
|
public $database;
|
||||||
|
|
@ -44,29 +47,45 @@ public function getListeners()
|
||||||
|
|
||||||
public function cleanupFailed()
|
public function cleanupFailed()
|
||||||
{
|
{
|
||||||
if ($this->backup) {
|
try {
|
||||||
$this->backup->executions()->where('status', 'failed')->delete();
|
$this->authorize('manageBackups', $this->database);
|
||||||
$this->refreshBackupExecutions();
|
if ($this->backup) {
|
||||||
$this->dispatch('success', 'Failed backups cleaned up.');
|
$this->backup->executions()->where('status', 'failed')->delete();
|
||||||
|
$this->refreshBackupExecutions();
|
||||||
|
$this->dispatch('success', 'Failed backups cleaned up.');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cleanupDeleted()
|
public function cleanupDeleted()
|
||||||
{
|
{
|
||||||
if ($this->backup) {
|
try {
|
||||||
$deletedCount = $this->backup->executions()->where('local_storage_deleted', true)->count();
|
$this->authorize('manageBackups', $this->database);
|
||||||
if ($deletedCount > 0) {
|
if ($this->backup) {
|
||||||
$this->backup->executions()->where('local_storage_deleted', true)->delete();
|
$deletedCount = $this->backup->executions()->where('local_storage_deleted', true)->count();
|
||||||
$this->refreshBackupExecutions();
|
if ($deletedCount > 0) {
|
||||||
$this->dispatch('success', "Cleaned up {$deletedCount} backup entries deleted from local storage.");
|
$this->backup->executions()->where('local_storage_deleted', true)->delete();
|
||||||
} else {
|
$this->refreshBackupExecutions();
|
||||||
$this->dispatch('info', 'No backup entries found that are deleted from local storage.');
|
$this->dispatch('success', "Cleaned up {$deletedCount} backup entries deleted from local storage.");
|
||||||
|
} else {
|
||||||
|
$this->dispatch('info', 'No backup entries found that are deleted from local storage.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteBackup($executionId, $password)
|
public function deleteBackup($executionId, $password)
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
|
$this->authorize('manageBackups', $this->database);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
|
||||||
if (! verifyPasswordConfirmation($password, $this)) {
|
if (! verifyPasswordConfirmation($password, $this)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,13 @@
|
||||||
namespace App\Livewire\Project\Service;
|
namespace App\Livewire\Project\Service;
|
||||||
|
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class EditCompose extends Component
|
class EditCompose extends Component
|
||||||
{
|
{
|
||||||
|
use AuthorizesRequests;
|
||||||
|
|
||||||
public Service $service;
|
public Service $service;
|
||||||
|
|
||||||
public $serviceId;
|
public $serviceId;
|
||||||
|
|
@ -72,19 +75,29 @@ public function validateCompose()
|
||||||
|
|
||||||
public function saveEditedCompose()
|
public function saveEditedCompose()
|
||||||
{
|
{
|
||||||
$this->dispatch('info', 'Saving new docker compose...');
|
try {
|
||||||
$this->dispatch('saveCompose', $this->dockerComposeRaw);
|
$this->authorize('update', $this->service);
|
||||||
$this->dispatch('refreshStorages');
|
$this->dispatch('info', 'Saving new docker compose...');
|
||||||
|
$this->dispatch('saveCompose', $this->dockerComposeRaw);
|
||||||
|
$this->dispatch('refreshStorages');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
$this->validate([
|
try {
|
||||||
'isContainerLabelEscapeEnabled' => 'required',
|
$this->authorize('update', $this->service);
|
||||||
]);
|
$this->validate([
|
||||||
$this->syncData(true);
|
'isContainerLabelEscapeEnabled' => 'required',
|
||||||
$this->service->save(['is_container_label_escape_enabled' => $this->isContainerLabelEscapeEnabled]);
|
]);
|
||||||
$this->dispatch('success', 'Service updated successfully');
|
$this->syncData(true);
|
||||||
|
$this->service->save(['is_container_label_escape_enabled' => $this->isContainerLabelEscapeEnabled]);
|
||||||
|
$this->dispatch('success', 'Service updated successfully');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,15 @@
|
||||||
|
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use App\Support\ValidationPatterns;
|
use App\Support\ValidationPatterns;
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class StackForm extends Component
|
class StackForm extends Component
|
||||||
{
|
{
|
||||||
|
use AuthorizesRequests;
|
||||||
|
|
||||||
public Service $service;
|
public Service $service;
|
||||||
|
|
||||||
public Collection $fields;
|
public Collection $fields;
|
||||||
|
|
@ -128,14 +131,20 @@ public function saveCompose($raw)
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
$this->syncData(true);
|
try {
|
||||||
$this->service->save();
|
$this->authorize('update', $this->service);
|
||||||
$this->dispatch('success', 'Service settings saved.');
|
$this->syncData(true);
|
||||||
|
$this->service->save();
|
||||||
|
$this->dispatch('success', 'Service settings saved.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit($notify = true)
|
public function submit($notify = true)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$this->authorize('update', $this->service);
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->syncData(true);
|
$this->syncData(true);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,13 @@
|
||||||
namespace App\Livewire\Project\Shared;
|
namespace App\Livewire\Project\Shared;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class UploadConfig extends Component
|
class UploadConfig extends Component
|
||||||
{
|
{
|
||||||
|
use AuthorizesRequests;
|
||||||
|
|
||||||
public $config;
|
public $config;
|
||||||
|
|
||||||
public $applicationId;
|
public $applicationId;
|
||||||
|
|
@ -29,13 +32,12 @@ public function mount()
|
||||||
public function uploadConfig()
|
public function uploadConfig()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$application = Application::findOrFail($this->applicationId);
|
$application = Application::ownedByCurrentTeam()->findOrFail($this->applicationId);
|
||||||
|
$this->authorize('update', $application);
|
||||||
$application->setConfig($this->config);
|
$application->setConfig($this->config);
|
||||||
$this->dispatch('success', 'Application settings updated');
|
$this->dispatch('success', 'Application settings updated');
|
||||||
} catch (\Exception $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->dispatch('error', $e->getMessage());
|
return handleError($e, $this);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,9 @@ public function checkUpdate()
|
||||||
public function upgrade()
|
public function upgrade()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
if (! isInstanceAdmin()) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
if ($this->updateInProgress) {
|
if ($this->updateInProgress) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
Save
|
Save
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@if (str($status)->startsWith('running'))
|
@if (str($status)->startsWith('running'))
|
||||||
<livewire:project.database.backup-now :backup="$backup" />
|
<x-forms.button wire:click='backupNow'>Backup Now</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
@if ($backup->database_id !== 0)
|
@if ($backup->database_id !== 0)
|
||||||
<x-modal-confirmation title="Confirm Backup Schedule Deletion?" buttonTitle="Delete Backups and Schedule"
|
<x-modal-confirmation title="Confirm Backup Schedule Deletion?" buttonTitle="Delete Backups and Schedule"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue