diff --git a/app/Livewire/Destination/Show.php b/app/Livewire/Destination/Show.php index 63ceebef4e..b0ab4d183e 100644 --- a/app/Livewire/Destination/Show.php +++ b/app/Livewire/Destination/Show.php @@ -43,7 +43,7 @@ class Show extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); diff --git a/app/Livewire/Notifications/Discord.php b/app/Livewire/Notifications/Discord.php index 71c24e238d..8ea4bbc958 100644 --- a/app/Livewire/Notifications/Discord.php +++ b/app/Livewire/Notifications/Discord.php @@ -85,11 +85,10 @@ class Discord extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); - $this->authorize('update', $this->settings); $this->settings->discord_enabled = $this->discordEnabled; $this->settings->discord_webhook_url = $this->discordWebhookUrl; @@ -174,6 +173,7 @@ class Discord extends Component public function instantSave() { try { + $this->authorize('update', $this->settings); $this->syncData(true); } catch (\Throwable $e) { return handleError($e, $this); @@ -184,6 +184,7 @@ class Discord extends Component { try { $this->resetErrorBag(); + $this->authorize('update', $this->settings); $this->syncData(true); $this->saveModel(); } catch (\Throwable $e) { @@ -193,6 +194,8 @@ class Discord extends Component public function saveModel() { + $this->authorize('update', $this->settings); + $this->syncData(true); refreshSession(); $this->dispatch('success', 'Settings saved.'); diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php index 530583db5c..5bd55137b5 100644 --- a/app/Livewire/Notifications/Email.php +++ b/app/Livewire/Notifications/Email.php @@ -132,12 +132,11 @@ class Email extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); $this->validate(['smtpEhloDomain' => ['nullable', 'string', new ValidHostname]]); - $this->authorize('update', $this->settings); $this->settings->smtp_enabled = $this->smtpEnabled; $this->settings->smtp_from_address = $this->smtpFromAddress; $this->settings->smtp_from_name = $this->smtpFromName; @@ -224,6 +223,8 @@ class Email extends Component public function saveModel() { + $this->authorize('update', $this->settings); + $this->syncData(true); $this->dispatch('success', 'Email notifications settings updated.'); } diff --git a/app/Livewire/Notifications/Pushover.php b/app/Livewire/Notifications/Pushover.php index 04cd827569..7caacdb916 100644 --- a/app/Livewire/Notifications/Pushover.php +++ b/app/Livewire/Notifications/Pushover.php @@ -89,11 +89,10 @@ class Pushover extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); - $this->authorize('update', $this->settings); $this->settings->pushover_enabled = $this->pushoverEnabled; $this->settings->pushover_user_key = $this->pushoverUserKey; $this->settings->pushover_api_token = $this->pushoverApiToken; @@ -167,6 +166,7 @@ class Pushover extends Component public function instantSave() { try { + $this->authorize('update', $this->settings); $this->syncData(true); } catch (\Throwable $e) { return handleError($e, $this); @@ -179,6 +179,7 @@ class Pushover extends Component { try { $this->resetErrorBag(); + $this->authorize('update', $this->settings); $this->syncData(true); $this->saveModel(); } catch (\Throwable $e) { @@ -188,6 +189,8 @@ class Pushover extends Component public function saveModel() { + $this->authorize('update', $this->settings); + $this->syncData(true); refreshSession(); $this->dispatch('success', 'Settings saved.'); diff --git a/app/Livewire/Notifications/Slack.php b/app/Livewire/Notifications/Slack.php index f4323875f2..fe84710bdf 100644 --- a/app/Livewire/Notifications/Slack.php +++ b/app/Livewire/Notifications/Slack.php @@ -87,11 +87,10 @@ class Slack extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); - $this->authorize('update', $this->settings); $this->settings->slack_enabled = $this->slackEnabled; $this->settings->slack_webhook_url = $this->slackWebhookUrl; @@ -158,6 +157,7 @@ class Slack extends Component public function instantSave() { try { + $this->authorize('update', $this->settings); $this->syncData(true); } catch (\Throwable $e) { return handleError($e, $this); @@ -170,6 +170,7 @@ class Slack extends Component { try { $this->resetErrorBag(); + $this->authorize('update', $this->settings); $this->syncData(true); $this->saveModel(); } catch (\Throwable $e) { @@ -179,6 +180,8 @@ class Slack extends Component public function saveModel() { + $this->authorize('update', $this->settings); + $this->syncData(true); refreshSession(); $this->dispatch('success', 'Settings saved.'); diff --git a/app/Livewire/Notifications/Telegram.php b/app/Livewire/Notifications/Telegram.php index f77b3c3a8e..51239b8621 100644 --- a/app/Livewire/Notifications/Telegram.php +++ b/app/Livewire/Notifications/Telegram.php @@ -134,11 +134,10 @@ class Telegram extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); - $this->authorize('update', $this->settings); $this->settings->telegram_enabled = $this->telegramEnabled; $this->settings->telegram_token = $this->telegramToken; $this->settings->telegram_chat_id = $this->telegramChatId; @@ -223,6 +222,7 @@ class Telegram extends Component public function instantSave() { try { + $this->authorize('update', $this->settings); $this->syncData(true); } catch (\Throwable $e) { return handleError($e, $this); @@ -235,6 +235,7 @@ class Telegram extends Component { try { $this->resetErrorBag(); + $this->authorize('update', $this->settings); $this->syncData(true); $this->saveModel(); } catch (\Throwable $e) { @@ -264,6 +265,8 @@ class Telegram extends Component public function saveModel() { + $this->authorize('update', $this->settings); + $this->syncData(true); refreshSession(); $this->dispatch('success', 'Settings saved.'); diff --git a/app/Livewire/Notifications/Webhook.php b/app/Livewire/Notifications/Webhook.php index 3461503d44..a3480ada69 100644 --- a/app/Livewire/Notifications/Webhook.php +++ b/app/Livewire/Notifications/Webhook.php @@ -82,11 +82,10 @@ class Webhook extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); - $this->authorize('update', $this->settings); $this->settings->webhook_enabled = $this->webhookEnabled; $this->settings->webhook_url = $this->webhookUrl; @@ -152,6 +151,7 @@ class Webhook extends Component public function instantSave() { try { + $this->authorize('update', $this->settings); $this->syncData(true); } catch (\Throwable $e) { return handleError($e, $this); @@ -162,6 +162,7 @@ class Webhook extends Component { try { $this->resetErrorBag(); + $this->authorize('update', $this->settings); $this->syncData(true); $this->saveModel(); } catch (\Throwable $e) { @@ -171,6 +172,8 @@ class Webhook extends Component public function saveModel() { + $this->authorize('update', $this->settings); + $this->syncData(true); refreshSession(); diff --git a/app/Livewire/Project/Application/Advanced.php b/app/Livewire/Project/Application/Advanced.php index bf84f385dd..45e284c5dc 100644 --- a/app/Livewire/Project/Application/Advanced.php +++ b/app/Livewire/Project/Application/Advanced.php @@ -99,7 +99,7 @@ class Advanced extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 0c424631f7..bb6bcf5f5d 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -330,7 +330,7 @@ class General extends Component $this->syncData(); } - public function syncData(bool $toModel = false): void + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); diff --git a/app/Livewire/Project/Application/Source.php b/app/Livewire/Project/Application/Source.php index 29f798d595..60a7955738 100644 --- a/app/Livewire/Project/Application/Source.php +++ b/app/Livewire/Project/Application/Source.php @@ -65,7 +65,7 @@ class Source extends Component $this->gitCommitSha = trim($this->gitCommitSha); } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); diff --git a/app/Livewire/Project/Application/Swarm.php b/app/Livewire/Project/Application/Swarm.php index 661578fb3d..ac867e69aa 100644 --- a/app/Livewire/Project/Application/Swarm.php +++ b/app/Livewire/Project/Application/Swarm.php @@ -31,7 +31,7 @@ class Swarm extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); diff --git a/app/Livewire/Project/Database/BackupEdit.php b/app/Livewire/Project/Database/BackupEdit.php index 2cd0d76739..28f7e02556 100644 --- a/app/Livewire/Project/Database/BackupEdit.php +++ b/app/Livewire/Project/Database/BackupEdit.php @@ -128,7 +128,7 @@ class BackupEdit extends Component $this->status = $database->status; } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->backup->enabled = $this->backupEnabled; diff --git a/app/Livewire/Project/Database/Clickhouse/General.php b/app/Livewire/Project/Database/Clickhouse/General.php index ad5e45b3fe..1d8354a4fb 100644 --- a/app/Livewire/Project/Database/Clickhouse/General.php +++ b/app/Livewire/Project/Database/Clickhouse/General.php @@ -121,7 +121,7 @@ class General extends Component ); } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); @@ -199,6 +199,7 @@ class General extends Component } $this->dispatch('databaseUpdated'); } catch (\Throwable $e) { + $this->authorize('update', $this->database); $this->isPublic = ! $this->isPublic; $this->syncData(true); diff --git a/app/Livewire/Project/Database/Dragonfly/General.php b/app/Livewire/Project/Database/Dragonfly/General.php index 2f5b844845..a8bde2f007 100644 --- a/app/Livewire/Project/Database/Dragonfly/General.php +++ b/app/Livewire/Project/Database/Dragonfly/General.php @@ -115,7 +115,7 @@ class General extends Component ); } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); @@ -191,6 +191,7 @@ class General extends Component } $this->dispatch('databaseUpdated'); } catch (\Throwable $e) { + $this->authorize('update', $this->database); $this->isPublic = ! $this->isPublic; $this->syncData(true); diff --git a/app/Livewire/Project/Database/Health.php b/app/Livewire/Project/Database/Health.php index 8943e6316e..07373bdba2 100644 --- a/app/Livewire/Project/Database/Health.php +++ b/app/Livewire/Project/Database/Health.php @@ -34,7 +34,7 @@ class Health extends Component $this->syncData(); } - public function syncData(bool $toModel = false): void + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); diff --git a/app/Livewire/Project/Database/Keydb/General.php b/app/Livewire/Project/Database/Keydb/General.php index b2d9bce91b..0398362bbb 100644 --- a/app/Livewire/Project/Database/Keydb/General.php +++ b/app/Livewire/Project/Database/Keydb/General.php @@ -118,7 +118,7 @@ class General extends Component ); } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); @@ -196,6 +196,7 @@ class General extends Component } $this->dispatch('databaseUpdated'); } catch (\Throwable $e) { + $this->authorize('update', $this->database); $this->isPublic = ! $this->isPublic; $this->syncData(true); diff --git a/app/Livewire/Project/Database/Mariadb/General.php b/app/Livewire/Project/Database/Mariadb/General.php index 61280a34b5..4d2dd9d8c2 100644 --- a/app/Livewire/Project/Database/Mariadb/General.php +++ b/app/Livewire/Project/Database/Mariadb/General.php @@ -136,7 +136,7 @@ class General extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); @@ -244,6 +244,7 @@ class General extends Component } $this->dispatch('databaseUpdated'); } catch (\Throwable $e) { + $this->authorize('update', $this->database); $this->isPublic = ! $this->isPublic; $this->syncData(true); diff --git a/app/Livewire/Project/Database/Mongodb/General.php b/app/Livewire/Project/Database/Mongodb/General.php index f68ba82c7d..d3545564ee 100644 --- a/app/Livewire/Project/Database/Mongodb/General.php +++ b/app/Livewire/Project/Database/Mongodb/General.php @@ -128,7 +128,7 @@ class General extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); @@ -237,6 +237,7 @@ class General extends Component } $this->dispatch('databaseUpdated'); } catch (\Throwable $e) { + $this->authorize('update', $this->database); $this->isPublic = ! $this->isPublic; $this->syncData(true); diff --git a/app/Livewire/Project/Database/Mysql/General.php b/app/Livewire/Project/Database/Mysql/General.php index 1adfe2ea79..ce7fc01ecd 100644 --- a/app/Livewire/Project/Database/Mysql/General.php +++ b/app/Livewire/Project/Database/Mysql/General.php @@ -136,7 +136,7 @@ class General extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); @@ -244,6 +244,7 @@ class General extends Component } $this->dispatch('databaseUpdated'); } catch (\Throwable $e) { + $this->authorize('update', $this->database); $this->isPublic = ! $this->isPublic; $this->syncData(true); diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php index 83d6e97dfa..3d0406956f 100644 --- a/app/Livewire/Project/Database/Postgresql/General.php +++ b/app/Livewire/Project/Database/Postgresql/General.php @@ -149,7 +149,7 @@ class General extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); @@ -240,6 +240,7 @@ class General extends Component } $this->dispatch('databaseUpdated'); } catch (\Throwable $e) { + $this->authorize('update', $this->database); $this->isPublic = ! $this->isPublic; $this->syncData(true); diff --git a/app/Livewire/Project/Database/Redis/General.php b/app/Livewire/Project/Database/Redis/General.php index d431b15064..7c6313c8da 100644 --- a/app/Livewire/Project/Database/Redis/General.php +++ b/app/Livewire/Project/Database/Redis/General.php @@ -127,7 +127,7 @@ class General extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); @@ -235,6 +235,7 @@ class General extends Component } $this->dispatch('databaseUpdated'); } catch (\Throwable $e) { + $this->authorize('update', $this->database); $this->isPublic = ! $this->isPublic; $this->syncData(true); diff --git a/app/Livewire/Project/Edit.php b/app/Livewire/Project/Edit.php index 91b0444f51..0d42c71e94 100644 --- a/app/Livewire/Project/Edit.php +++ b/app/Livewire/Project/Edit.php @@ -77,7 +77,7 @@ class Edit extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); diff --git a/app/Livewire/Project/EnvironmentEdit.php b/app/Livewire/Project/EnvironmentEdit.php index 9b9a3670db..35db3167d3 100644 --- a/app/Livewire/Project/EnvironmentEdit.php +++ b/app/Livewire/Project/EnvironmentEdit.php @@ -48,7 +48,7 @@ class EnvironmentEdit extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); diff --git a/app/Livewire/Project/Service/EditDomain.php b/app/Livewire/Project/Service/EditDomain.php index 96fe6a62c3..bfcd04ac56 100644 --- a/app/Livewire/Project/Service/EditDomain.php +++ b/app/Livewire/Project/Service/EditDomain.php @@ -46,7 +46,7 @@ class EditDomain extends Component $this->syncData(); } - public function syncData(bool $toModel = false): void + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); diff --git a/app/Livewire/Project/Service/FileStorage.php b/app/Livewire/Project/Service/FileStorage.php index 4520876d16..d6ab2ac151 100644 --- a/app/Livewire/Project/Service/FileStorage.php +++ b/app/Livewire/Project/Service/FileStorage.php @@ -120,7 +120,7 @@ class FileStorage extends Component : route('project.application.backup.show', [...$parameters, 'backup_uuid' => $backup->uuid]); } - public function syncData(bool $toModel = false): void + private function syncData(bool $toModel = false): void { if ($toModel) { if ($this->fileStorage->is_too_large) { diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index cd86d8670f..0e0851aaa0 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -162,7 +162,7 @@ class Show extends Component $this->valuesLoaded = true; } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->key = ValidationPatterns::normalizeEnvironmentVariableKey($this->key); diff --git a/app/Livewire/Project/Shared/HealthChecks.php b/app/Livewire/Project/Shared/HealthChecks.php index 6a128a1426..70633fe030 100644 --- a/app/Livewire/Project/Shared/HealthChecks.php +++ b/app/Livewire/Project/Shared/HealthChecks.php @@ -86,7 +86,7 @@ class HealthChecks extends Component } } - public function syncData(bool $toModel = false): void + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); diff --git a/app/Livewire/Project/Shared/ScheduledTask/Add.php b/app/Livewire/Project/Shared/ScheduledTask/Add.php index 61bc6b0fbc..f170a0a6f0 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Add.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Add.php @@ -102,7 +102,7 @@ class Add extends Component } } - public function saveScheduledTask() + private function saveScheduledTask(): mixed { try { $task = new ScheduledTask; diff --git a/app/Livewire/Project/Shared/ScheduledTask/Show.php b/app/Livewire/Project/Shared/ScheduledTask/Show.php index 4eb5c071c5..30d1024621 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Show.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Show.php @@ -87,7 +87,7 @@ class Show extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); diff --git a/app/Livewire/Server/Advanced.php b/app/Livewire/Server/Advanced.php index a94881b12b..895ce34e79 100644 --- a/app/Livewire/Server/Advanced.php +++ b/app/Livewire/Server/Advanced.php @@ -42,10 +42,9 @@ class Advanced extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { - $this->authorize('update', $this->server); $this->validate(); $this->server->settings->concurrent_builds = $this->concurrentBuilds; $this->server->settings->dynamic_timeout = $this->dynamicTimeout; @@ -67,6 +66,7 @@ class Advanced extends Component public function instantSave() { try { + $this->authorize('update', $this->server); $this->syncData(true); $this->dispatch('success', 'Server updated.'); } catch (\Throwable $e) { @@ -81,6 +81,7 @@ class Advanced extends Component $this->serverDiskUsageCheckFrequency = $this->server->settings->getOriginal('server_disk_usage_check_frequency'); throw new \Exception('Invalid Cron / Human expression for Disk Usage Check Frequency.'); } + $this->authorize('update', $this->server); $this->syncData(true); $this->dispatch('success', 'Server updated.'); } catch (\Throwable $e) { diff --git a/app/Livewire/Server/DockerCleanup.php b/app/Livewire/Server/DockerCleanup.php index 12d111d219..40dd92d87e 100644 --- a/app/Livewire/Server/DockerCleanup.php +++ b/app/Livewire/Server/DockerCleanup.php @@ -97,10 +97,9 @@ class DockerCleanup extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { - $this->authorize('update', $this->server); $this->validate(); $this->server->settings->force_docker_cleanup = $this->forceDockerCleanup; $this->server->settings->docker_cleanup_frequency = $this->dockerCleanupFrequency; @@ -122,6 +121,7 @@ class DockerCleanup extends Component public function instantSave() { try { + $this->authorize('update', $this->server); $this->syncData(true); $this->dispatch('success', 'Server updated.'); } catch (\Throwable $e) { @@ -147,6 +147,7 @@ class DockerCleanup extends Component $this->dockerCleanupFrequency = $this->server->settings->getOriginal('docker_cleanup_frequency'); throw new \Exception('Invalid Cron / Human expression for Docker Cleanup Frequency.'); } + $this->authorize('update', $this->server); $this->syncData(true); $this->dispatch('success', 'Server updated.'); } catch (\Throwable $e) { diff --git a/app/Livewire/Server/LogDrains.php b/app/Livewire/Server/LogDrains.php index 3af0a22610..5ce657f001 100644 --- a/app/Livewire/Server/LogDrains.php +++ b/app/Livewire/Server/LogDrains.php @@ -52,7 +52,7 @@ class LogDrains extends Component } } - public function syncDataNewRelic(bool $toModel = false) + private function syncDataNewRelic(bool $toModel = false): void { if ($toModel) { $this->server->settings->is_logdrain_newrelic_enabled = $this->isLogDrainNewRelicEnabled; @@ -65,7 +65,7 @@ class LogDrains extends Component } } - public function syncDataAxiom(bool $toModel = false) + private function syncDataAxiom(bool $toModel = false): void { if ($toModel) { $this->server->settings->is_logdrain_axiom_enabled = $this->isLogDrainAxiomEnabled; @@ -78,7 +78,7 @@ class LogDrains extends Component } } - public function syncDataCustom(bool $toModel = false) + private function syncDataCustom(bool $toModel = false): void { if ($toModel) { $this->server->settings->is_logdrain_custom_enabled = $this->isLogDrainCustomEnabled; @@ -91,7 +91,7 @@ class LogDrains extends Component } } - public function syncData(bool $toModel = false, ?string $type = null) + private function syncData(bool $toModel = false, ?string $type = null): void { if ($toModel) { $this->customValidation(); diff --git a/app/Livewire/Server/Security/TerminalAccess.php b/app/Livewire/Server/Security/TerminalAccess.php index b4b99a3e7c..999482dcff 100644 --- a/app/Livewire/Server/Security/TerminalAccess.php +++ b/app/Livewire/Server/Security/TerminalAccess.php @@ -62,10 +62,9 @@ class TerminalAccess extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { - $this->authorize('update', $this->server); $this->validate(); // No other fields to sync for terminal access } else { diff --git a/app/Livewire/Server/Sentinel.php b/app/Livewire/Server/Sentinel.php index a69eb3f807..2d4742eb67 100644 --- a/app/Livewire/Server/Sentinel.php +++ b/app/Livewire/Server/Sentinel.php @@ -54,10 +54,9 @@ class Sentinel extends Component $this->syncData(); } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { - $this->authorize('update', $this->server); $this->validate(); $this->server->settings->is_metrics_enabled = $this->isMetricsEnabled; $this->server->settings->sentinel_token = $this->sentinelToken; @@ -145,6 +144,7 @@ class Sentinel extends Component public function submit() { try { + $this->authorize('update', $this->server); $this->syncData(true); $this->dispatch('success', 'Sentinel settings updated. Restarting Sentinel.'); } catch (\Throwable $e) { @@ -155,6 +155,7 @@ class Sentinel extends Component public function instantSave() { try { + $this->authorize('update', $this->server); $this->syncData(true); $this->restartSentinel(); } catch (\Throwable $e) { diff --git a/app/Livewire/Server/Show.php b/app/Livewire/Server/Show.php index 017beb3719..38bbe24e7c 100644 --- a/app/Livewire/Server/Show.php +++ b/app/Livewire/Server/Show.php @@ -230,12 +230,10 @@ class Show extends Component ->toArray(); } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); - - $this->authorize('update', $this->server); $foundServer = Server::where('ip', $this->ip) ->where('id', '!=', $this->server->id) ->first(); @@ -363,6 +361,7 @@ class Show extends Component public function checkLocalhostConnection() { try { + $this->authorize('update', $this->server); $this->syncData(true); ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection(); if ($uptime) { @@ -479,6 +478,7 @@ class Show extends Component public function instantSave() { try { + $this->authorize('update', $this->server); $this->syncData(true); } catch (\Throwable $e) { return handleError($e, $this); @@ -694,6 +694,7 @@ class Show extends Component public function submit() { try { + $this->authorize('update', $this->server); $this->syncData(true); $this->dispatch('success', 'Server settings updated.'); } catch (\Throwable $e) { diff --git a/app/Livewire/Server/Swarm.php b/app/Livewire/Server/Swarm.php index e3e441ea0e..af785a8c20 100644 --- a/app/Livewire/Server/Swarm.php +++ b/app/Livewire/Server/Swarm.php @@ -29,10 +29,9 @@ class Swarm extends Component } } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { - $this->authorize('update', $this->server); $this->server->settings->is_swarm_manager = $this->isSwarmManager; $this->server->settings->is_swarm_worker = $this->isSwarmWorker; $this->server->settings->save(); @@ -45,6 +44,7 @@ class Swarm extends Component public function instantSave() { try { + $this->authorize('update', $this->server); $this->syncData(true); $this->dispatch('success', 'Swarm settings updated.'); } catch (\Throwable $e) { diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php index 9bca0db2e3..4b5857db50 100644 --- a/app/Livewire/SettingsEmail.php +++ b/app/Livewire/SettingsEmail.php @@ -74,7 +74,7 @@ class SettingsEmail extends Component $this->testEmailAddress = auth()->user()->email; } - public function syncData(bool $toModel = false) + private function syncData(bool $toModel = false): void { if ($toModel) { $this->validate(); diff --git a/tests/Feature/Authorization/ApplicationConfigAuthorizationTest.php b/tests/Feature/Authorization/ApplicationConfigAuthorizationTest.php index 31c30c1245..5842769908 100644 --- a/tests/Feature/Authorization/ApplicationConfigAuthorizationTest.php +++ b/tests/Feature/Authorization/ApplicationConfigAuthorizationTest.php @@ -13,6 +13,7 @@ use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; +use Livewire\Exceptions\MethodNotFoundException; use Livewire\Livewire; uses(RefreshDatabase::class); @@ -188,6 +189,21 @@ test('member cannot submit application advanced settings', function () { ->assertDispatched('error'); }); +test('the private application advanced syncData helper is not remotely callable', function () { + $this->actingAs($this->member); + session(['currentTeam' => $this->team]); + + $original = (bool) $this->application->settings->is_force_https_enabled; + + $component = Livewire::test(ApplicationAdvanced::class, ['application' => $this->application]) + ->set('isForceHttpsEnabled', ! $original); + + expect(fn () => $component->call('syncData', true)) + ->toThrow(MethodNotFoundException::class); + + expect((bool) $this->application->settings->fresh()->is_force_https_enabled)->toBe($original); +}); + // --- Application Rollback Livewire actions --- test('member cannot save rollback settings', function () { diff --git a/tests/Feature/Authorization/NotificationAuthorizationTest.php b/tests/Feature/Authorization/NotificationAuthorizationTest.php index 84b4ace6fa..d4fa8dfc94 100644 --- a/tests/Feature/Authorization/NotificationAuthorizationTest.php +++ b/tests/Feature/Authorization/NotificationAuthorizationTest.php @@ -10,6 +10,7 @@ use App\Models\InstanceSettings; use App\Models\Team; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Livewire\Exceptions\MethodNotFoundException; use Livewire\Livewire; uses(RefreshDatabase::class); @@ -43,13 +44,14 @@ test('member cannot send test notification on discord', function () { ->assertDispatched('error'); }); -test('member cannot update discord notification settings', function () { +test('the private discord syncData helper is not remotely callable', function () { $this->actingAs($this->member); session(['currentTeam' => $this->team]); - Livewire::test(DiscordNotification::class) - ->call('syncData', true) - ->assertForbidden(); + $component = Livewire::test(DiscordNotification::class); + + expect(fn () => $component->call('syncData', true)) + ->toThrow(MethodNotFoundException::class); }); test('admin can update discord notification settings', function () { @@ -78,13 +80,14 @@ test('member cannot send test notification on slack', function () { ->assertDispatched('error'); }); -test('member cannot update slack notification settings', function () { +test('the private slack syncData helper is not remotely callable', function () { $this->actingAs($this->member); session(['currentTeam' => $this->team]); - Livewire::test(SlackNotification::class) - ->call('syncData', true) - ->assertForbidden(); + $component = Livewire::test(SlackNotification::class); + + expect(fn () => $component->call('syncData', true)) + ->toThrow(MethodNotFoundException::class); }); test('admin can update slack notification settings', function () { @@ -114,13 +117,14 @@ test('member cannot send test notification on telegram', function () { ->assertDispatched('error'); }); -test('member cannot update telegram notification settings', function () { +test('the private telegram syncData helper is not remotely callable', function () { $this->actingAs($this->member); session(['currentTeam' => $this->team]); - Livewire::test(TelegramNotification::class) - ->call('syncData', true) - ->assertForbidden(); + $component = Livewire::test(TelegramNotification::class); + + expect(fn () => $component->call('syncData', true)) + ->toThrow(MethodNotFoundException::class); }); test('admin can update telegram notification settings', function () { @@ -138,7 +142,7 @@ test('telegram restart limit thread id accepts 255 characters', function () { Livewire::test(TelegramNotification::class) ->set('telegramNotificationsRestartLimitReachedThreadId', str_repeat('a', 255)) - ->call('syncData', true) + ->call('submit') ->assertHasNoErrors(['telegramNotificationsRestartLimitReachedThreadId']); expect($this->team->telegramNotificationSettings->fresh()->telegram_notifications_restart_limit_reached_thread_id) @@ -151,8 +155,11 @@ test('telegram restart limit thread id rejects 256 characters', function () { Livewire::test(TelegramNotification::class) ->set('telegramNotificationsRestartLimitReachedThreadId', str_repeat('a', 256)) - ->call('syncData', true) - ->assertHasErrors(['telegramNotificationsRestartLimitReachedThreadId' => 'max']); + ->call('submit') + ->assertDispatched('error'); + + expect($this->team->telegramNotificationSettings->fresh()->telegram_notifications_restart_limit_reached_thread_id) + ->not->toBe(str_repeat('a', 256)); }); test('member cannot view telegram thread ids', function () { @@ -226,13 +233,14 @@ test('member cannot send test email notification', function () { ->assertDispatched('error'); }); -test('member cannot update email notification settings', function () { +test('the private email syncData helper is not remotely callable', function () { $this->actingAs($this->member); session(['currentTeam' => $this->team]); - Livewire::test(EmailNotification::class) - ->call('syncData', true) - ->assertForbidden(); + $component = Livewire::test(EmailNotification::class); + + expect(fn () => $component->call('syncData', true)) + ->toThrow(MethodNotFoundException::class); }); test('member cannot update smtp email transport directly', function () { @@ -330,13 +338,14 @@ test('member cannot send test notification on pushover', function () { ->assertDispatched('error'); }); -test('member cannot update pushover notification settings', function () { +test('the private pushover syncData helper is not remotely callable', function () { $this->actingAs($this->member); session(['currentTeam' => $this->team]); - Livewire::test(PushoverNotification::class) - ->call('syncData', true) - ->assertForbidden(); + $component = Livewire::test(PushoverNotification::class); + + expect(fn () => $component->call('syncData', true)) + ->toThrow(MethodNotFoundException::class); }); test('admin can update pushover notification settings', function () { @@ -365,13 +374,14 @@ test('member cannot send test notification on webhook', function () { ->assertDispatched('error'); }); -test('member cannot update webhook notification settings', function () { +test('the private webhook syncData helper is not remotely callable', function () { $this->actingAs($this->member); session(['currentTeam' => $this->team]); - Livewire::test(WebhookNotification::class) - ->call('syncData', true) - ->assertForbidden(); + $component = Livewire::test(WebhookNotification::class); + + expect(fn () => $component->call('syncData', true)) + ->toThrow(MethodNotFoundException::class); }); test('admin can update webhook notification settings', function () { diff --git a/tests/Feature/Authorization/ScheduledTaskSyncAuthorizationTest.php b/tests/Feature/Authorization/ScheduledTaskSyncAuthorizationTest.php new file mode 100644 index 0000000000..5a44b26c40 --- /dev/null +++ b/tests/Feature/Authorization/ScheduledTaskSyncAuthorizationTest.php @@ -0,0 +1,180 @@ +withoutVite(); + InstanceSettings::forceCreate(['id' => 0]); + + $this->team = Team::factory()->create(); + + $this->owner = User::factory()->create(); + $this->owner->teams()->attach($this->team, ['role' => 'owner']); + + $this->member = User::factory()->create(); + $this->member->teams()->attach($this->team, ['role' => 'member']); + + $this->server = Server::factory()->create(['team_id' => $this->team->id]); + $this->destination = StandaloneDocker::where('server_id', $this->server->id)->first(); + $this->project = Project::factory()->create(['team_id' => $this->team->id]); + $this->environment = $this->project->environments()->first(); + $this->application = Application::factory()->create([ + 'environment_id' => $this->environment->id, + 'destination_id' => $this->destination->id, + 'destination_type' => $this->destination->getMorphClass(), + ]); + + $this->task = ScheduledTask::factory()->create([ + 'team_id' => $this->team->id, + 'application_id' => $this->application->id, + 'name' => 'validate-benign', + 'command' => 'echo scheduled-ok', + 'frequency' => '0 0 * * *', + 'timeout' => 300, + 'enabled' => true, + ]); +}); + +afterEach(function () { + Event::forget(RouteMatched::class); +}); + +function bindApplicationScheduledTaskRoute(Application $application, ScheduledTask $task): void +{ + $application->loadMissing('environment.project'); + + Event::listen(RouteMatched::class, function (RouteMatched $event) use ($application, $task): void { + $event->route->setParameter('task_uuid', $task->uuid); + $event->route->setParameter('project_uuid', $application->environment->project->uuid); + $event->route->setParameter('environment_uuid', $application->environment->uuid); + $event->route->setParameter('application_uuid', $application->uuid); + }); +} + +function originalScheduledTaskState(ScheduledTask $task): array +{ + return $task->only(['name', 'command', 'frequency', 'timeout', 'enabled', 'container']); +} + +test('read-only member can view an existing scheduled task', function () { + $this->actingAs($this->member); + session(['currentTeam' => $this->team]); + bindApplicationScheduledTaskRoute($this->application, $this->task); + + Livewire::test(Show::class) + ->assertSuccessful() + ->assertSet('name', 'validate-benign') + ->assertSet('command', 'echo scheduled-ok') + ->assertSet('frequency', '0 0 * * *'); +}); + +test('the private syncData helper is not remotely callable through Livewire', function () { + $this->actingAs($this->member); + session(['currentTeam' => $this->team]); + bindApplicationScheduledTaskRoute($this->application, $this->task); + + $original = originalScheduledTaskState($this->task); + + $component = Livewire::test(Show::class) + ->set('name', 'member-edited') + ->set('command', 'echo member-edited') + ->set('frequency', '* * * * *') + ->set('timeout', 120) + ->set('isEnabled', true); + + expect(fn () => $component->call('syncData', true)) + ->toThrow(MethodNotFoundException::class); + + expect(originalScheduledTaskState($this->task->fresh()))->toBe($original); +}); + +test('read-only member cannot persist scheduled task changes through submit', function () { + $this->actingAs($this->member); + session(['currentTeam' => $this->team]); + bindApplicationScheduledTaskRoute($this->application, $this->task); + + $original = originalScheduledTaskState($this->task); + + Livewire::test(Show::class) + ->set('name', 'member-edited') + ->set('command', 'echo member-edited') + ->set('frequency', '* * * * *') + ->call('submit'); + + expect(originalScheduledTaskState($this->task->fresh()))->toBe($original); +}); + +test('the private saveScheduledTask helper is not remotely callable through Livewire', function () { + $this->actingAs($this->member); + session(['currentTeam' => $this->team]); + + $component = Livewire::test(Add::class, [ + 'id' => (string) $this->application->id, + 'type' => 'application', + 'containerNames' => collect(), + ]) + ->set('name', 'member-created') + ->set('command', 'id') + ->set('frequency', '* * * * *') + ->set('timeout', 300); + + expect(fn () => $component->call('saveScheduledTask')) + ->toThrow(MethodNotFoundException::class); + + expect(ScheduledTask::query()->where('name', 'member-created')->exists())->toBeFalse(); +}); + +test('read-only member cannot create a scheduled task through submit', function () { + $this->actingAs($this->member); + session(['currentTeam' => $this->team]); + + Livewire::test(Add::class, [ + 'id' => (string) $this->application->id, + 'type' => 'application', + 'containerNames' => collect(), + ]) + ->set('name', 'member-created') + ->set('command', 'id') + ->set('frequency', '* * * * *') + ->set('timeout', 300) + ->call('submit'); + + expect(ScheduledTask::query()->where('name', 'member-created')->exists())->toBeFalse(); +}); + +test('admin can persist scheduled task changes through submit', function () { + $this->actingAs($this->owner); + session(['currentTeam' => $this->team]); + bindApplicationScheduledTaskRoute($this->application, $this->task); + + Livewire::test(Show::class) + ->set('name', 'updated-by-admin') + ->set('command', 'echo admin-ok') + ->set('frequency', '0 1 * * *') + ->set('timeout', 600) + ->call('submit') + ->assertSuccessful(); + + $this->task->refresh(); + + expect($this->task->name)->toBe('updated-by-admin') + ->and($this->task->command)->toBe('echo admin-ok') + ->and($this->task->frequency)->toBe('0 1 * * *') + ->and($this->task->timeout)->toBe(600); +}); diff --git a/tests/Feature/Authorization/ServerAuthorizationTest.php b/tests/Feature/Authorization/ServerAuthorizationTest.php index c9e7ec10d6..63e15be2fe 100644 --- a/tests/Feature/Authorization/ServerAuthorizationTest.php +++ b/tests/Feature/Authorization/ServerAuthorizationTest.php @@ -3,6 +3,7 @@ use App\Http\Middleware\PreventRequestsDuringMaintenance; use App\Livewire\Server\Create as ServerCreate; use App\Livewire\Server\Index as ServerIndex; +use App\Livewire\Server\LogDrains; use App\Livewire\Server\Navbar as ServerNavbar; use App\Models\CloudProviderToken; use App\Models\InstanceSettings; @@ -12,6 +13,7 @@ use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; +use Livewire\Exceptions\MethodNotFoundException; use Livewire\Livewire; uses(RefreshDatabase::class); @@ -79,6 +81,20 @@ test('member can view server', function () { expect($this->member->can('view', $this->server))->toBeTrue(); }); +test('the private log drain syncData helper is not remotely callable', function () { + $this->withoutVite(); + $this->actingAs($this->member); + session(['currentTeam' => $this->team]); + + $component = Livewire::test(LogDrains::class, ['server_uuid' => $this->server->uuid]) + ->set('isLogDrainAxiomEnabled', true); + + expect(fn () => $component->call('syncData', true)) + ->toThrow(MethodNotFoundException::class); + + expect((bool) $this->server->settings->fresh()->is_logdrain_axiom_enabled)->toBeFalse(); +}); + // --- Server Policy: manageProxy --- test('admin can manage proxy', function () { diff --git a/tests/Unit/LivewireSyncDataAuthorizationTest.php b/tests/Unit/LivewireSyncDataAuthorizationTest.php new file mode 100644 index 0000000000..7ac6136d5c --- /dev/null +++ b/tests/Unit/LivewireSyncDataAuthorizationTest.php @@ -0,0 +1,196 @@ + */ +function livewireSyncDataMethods(string $directory): array +{ + $methods = []; + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory)); + $parser = (new ParserFactory)->createForNewestSupportedVersion(); + $finder = new NodeFinder; + + foreach ($iterator as $file) { + if (! $file->isFile() || $file->getExtension() !== 'php') { + continue; + } + $path = $file->getPathname(); + $contents = file_get_contents($path); + if ($contents === false) { + continue; + } + foreach ($finder->findInstanceOf($parser->parse($contents) ?? [], ClassMethod::class) as $method) { + if (preg_match('/^sync(Data|DatabaseData|ApplicationData)/', $method->name->toString())) { + $methods[] = ['path' => $path, 'method' => $method]; + } + } + } + + usort($methods, fn (array $a, array $b): int => [$a['path'], $a['method']->name->toString()] <=> [$b['path'], $b['method']->name->toString()]); + + return $methods; +} + +function livewireMethodCallName(MethodCall $call): ?string +{ + return $call->name instanceof Identifier ? $call->name->toString() : null; +} + +function livewireIsWriteSyncCall(MethodCall $call, string $syncMethod): bool +{ + if (livewireMethodCallName($call) !== $syncMethod) { + return false; + } + foreach ($call->args as $position => $argument) { + if (($position === 0 || $argument->name?->toString() === 'toModel') && $argument->value instanceof ConstFetch && $argument->value->name->toLowerString() === 'true') { + return true; + } + } + + return false; +} + +/** @param list $statements @param list $unauthorizedLines */ +function livewireScanStatements(array $statements, string $syncMethod, bool $authorized, array &$unauthorizedLines): bool +{ + foreach ($statements as $statement) { + $authorized = livewireScanNode($statement, $syncMethod, $authorized, $unauthorizedLines); + } + + return $authorized; +} + +/** @param list $unauthorizedLines */ +function livewireScanNode(mixed $node, string $syncMethod, bool $authorized, array &$unauthorizedLines): bool +{ + if (! $node instanceof Node && ! is_array($node)) { + return $authorized; + } + if (is_array($node)) { + return livewireScanStatements($node, $syncMethod, $authorized, $unauthorizedLines); + } + if ($node instanceof TryCatch) { + $tryAuthorized = livewireScanStatements($node->stmts, $syncMethod, $authorized, $unauthorizedLines); + $allCatchesTerminate = $node->catches !== []; + foreach ($node->catches as $catch) { + livewireScanStatements($catch->stmts, $syncMethod, $authorized, $unauthorizedLines); + $allCatchesTerminate = $allCatchesTerminate && collect($catch->stmts)->contains(fn (Node\Stmt $statement): bool => $statement instanceof Return_); + } + if ($node->finally !== null) { + livewireScanStatements($node->finally->stmts, $syncMethod, $authorized, $unauthorizedLines); + } + + return $allCatchesTerminate ? $tryAuthorized : $authorized; + } + if ($node instanceof If_) { + livewireScanNode($node->cond, $syncMethod, $authorized, $unauthorizedLines); + livewireScanStatements($node->stmts, $syncMethod, $authorized, $unauthorizedLines); + foreach ($node->elseifs as $elseif) { + livewireScanNode($elseif->cond, $syncMethod, $authorized, $unauthorizedLines); + livewireScanStatements($elseif->stmts, $syncMethod, $authorized, $unauthorizedLines); + } + if ($node->else !== null) { + livewireScanStatements($node->else->stmts, $syncMethod, $authorized, $unauthorizedLines); + } + + return $authorized; + } + if ($node instanceof MethodCall) { + $name = livewireMethodCallName($node); + if ($name === 'authorize') { + return true; + } + if (livewireIsWriteSyncCall($node, $syncMethod) && ! $authorized) { + $unauthorizedLines[] = $node->getStartLine(); + } + } + foreach ($node->getSubNodeNames() as $name) { + $authorized = livewireScanNode($node->{$name}, $syncMethod, $authorized, $unauthorizedLines); + } + + return $authorized; +} + +/** @return list */ +function livewireUnauthorizedSyncWriteLines(ClassMethod $method, string $syncMethod): array +{ + $lines = []; + livewireScanStatements($method->stmts ?? [], $syncMethod, false, $lines); + + return $lines; +} + +function livewireMethodFromSource(string $source, string $method): ClassMethod +{ + $nodes = (new ParserFactory)->createForNewestSupportedVersion()->parse($source) ?? []; + + return (new NodeFinder)->findFirst($nodes, fn (Node $node): bool => $node instanceof ClassMethod && $node->name->toString() === $method); +} + +it('keeps every Livewire syncData helper private', function () { + $violations = []; + foreach (livewireSyncDataMethods(dirname(__DIR__, 2).'/app/Livewire') as $syncMethod) { + if (! $syncMethod['method']->isPrivate()) { + $relative = str_replace(dirname(__DIR__, 2).'/', '', $syncMethod['path']); + $violations[] = "{$relative}::{$syncMethod['method']->name}() is not private"; + } + } + expect($violations)->toBeEmpty("Livewire syncData helpers must be private:\n".implode("\n", $violations)); +}); + +it('discovers files that only define syncApplicationData helpers', function () { + $directory = sys_get_temp_dir().'/livewire-sync-'.uniqid(); + mkdir($directory); + file_put_contents($directory.'/Example.php', 'toHaveCount(1) + ->and($methods[0]['method']->name->toString())->toBe('syncApplicationData'); +}); + +it('detects named write arguments and does not carry authorization into catch paths', function () { + $method = livewireMethodFromSource(<<<'PHP' + authorize('update', $this->resource); + $this->syncData(toModel: true); + } catch (Throwable $exception) { + $this->syncData(toModel: true); + } + } + } + PHP, 'instantSave'); + + expect(livewireUnauthorizedSyncWriteLines($method, 'syncData'))->toHaveCount(1); +}); + +it('authorizes before every syncData write call', function () { + $violations = []; + $root = dirname(__DIR__, 2); + foreach (livewireSyncDataMethods($root.'/app/Livewire') as $syncMethod) { + $contents = file_get_contents($syncMethod['path']); + $nodes = (new ParserFactory)->createForNewestSupportedVersion()->parse($contents) ?? []; + foreach ((new NodeFinder)->findInstanceOf($nodes, ClassMethod::class) as $caller) { + foreach (livewireUnauthorizedSyncWriteLines($caller, $syncMethod['method']->name->toString()) as $line) { + $relative = str_replace($root.'/', '', $syncMethod['path']); + $violations[] = "{$relative}:{$line}::{$caller->name}() calls {$syncMethod['method']->name}(true) without prior authorization"; + } + } + } + expect($violations)->toBeEmpty("Missing authorization before syncData write calls:\n".implode("\n", array_unique($violations))); +});