diff --git a/app/Livewire/Project/Application/Advanced.php b/app/Livewire/Project/Application/Advanced.php index 6020af715..862dc20d8 100644 --- a/app/Livewire/Project/Application/Advanced.php +++ b/app/Livewire/Project/Application/Advanced.php @@ -3,11 +3,14 @@ namespace App\Livewire\Project\Application; use App\Models\Application; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Attributes\Validate; use Livewire\Component; class Advanced extends Component { + use AuthorizesRequests; + public Application $application; #[Validate(['boolean'])] @@ -142,6 +145,7 @@ class Advanced extends Component public function instantSave() { try { + $this->authorize('update', $this->application); $reset = false; if ($this->isLogDrainEnabled) { if (! $this->application->destination->server->isLogDrainEnabled()) { @@ -180,6 +184,7 @@ class Advanced extends Component public function submit() { try { + $this->authorize('update', $this->application); if ($this->gpuCount && $this->gpuDeviceIds) { $this->dispatch('error', 'You cannot set both GPU count and GPU device IDs.'); $this->gpuCount = null; @@ -197,33 +202,39 @@ class Advanced extends Component public function saveCustomName() { - if (str($this->customInternalName)->isNotEmpty()) { - $this->customInternalName = str($this->customInternalName)->slug()->value(); - } else { - $this->customInternalName = null; - } - if (is_null($this->customInternalName)) { + try { + $this->authorize('update', $this->application); + + if (str($this->customInternalName)->isNotEmpty()) { + $this->customInternalName = str($this->customInternalName)->slug()->value(); + } else { + $this->customInternalName = null; + } + if (is_null($this->customInternalName)) { + $this->syncData(true); + $this->dispatch('success', 'Custom name saved.'); + + return; + } + $customInternalName = $this->customInternalName; + $server = $this->application->destination->server; + $allApplications = $server->applications(); + + $foundSameInternalName = $allApplications->filter(function ($application) { + return $application->id !== $this->application->id && $application->settings->custom_internal_name === $this->customInternalName; + }); + if ($foundSameInternalName->isNotEmpty()) { + $this->dispatch('error', 'This custom container name is already in use by another application on this server.'); + $this->customInternalName = $customInternalName; + $this->syncData(true); + + return; + } $this->syncData(true); $this->dispatch('success', 'Custom name saved.'); - - return; + } catch (\Throwable $e) { + return handleError($e, $this); } - $customInternalName = $this->customInternalName; - $server = $this->application->destination->server; - $allApplications = $server->applications(); - - $foundSameInternalName = $allApplications->filter(function ($application) { - return $application->id !== $this->application->id && $application->settings->custom_internal_name === $this->customInternalName; - }); - if ($foundSameInternalName->isNotEmpty()) { - $this->dispatch('error', 'This custom container name is already in use by another application on this server.'); - $this->customInternalName = $customInternalName; - $this->syncData(true); - - return; - } - $this->syncData(true); - $this->dispatch('success', 'Custom name saved.'); } public function render() diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index e7d464ba4..cfee15efc 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -5,6 +5,7 @@ namespace App\Livewire\Project\Application; use App\Actions\Application\GenerateConfig; use App\Models\Application; use App\Support\ValidationPatterns; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Support\Collection; use Livewire\Component; use Spatie\Url\Url; @@ -12,6 +13,8 @@ use Visus\Cuid2\Cuid2; class General extends Component { + use AuthorizesRequests; + public string $applicationId; public Application $application; @@ -224,37 +227,44 @@ class General extends Component public function instantSave() { - if ($this->application->settings->isDirty('is_spa')) { - $this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static'); - } - if ($this->application->isDirty('is_http_basic_auth_enabled')) { - $this->application->save(); - } - $this->application->settings->save(); - $this->dispatch('success', 'Settings saved.'); - $this->application->refresh(); + try { + $this->authorize('update', $this->application); - // If port_exposes changed, reset default labels - if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) { - $this->resetDefaultLabels(false); - } - if ($this->is_preserve_repository_enabled !== $this->application->settings->is_preserve_repository_enabled) { - if ($this->application->settings->is_preserve_repository_enabled === false) { - $this->application->fileStorages->each(function ($storage) { - $storage->is_based_on_git = $this->application->settings->is_preserve_repository_enabled; - $storage->save(); - }); + if ($this->application->settings->isDirty('is_spa')) { + $this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static'); } - } - if ($this->application->settings->is_container_label_readonly_enabled) { - $this->resetDefaultLabels(false); - } + if ($this->application->isDirty('is_http_basic_auth_enabled')) { + $this->application->save(); + } + $this->application->settings->save(); + $this->dispatch('success', 'Settings saved.'); + $this->application->refresh(); + // If port_exposes changed, reset default labels + if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) { + $this->resetDefaultLabels(false); + } + if ($this->is_preserve_repository_enabled !== $this->application->settings->is_preserve_repository_enabled) { + if ($this->application->settings->is_preserve_repository_enabled === false) { + $this->application->fileStorages->each(function ($storage) { + $storage->is_based_on_git = $this->application->settings->is_preserve_repository_enabled; + $storage->save(); + }); + } + } + if ($this->application->settings->is_container_label_readonly_enabled) { + $this->resetDefaultLabels(false); + } + } catch (\Throwable $e) { + return handleError($e, $this); + } } public function loadComposeFile($isInit = false, $showToast = true) { try { + $this->authorize('update', $this->application); + if ($isInit && $this->application->docker_compose_raw) { return; } @@ -293,35 +303,41 @@ class General extends Component public function generateDomain(string $serviceName) { - $uuid = new Cuid2; - $domain = generateUrl(server: $this->application->destination->server, random: $uuid); - $sanitizedKey = str($serviceName)->slug('_')->toString(); - $this->parsedServiceDomains[$sanitizedKey]['domain'] = $domain; + try { + $this->authorize('update', $this->application); - // Convert back to original service names for storage - $originalDomains = []; - foreach ($this->parsedServiceDomains as $key => $value) { - // Find the original service name by checking parsed services - $originalServiceName = $key; - if (isset($this->parsedServices['services'])) { - foreach ($this->parsedServices['services'] as $originalName => $service) { - if (str($originalName)->slug('_')->toString() === $key) { - $originalServiceName = $originalName; - break; + $uuid = new Cuid2; + $domain = generateUrl(server: $this->application->destination->server, random: $uuid); + $sanitizedKey = str($serviceName)->slug('_')->toString(); + $this->parsedServiceDomains[$sanitizedKey]['domain'] = $domain; + + // Convert back to original service names for storage + $originalDomains = []; + foreach ($this->parsedServiceDomains as $key => $value) { + // Find the original service name by checking parsed services + $originalServiceName = $key; + if (isset($this->parsedServices['services'])) { + foreach ($this->parsedServices['services'] as $originalName => $service) { + if (str($originalName)->slug('_')->toString() === $key) { + $originalServiceName = $originalName; + break; + } } } + $originalDomains[$originalServiceName] = $value; } - $originalDomains[$originalServiceName] = $value; - } - $this->application->docker_compose_domains = json_encode($originalDomains); - $this->application->save(); - $this->dispatch('success', 'Domain generated.'); - if ($this->application->build_pack === 'dockercompose') { - $this->loadComposeFile(showToast: false); - } + $this->application->docker_compose_domains = json_encode($originalDomains); + $this->application->save(); + $this->dispatch('success', 'Domain generated.'); + if ($this->application->build_pack === 'dockercompose') { + $this->loadComposeFile(showToast: false); + } - return $domain; + return $domain; + } catch (\Throwable $e) { + return handleError($e, $this); + } } public function updatedApplicationBaseDirectory() @@ -374,21 +390,33 @@ class General extends Component public function getWildcardDomain() { - $server = data_get($this->application, 'destination.server'); - if ($server) { - $fqdn = generateFqdn(server: $server, random: $this->application->uuid, parserVersion: $this->application->compose_parsing_version); - $this->application->fqdn = $fqdn; - $this->application->save(); - $this->resetDefaultLabels(); - $this->dispatch('success', 'Wildcard domain generated.'); + try { + $this->authorize('update', $this->application); + + $server = data_get($this->application, 'destination.server'); + if ($server) { + $fqdn = generateFqdn(server: $server, random: $this->application->uuid, parserVersion: $this->application->compose_parsing_version); + $this->application->fqdn = $fqdn; + $this->application->save(); + $this->resetDefaultLabels(); + $this->dispatch('success', 'Wildcard domain generated.'); + } + } catch (\Throwable $e) { + return handleError($e, $this); } } public function generateNginxConfiguration($type = 'static') { - $this->application->custom_nginx_configuration = defaultNginxConfiguration($type); - $this->application->save(); - $this->dispatch('success', 'Nginx configuration generated.'); + try { + $this->authorize('update', $this->application); + + $this->application->custom_nginx_configuration = defaultNginxConfiguration($type); + $this->application->save(); + $this->dispatch('success', 'Nginx configuration generated.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } } public function resetDefaultLabels($manualReset = false) @@ -430,6 +458,8 @@ class General extends Component public function setRedirect() { + $this->authorize('update', $this->application); + try { $has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count(); if ($has_www === 0 && $this->application->redirect === 'www') { @@ -448,6 +478,7 @@ class General extends Component public function submit($showToaster = true) { try { + $this->authorize('update', $this->application); $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim(); $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php index 9fd4da68a..62c93611e 100644 --- a/app/Livewire/Project/Application/Heading.php +++ b/app/Livewire/Project/Application/Heading.php @@ -5,11 +5,14 @@ namespace App\Livewire\Project\Application; use App\Actions\Application\StopApplication; use App\Actions\Docker\GetContainersStatus; use App\Models\Application; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Component; use Visus\Cuid2\Cuid2; class Heading extends Component { + use AuthorizesRequests; + public Application $application; public ?string $lastDeploymentInfo = null; @@ -57,11 +60,15 @@ class Heading extends Component public function force_deploy_without_cache() { + $this->authorize('deploy', $this->application); + $this->deploy(force_rebuild: true); } public function deploy(bool $force_rebuild = false) { + $this->authorize('deploy', $this->application); + if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) { $this->dispatch('error', 'Failed to deploy', 'Please load a Compose file first.'); @@ -110,12 +117,16 @@ class Heading extends Component public function stop() { + $this->authorize('deploy', $this->application); + $this->dispatch('info', 'Gracefully stopping application.
It could take a while depending on the application.'); StopApplication::dispatch($this->application, false, $this->docker_cleanup); } public function restart() { + $this->authorize('deploy', $this->application); + if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) { $this->dispatch('error', 'Failed to deploy', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.
More information here: documentation'); diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index 62b1f1929..5f07b02f3 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -6,12 +6,15 @@ use App\Actions\Docker\GetContainersStatus; use App\Jobs\DeleteResourceJob; use App\Models\Application; use App\Models\ApplicationPreview; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Support\Collection; use Livewire\Component; use Visus\Cuid2\Cuid2; class Previews extends Component { + use AuthorizesRequests; + public Application $application; public string $deployment_uuid; @@ -48,6 +51,7 @@ class Previews extends Component public function save_preview($preview_id) { try { + $this->authorize('update', $this->application); $success = true; $preview = $this->application->previews->find($preview_id); if (data_get_str($preview, 'fqdn')->isNotEmpty()) { @@ -73,29 +77,36 @@ class Previews extends Component public function generate_preview($preview_id) { - $preview = $this->application->previews->find($preview_id); - if (! $preview) { - $this->dispatch('error', 'Preview not found.'); + try { + $this->authorize('update', $this->application); - return; - } - if ($this->application->build_pack === 'dockercompose') { - $preview->generate_preview_fqdn_compose(); + $preview = $this->application->previews->find($preview_id); + if (! $preview) { + $this->dispatch('error', 'Preview not found.'); + + return; + } + if ($this->application->build_pack === 'dockercompose') { + $preview->generate_preview_fqdn_compose(); + $this->application->refresh(); + $this->dispatch('success', 'Domain generated.'); + + return; + } + + $preview->generate_preview_fqdn(); $this->application->refresh(); + $this->dispatch('update_links'); $this->dispatch('success', 'Domain generated.'); - - return; + } catch (\Throwable $e) { + return handleError($e, $this); } - - $preview->generate_preview_fqdn(); - $this->application->refresh(); - $this->dispatch('update_links'); - $this->dispatch('success', 'Domain generated.'); } public function add(int $pull_request_id, ?string $pull_request_html_url = null) { try { + $this->authorize('update', $this->application); if ($this->application->build_pack === 'dockercompose') { $this->setDeploymentUuid(); $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); @@ -131,17 +142,23 @@ class Previews extends Component public function force_deploy_without_cache(int $pull_request_id, ?string $pull_request_html_url = null) { + $this->authorize('deploy', $this->application); + $this->deploy($pull_request_id, $pull_request_html_url, force_rebuild: true); } public function add_and_deploy(int $pull_request_id, ?string $pull_request_html_url = null) { + $this->authorize('deploy', $this->application); + $this->add($pull_request_id, $pull_request_html_url); $this->deploy($pull_request_id, $pull_request_html_url); } public function deploy(int $pull_request_id, ?string $pull_request_html_url = null, bool $force_rebuild = false) { + $this->authorize('deploy', $this->application); + try { $this->setDeploymentUuid(); $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); @@ -184,6 +201,8 @@ class Previews extends Component public function stop(int $pull_request_id) { + $this->authorize('deploy', $this->application); + try { $server = $this->application->destination->server; @@ -206,6 +225,7 @@ class Previews extends Component public function delete(int $pull_request_id) { try { + $this->authorize('delete', $this->application); $preview = ApplicationPreview::where('application_id', $this->application->id) ->where('pull_request_id', $pull_request_id) ->first(); diff --git a/app/Livewire/Project/Application/PreviewsCompose.php b/app/Livewire/Project/Application/PreviewsCompose.php index 5938b2944..0317ba7e7 100644 --- a/app/Livewire/Project/Application/PreviewsCompose.php +++ b/app/Livewire/Project/Application/PreviewsCompose.php @@ -3,12 +3,15 @@ namespace App\Livewire\Project\Application; use App\Models\ApplicationPreview; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Component; use Spatie\Url\Url; use Visus\Cuid2\Cuid2; class PreviewsCompose extends Component { + use AuthorizesRequests; + public $service; public $serviceName; @@ -22,59 +25,71 @@ class PreviewsCompose extends Component public function save() { - $domain = data_get($this->service, 'domain'); - $docker_compose_domains = data_get($this->preview, 'docker_compose_domains'); - $docker_compose_domains = json_decode($docker_compose_domains, true); - $docker_compose_domains[$this->serviceName]['domain'] = $domain; - $this->preview->docker_compose_domains = json_encode($docker_compose_domains); - $this->preview->save(); - $this->dispatch('update_links'); - $this->dispatch('success', 'Domain saved.'); + try { + $this->authorize('update', $this->preview->application); + + $domain = data_get($this->service, 'domain'); + $docker_compose_domains = data_get($this->preview, 'docker_compose_domains'); + $docker_compose_domains = json_decode($docker_compose_domains, true); + $docker_compose_domains[$this->serviceName]['domain'] = $domain; + $this->preview->docker_compose_domains = json_encode($docker_compose_domains); + $this->preview->save(); + $this->dispatch('update_links'); + $this->dispatch('success', 'Domain saved.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } } public function generate() { - $domains = collect(json_decode($this->preview->application->docker_compose_domains)) ?? collect(); - $domain = $domains->first(function ($_, $key) { - return $key === $this->serviceName; - }); + try { + $this->authorize('update', $this->preview->application); - $domain_string = data_get($domain, 'domain'); + $domains = collect(json_decode($this->preview->application->docker_compose_domains)) ?? collect(); + $domain = $domains->first(function ($_, $key) { + return $key === $this->serviceName; + }); - // If no domain is set in the main application, generate a default domain - if (empty($domain_string)) { - $server = $this->preview->application->destination->server; - $template = $this->preview->application->preview_url_template; - $random = new Cuid2; + $domain_string = data_get($domain, 'domain'); - // Generate a unique domain like main app services do - $generated_fqdn = generateFqdn(server: $server, random: $random, parserVersion: $this->preview->application->compose_parsing_version); + // If no domain is set in the main application, generate a default domain + if (empty($domain_string)) { + $server = $this->preview->application->destination->server; + $template = $this->preview->application->preview_url_template; + $random = new Cuid2; - $preview_fqdn = str_replace('{{random}}', $random, $template); - $preview_fqdn = str_replace('{{domain}}', str($generated_fqdn)->after('://'), $preview_fqdn); - $preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn); - $preview_fqdn = str($generated_fqdn)->before('://').'://'.$preview_fqdn; - } else { - // Use the existing domain from the main application - $url = Url::fromString($domain_string); - $template = $this->preview->application->preview_url_template; - $host = $url->getHost(); - $schema = $url->getScheme(); - $random = new Cuid2; - $preview_fqdn = str_replace('{{random}}', $random, $template); - $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); - $preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn); - $preview_fqdn = "$schema://$preview_fqdn"; + // Generate a unique domain like main app services do + $generated_fqdn = generateFqdn(server: $server, random: $random, parserVersion: $this->preview->application->compose_parsing_version); + + $preview_fqdn = str_replace('{{random}}', $random, $template); + $preview_fqdn = str_replace('{{domain}}', str($generated_fqdn)->after('://'), $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn); + $preview_fqdn = str($generated_fqdn)->before('://').'://'.$preview_fqdn; + } else { + // Use the existing domain from the main application + $url = Url::fromString($domain_string); + $template = $this->preview->application->preview_url_template; + $host = $url->getHost(); + $schema = $url->getScheme(); + $random = new Cuid2; + $preview_fqdn = str_replace('{{random}}', $random, $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn"; + } + + // Save the generated domain + $docker_compose_domains = data_get($this->preview, 'docker_compose_domains'); + $docker_compose_domains = json_decode($docker_compose_domains, true); + $docker_compose_domains[$this->serviceName]['domain'] = $this->service->domain = $preview_fqdn; + $this->preview->docker_compose_domains = json_encode($docker_compose_domains); + $this->preview->save(); + + $this->dispatch('update_links'); + $this->dispatch('success', 'Domain generated.'); + } catch (\Throwable $e) { + return handleError($e, $this); } - - // Save the generated domain - $docker_compose_domains = data_get($this->preview, 'docker_compose_domains'); - $docker_compose_domains = json_decode($docker_compose_domains, true); - $docker_compose_domains[$this->serviceName]['domain'] = $this->service->domain = $preview_fqdn; - $this->preview->docker_compose_domains = json_encode($docker_compose_domains); - $this->preview->save(); - - $this->dispatch('update_links'); - $this->dispatch('success', 'Domain generated.'); } } diff --git a/app/Livewire/Project/Application/Rollback.php b/app/Livewire/Project/Application/Rollback.php index ff5db1e08..da67a5707 100644 --- a/app/Livewire/Project/Application/Rollback.php +++ b/app/Livewire/Project/Application/Rollback.php @@ -3,11 +3,14 @@ namespace App\Livewire\Project\Application; use App\Models\Application; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Component; use Visus\Cuid2\Cuid2; class Rollback extends Component { + use AuthorizesRequests; + public Application $application; public $images = []; @@ -23,6 +26,8 @@ class Rollback extends Component public function rollbackImage($commit) { + $this->authorize('deploy', $this->application); + $deployment_uuid = new Cuid2; queue_application_deployment( @@ -43,6 +48,8 @@ class Rollback extends Component public function loadImages($showToast = false) { + $this->authorize('view', $this->application); + try { $image = $this->application->docker_registry_image_name ?? $this->application->uuid; if ($this->application->destination->server->isFunctional()) { diff --git a/app/Livewire/Project/Application/Source.php b/app/Livewire/Project/Application/Source.php index 932a302ad..29be68b6c 100644 --- a/app/Livewire/Project/Application/Source.php +++ b/app/Livewire/Project/Application/Source.php @@ -4,12 +4,15 @@ namespace App\Livewire\Project\Application; use App\Models\Application; use App\Models\PrivateKey; +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] @@ -81,6 +84,7 @@ class Source extends Component public function setPrivateKey(int $privateKeyId) { try { + $this->authorize('update', $this->application); $this->privateKeyId = $privateKeyId; $this->syncData(true); $this->getPrivateKeys(); @@ -94,7 +98,9 @@ class Source extends Component public function submit() { + try { + $this->authorize('update', $this->application); if (str($this->gitCommitSha)->isEmpty()) { $this->gitCommitSha = 'HEAD'; } @@ -107,7 +113,9 @@ class Source extends Component public function changeSource($sourceId, $sourceType) { + try { + $this->authorize('update', $this->application); $this->application->update([ 'source_id' => $sourceId, 'source_type' => $sourceType, diff --git a/app/Livewire/Project/Shared/Danger.php b/app/Livewire/Project/Shared/Danger.php index 94a4c161c..13a9eed94 100644 --- a/app/Livewire/Project/Shared/Danger.php +++ b/app/Livewire/Project/Shared/Danger.php @@ -7,6 +7,7 @@ use App\Models\InstanceSettings; use App\Models\Service; use App\Models\ServiceApplication; use App\Models\ServiceDatabase; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Livewire\Component; @@ -14,6 +15,8 @@ use Visus\Cuid2\Cuid2; class Danger extends Component { + use AuthorizesRequests; + public $resource; public $resourceName; @@ -96,6 +99,7 @@ class Danger extends Component } try { + $this->authorize('delete', $this->resource); $this->resource->delete(); DeleteResourceJob::dispatch( $this->resource, diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 3b6d8b937..88b8a0b17 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -4,11 +4,12 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable; use App\Models\EnvironmentVariable; use App\Traits\EnvironmentVariableProtection; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Component; class All extends Component { - use EnvironmentVariableProtection; + use AuthorizesRequests, EnvironmentVariableProtection; public $resource; @@ -44,6 +45,8 @@ class All extends Component public function instantSave() { + $this->authorize('manageEnvironment', $this->resource); + $this->resource->settings->is_env_sorting_enabled = $this->is_env_sorting_enabled; $this->resource->settings->save(); $this->sortEnvironmentVariables(); @@ -95,6 +98,8 @@ class All extends Component public function submit($data = null) { + $this->authorize('manageEnvironment', $this->resource); + try { if ($data === null) { $this->handleBulkSubmit(); diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index 853dbe57a..c9b341eed 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -12,11 +12,14 @@ use App\Models\Environment; use App\Models\Project; use App\Models\StandaloneDocker; use App\Models\SwarmDocker; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Livewire\Component; use Visus\Cuid2\Cuid2; class ResourceOperations extends Component { + use AuthorizesRequests; + public $resource; public $projectUuid; @@ -45,6 +48,8 @@ class ResourceOperations extends Component public function cloneTo($destination_id) { + $this->authorize('update', $this->resource); + $new_destination = StandaloneDocker::find($destination_id); if (! $new_destination) { $new_destination = SwarmDocker::find($destination_id); @@ -485,6 +490,7 @@ class ResourceOperations extends Component public function moveTo($environment_id) { try { + $this->authorize('update', $this->resource); $new_environment = Environment::findOrFail($environment_id); $this->resource->update([ 'environment_id' => $environment_id, diff --git a/app/Policies/ApplicationPolicy.php b/app/Policies/ApplicationPolicy.php index 05fc289b8..b848a9316 100644 --- a/app/Policies/ApplicationPolicy.php +++ b/app/Policies/ApplicationPolicy.php @@ -4,6 +4,7 @@ namespace App\Policies; use App\Models\Application; use App\Models\User; +use Illuminate\Auth\Access\Response; class ApplicationPolicy { @@ -28,15 +29,23 @@ class ApplicationPolicy */ public function create(User $user): bool { - return true; + if ($user->isAdmin()) { + return true; + } + + return false; } /** * Determine whether the user can update the model. */ - public function update(User $user, Application $application): bool + public function update(User $user, Application $application): Response { - return true; + if ($user->isAdmin()) { + return Response::allow(); + } + + return Response::deny('As a member, you cannot update this application.

You need at least admin or owner permissions.'); } /** @@ -64,6 +73,30 @@ class ApplicationPolicy */ public function forceDelete(User $user, Application $application): bool { - return true; + return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $application->team()->first()->id) !== null; + } + + /** + * Determine whether the user can deploy the application. + */ + public function deploy(User $user, Application $application): bool + { + return $user->teams()->get()->firstWhere('id', $application->team()->first()->id) !== null; + } + + /** + * Determine whether the user can manage deployments. + */ + public function manageDeployments(User $user, Application $application): bool + { + return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $application->team()->first()->id) !== null; + } + + /** + * Determine whether the user can manage environment variables. + */ + public function manageEnvironment(User $user, Application $application): bool + { + return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $application->team()->first()->id) !== null; } } diff --git a/app/Policies/ApplicationPreviewPolicy.php b/app/Policies/ApplicationPreviewPolicy.php new file mode 100644 index 000000000..26cf13fcc --- /dev/null +++ b/app/Policies/ApplicationPreviewPolicy.php @@ -0,0 +1,86 @@ +teams()->get()->firstWhere('id', $applicationPreview->application->team()->first()->id) !== null; + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, ApplicationPreview $applicationPreview): Response + { + if ($user->isAdmin()) { + return Response::allow(); + } + + return Response::deny('As a member, you cannot update this preview.

You need at least admin or owner permissions.'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, ApplicationPreview $applicationPreview): bool + { + return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationPreview->application->team()->first()->id) !== null; + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, ApplicationPreview $applicationPreview): bool + { + return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationPreview->application->team()->first()->id) !== null; + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, ApplicationPreview $applicationPreview): bool + { + return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationPreview->application->team()->first()->id) !== null; + } + + /** + * Determine whether the user can deploy the preview. + */ + public function deploy(User $user, ApplicationPreview $applicationPreview): bool + { + return $user->teams()->get()->firstWhere('id', $applicationPreview->application->team()->first()->id) !== null; + } + + /** + * Determine whether the user can manage preview deployments. + */ + public function manageDeployments(User $user, ApplicationPreview $applicationPreview): bool + { + return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationPreview->application->team()->first()->id) !== null; + } +} diff --git a/app/Policies/ApplicationSettingPolicy.php b/app/Policies/ApplicationSettingPolicy.php new file mode 100644 index 000000000..ff5e81d2f --- /dev/null +++ b/app/Policies/ApplicationSettingPolicy.php @@ -0,0 +1,65 @@ +teams()->get()->firstWhere('id', $applicationSetting->application->team()->first()->id) !== null; + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, ApplicationSetting $applicationSetting): bool + { + return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationSetting->application->team()->first()->id) !== null; + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, ApplicationSetting $applicationSetting): bool + { + return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationSetting->application->team()->first()->id) !== null; + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, ApplicationSetting $applicationSetting): bool + { + return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationSetting->application->team()->first()->id) !== null; + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, ApplicationSetting $applicationSetting): bool + { + return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationSetting->application->team()->first()->id) !== null; + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 30b7cc3c0..a2c02d20a 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -17,6 +17,9 @@ class AuthServiceProvider extends ServiceProvider \App\Models\PrivateKey::class => \App\Policies\PrivateKeyPolicy::class, \App\Models\StandaloneDocker::class => \App\Policies\StandaloneDockerPolicy::class, \App\Models\SwarmDocker::class => \App\Policies\SwarmDockerPolicy::class, + \App\Models\Application::class => \App\Policies\ApplicationPolicy::class, + \App\Models\ApplicationPreview::class => \App\Policies\ApplicationPreviewPolicy::class, + \App\Models\ApplicationSetting::class => \App\Policies\ApplicationSettingPolicy::class, ]; /**