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,
];
/**