feat(auth): enhance authorization checks in Livewire components for resource management
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
@@ -30,6 +31,12 @@ class Dashboard extends Component
|
||||
|
||||
public function cleanupQueue()
|
||||
{
|
||||
try {
|
||||
$this->authorize('cleanupDeploymentQueue', Application::class);
|
||||
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
Artisan::queue('cleanup:deployment-queue', [
|
||||
'--team-id' => currentTeam()->id,
|
||||
]);
|
||||
|
@@ -3,10 +3,13 @@
|
||||
namespace App\Livewire\Project;
|
||||
|
||||
use App\Models\Environment;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
|
||||
class DeleteEnvironment extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public int $environment_id;
|
||||
|
||||
public bool $disabled = false;
|
||||
@@ -31,6 +34,8 @@ class DeleteEnvironment extends Component
|
||||
'environment_id' => 'required|int',
|
||||
]);
|
||||
$environment = Environment::findOrFail($this->environment_id);
|
||||
$this->authorize('delete', $environment);
|
||||
|
||||
if ($environment->isEmpty()) {
|
||||
$environment->delete();
|
||||
|
||||
|
@@ -3,10 +3,13 @@
|
||||
namespace App\Livewire\Project;
|
||||
|
||||
use App\Models\Project;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
|
||||
class DeleteProject extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public array $parameters;
|
||||
|
||||
public int $project_id;
|
||||
@@ -27,6 +30,8 @@ class DeleteProject extends Component
|
||||
'project_id' => 'required|int',
|
||||
]);
|
||||
$project = Project::findOrFail($this->project_id);
|
||||
$this->authorize('delete', $project);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
$project->delete();
|
||||
|
||||
|
@@ -2,41 +2,19 @@
|
||||
|
||||
namespace App\Livewire\Project\Resource;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
|
||||
class Create extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public $type;
|
||||
|
||||
public $project;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->authorize('create', StandalonePostgresql::class);
|
||||
$this->authorize('create', StandaloneRedis::class);
|
||||
$this->authorize('create', StandaloneMongodb::class);
|
||||
$this->authorize('create', StandaloneMysql::class);
|
||||
$this->authorize('create', StandaloneMariadb::class);
|
||||
$this->authorize('create', StandaloneKeydb::class);
|
||||
$this->authorize('create', StandaloneDragonfly::class);
|
||||
$this->authorize('create', StandaloneClickhouse::class);
|
||||
$this->authorize('create', Service::class);
|
||||
$this->authorize('create', Application::class);
|
||||
|
||||
$type = str(request()->query('type'));
|
||||
$destination_uuid = request()->query('destination');
|
||||
@@ -57,32 +35,24 @@ class Create extends Component
|
||||
|
||||
if (in_array($type, DATABASE_TYPES)) {
|
||||
if ($type->value() === 'postgresql') {
|
||||
$this->authorize('create', StandalonePostgresql::class);
|
||||
$database = create_standalone_postgresql(
|
||||
environmentId: $environment->id,
|
||||
destinationUuid: $destination_uuid,
|
||||
databaseImage: $database_image
|
||||
);
|
||||
} elseif ($type->value() === 'redis') {
|
||||
$this->authorize('create', StandaloneRedis::class);
|
||||
$database = create_standalone_redis($environment->id, $destination_uuid);
|
||||
} elseif ($type->value() === 'mongodb') {
|
||||
$this->authorize('create', StandaloneMongodb::class);
|
||||
$database = create_standalone_mongodb($environment->id, $destination_uuid);
|
||||
} elseif ($type->value() === 'mysql') {
|
||||
$this->authorize('create', StandaloneMysql::class);
|
||||
$database = create_standalone_mysql($environment->id, $destination_uuid);
|
||||
} elseif ($type->value() === 'mariadb') {
|
||||
$this->authorize('create', StandaloneMariadb::class);
|
||||
$database = create_standalone_mariadb($environment->id, $destination_uuid);
|
||||
} elseif ($type->value() === 'keydb') {
|
||||
$this->authorize('create', StandaloneKeydb::class);
|
||||
$database = create_standalone_keydb($environment->id, $destination_uuid);
|
||||
} elseif ($type->value() === 'dragonfly') {
|
||||
$this->authorize('create', StandaloneDragonfly::class);
|
||||
$database = create_standalone_dragonfly($environment->id, $destination_uuid);
|
||||
} elseif ($type->value() === 'clickhouse') {
|
||||
$this->authorize('create', StandaloneClickhouse::class);
|
||||
$database = create_standalone_clickhouse($environment->id, $destination_uuid);
|
||||
}
|
||||
|
||||
|
@@ -37,6 +37,8 @@ class Danger extends Component
|
||||
|
||||
public string $resourceDomain = '';
|
||||
|
||||
public bool $canDelete = false;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$parameters = get_route_parameters();
|
||||
@@ -80,6 +82,13 @@ class Danger extends Component
|
||||
'service-database' => $this->resource->name ?? 'Service Database',
|
||||
default => 'Unknown Resource',
|
||||
};
|
||||
|
||||
// Check if user can delete this resource
|
||||
try {
|
||||
$this->canDelete = auth()->user()->can('delete', $this->resource);
|
||||
} catch (\Exception $e) {
|
||||
$this->canDelete = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($password)
|
||||
|
@@ -30,18 +30,22 @@
|
||||
</div>
|
||||
<div class="flex items-center justify-center gap-2 text-xs font-bold">
|
||||
@if ($project->environments->first())
|
||||
<a class="hover:underline" wire:click.stop
|
||||
href="{{ route('project.resource.create', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_uuid' => $project->environments->first()->uuid,
|
||||
]) }}">
|
||||
<span class="p-2 font-bold">+ Add Resource</span>
|
||||
</a>
|
||||
@can('createAnyResource')
|
||||
<a class="hover:underline" wire:click.stop
|
||||
href="{{ route('project.resource.create', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_uuid' => $project->environments->first()->uuid,
|
||||
]) }}">
|
||||
<span class="p-2 font-bold">+ Add Resource</span>
|
||||
</a>
|
||||
@endcan
|
||||
@endif
|
||||
<a class="hover:underline" wire:click.stop
|
||||
href="{{ route('project.edit', ['project_uuid' => $project->uuid]) }}">
|
||||
Settings
|
||||
</a>
|
||||
@can('update', $project)
|
||||
<a class="hover:underline" wire:click.stop
|
||||
href="{{ route('project.edit', ['project_uuid' => $project->uuid]) }}">
|
||||
Settings
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,10 +133,12 @@
|
||||
@if (count($deploymentsPerServer) > 0)
|
||||
<x-loading />
|
||||
@endif
|
||||
<x-modal-confirmation title="Confirm Cleanup Queues?" buttonTitle="Cleanup Queues" isErrorButton
|
||||
submitAction="cleanupQueue" :actions="['All running Deployment Queues will be cleaned up.']" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Cleanup Deployment Queues" :dispatchEvent="true"
|
||||
dispatchEventType="success" dispatchEventMessage="Deployment Queues cleanup started." />
|
||||
@can('cleanupDeploymentQueue', Application::class)
|
||||
<x-modal-confirmation title="Confirm Cleanup Queues?" buttonTitle="Cleanup Queues" isErrorButton
|
||||
submitAction="cleanupQueue" :actions="['All running Deployment Queues will be cleaned up.']" :confirmWithText="false" :confirmWithPassword="false"
|
||||
step2ButtonText="Permanently Cleanup Deployment Queues" :dispatchEvent="true"
|
||||
dispatchEventType="success" dispatchEventMessage="Deployment Queues cleanup started." />
|
||||
@endcan
|
||||
</div>
|
||||
<div wire:poll.3000ms="loadDeployments" class="grid grid-cols-1">
|
||||
@forelse ($deploymentsPerServer as $serverName => $deployments)
|
||||
|
@@ -50,7 +50,16 @@
|
||||
<x-forms.input
|
||||
helper="You can add a custom name for your container.<br><br>The name will be converted to slug format when you save it. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||
instantSave id="customInternalName" label="Custom Container Name" />
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
@can('update', $application)
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button type="submit" disabled
|
||||
title="You don't have permission to update this application. Contact your team administrator for access.">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@endcan
|
||||
</form>
|
||||
@endif
|
||||
@if ($application->build_pack === 'dockercompose')
|
||||
@@ -78,7 +87,16 @@
|
||||
<div class="flex gap-2 items-end pt-4">
|
||||
<h3>GPU</h3>
|
||||
@if ($isGpuEnabled)
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
@can('update', $application)
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button type="submit" disabled
|
||||
title="You don't have permission to update this application. Contact your team administrator for access.">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@endcan
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
@@ -6,6 +6,11 @@
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button type="submit" disabled
|
||||
title="You don't have permission to update this application. Contact your team administrator for access.">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@endcan
|
||||
|
||||
{{-- <x-forms.button wire:click="downloadConfig">
|
||||
@@ -82,8 +87,14 @@
|
||||
placeholder="Empty means default configuration will be used." label="Custom Nginx Configuration"
|
||||
helper="You can add custom Nginx configuration here." />
|
||||
@can('update', $application)
|
||||
<x-forms.button wire:click="generateNginxConfiguration">Generate Default Nginx
|
||||
Configuration</x-forms.button>
|
||||
<x-forms.button wire:click="generateNginxConfiguration">
|
||||
Generate Default Nginx Configuration
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button wire:click="generateNginxConfiguration" disabled
|
||||
title="You don't have permission to update this application. Contact your team administrator for access.">
|
||||
Generate Default Nginx Configuration
|
||||
</x-forms.button>
|
||||
@endcan
|
||||
@endif
|
||||
<div class="w-96 pb-6">
|
||||
|
@@ -2,9 +2,16 @@
|
||||
<form wire:submit='submit' class="flex flex-col">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Swarm Configuration</h2>
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@can('update', $application)
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button type="submit" disabled
|
||||
title="You don't have permission to update this application. Contact your team administrator for access.">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@endcan
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 py-4">
|
||||
<div class="flex flex-col items-end gap-2 xl:flex-row">
|
||||
|
@@ -5,8 +5,19 @@
|
||||
<form wire:submit='submit' class="flex flex-col">
|
||||
<div class="flex items-end gap-2">
|
||||
<h1>Environment: {{ data_get_str($environment, 'name')->limit(15) }}</h1>
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
<livewire:project.delete-environment :disabled="!$environment->isEmpty()" :environment_id="$environment->id" />
|
||||
@can('update', $environment)
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button type="submit" disabled
|
||||
title="You don't have permission to update this environment. Contact your team administrator for access.">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@endcan
|
||||
@can('delete', $environment)
|
||||
<livewire:project.delete-environment :disabled="!$environment->isEmpty()" :environment_id="$environment->id" />
|
||||
@endcan
|
||||
</div>
|
||||
<nav class="flex pt-2 pb-10">
|
||||
<ol class="flex flex-wrap items-center gap-y-1">
|
||||
|
@@ -6,20 +6,28 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<h1>Resources</h1>
|
||||
@if ($environment->isEmpty())
|
||||
<a class="button"
|
||||
href="{{ route('project.clone-me', ['project_uuid' => data_get($project, 'uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}">
|
||||
Clone
|
||||
</a>
|
||||
@can('createAnyResource')
|
||||
<a class="button"
|
||||
href="{{ route('project.clone-me', ['project_uuid' => data_get($project, 'uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}">
|
||||
Clone
|
||||
</a>
|
||||
@endcan
|
||||
@else
|
||||
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}"
|
||||
class="button">+
|
||||
New</a>
|
||||
<a class="button"
|
||||
href="{{ route('project.clone-me', ['project_uuid' => data_get($project, 'uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}">
|
||||
Clone
|
||||
</a>
|
||||
@can('createAnyResource')
|
||||
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}"
|
||||
class="button">+
|
||||
New</a>
|
||||
@endcan
|
||||
@can('createAnyResource')
|
||||
<a class="button"
|
||||
href="{{ route('project.clone-me', ['project_uuid' => data_get($project, 'uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}">
|
||||
Clone
|
||||
</a>
|
||||
@endcan
|
||||
@endif
|
||||
<livewire:project.delete-environment :disabled="!$environment->isEmpty()" :environment_id="$environment->id" />
|
||||
@can('delete', $environment)
|
||||
<livewire:project.delete-environment :disabled="!$environment->isEmpty()" :environment_id="$environment->id" />
|
||||
@endcan
|
||||
</div>
|
||||
<nav class="flex pt-2 pb-6">
|
||||
<ol class="flex items-center">
|
||||
@@ -44,14 +52,39 @@
|
||||
</nav>
|
||||
</div>
|
||||
@if ($environment->isEmpty())
|
||||
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}"
|
||||
class="items-center justify-center box">+ Add New Resource</a>
|
||||
@can('createAnyResource')
|
||||
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}"
|
||||
class="items-center justify-center box">+ Add New Resource</a>
|
||||
@else
|
||||
<div
|
||||
class="flex flex-col items-center justify-center p-8 text-center border border-dashed border-neutral-300 dark:border-coolgray-300 rounded-lg">
|
||||
<h3 class="mb-2 text-lg font-semibold text-neutral-600 dark:text-neutral-400">No Resources Found</h3>
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
This environment doesn't have any resources yet.<br>
|
||||
Contact your team administrator to add resources.
|
||||
</p>
|
||||
</div>
|
||||
@endcan
|
||||
@else
|
||||
<div x-data="searchComponent()">
|
||||
<x-forms.input placeholder="Search for name, fqdn..." x-model="search" id="null" />
|
||||
<template
|
||||
x-if="filteredApplications.length === 0 && filteredDatabases.length === 0 && filteredServices.length === 0">
|
||||
<div>No resource found with the search term "<span x-text="search"></span>".</div>
|
||||
<div class="flex flex-col items-center justify-center p-8 text-center">
|
||||
<div x-show="search.length > 0">
|
||||
<p class="text-neutral-600 dark:text-neutral-400">No resource found with the search term "<span
|
||||
class="font-semibold" x-text="search"></span>".</p>
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-500 mt-1">Try adjusting your search
|
||||
criteria.</p>
|
||||
</div>
|
||||
<div x-show="search.length === 0">
|
||||
<p class="text-neutral-600 dark:text-neutral-400">No resources found in this environment.</p>
|
||||
@cannot('createAnyResource')
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-500 mt-1">Contact your team administrator
|
||||
to add resources.</p>
|
||||
@endcannot
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template x-if="filteredApplications.length > 0">
|
||||
|
@@ -4,8 +4,24 @@
|
||||
<h4 class="pt-4">Delete Resource</h4>
|
||||
<div class="pb-4">This will stop your containers, delete all related data, etc. Beware! There is no coming back!
|
||||
</div>
|
||||
<x-modal-confirmation title="Confirm Resource Deletion?" buttonTitle="Delete" isErrorButton submitAction="delete"
|
||||
buttonTitle="Delete" :checkboxes="$checkboxes" :actions="['Permanently delete all containers of this resource.']" confirmationText="{{ $resourceName }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Resource Name below"
|
||||
shortConfirmationLabel="Resource Name" />
|
||||
|
||||
@if ($canDelete)
|
||||
<x-modal-confirmation title="Confirm Resource Deletion?" buttonTitle="Delete" isErrorButton submitAction="delete"
|
||||
buttonTitle="Delete" :checkboxes="$checkboxes" :actions="['Permanently delete all containers of this resource.']" confirmationText="{{ $resourceName }}"
|
||||
confirmationLabel="Please confirm the execution of the actions by entering the Resource Name below"
|
||||
shortConfirmationLabel="Resource Name" />
|
||||
@else
|
||||
<div class="flex items-center gap-2 p-4 border border-red-500 rounded-lg bg-red-50 dark:bg-red-900/20">
|
||||
<svg class="w-5 h-5 text-red-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<div>
|
||||
<div class="font-semibold text-red-700 dark:text-red-300">Insufficient Permissions</div>
|
||||
<div class="text-sm text-red-600 dark:text-red-400">You don't have permission to delete this resource.
|
||||
Contact your team administrator for access.</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
@@ -7,12 +7,21 @@
|
||||
<x-modal-input buttonTitle="+ Add" title="New Environment">
|
||||
<form class="flex flex-col w-full gap-2 rounded-sm" wire:submit='submit'>
|
||||
<x-forms.input placeholder="production" id="name" label="Name" required />
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@can('update', $project)
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button type="submit" disabled
|
||||
title="You don't have permission to update this project. Contact your team administrator for access.">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@endcan
|
||||
</form>
|
||||
</x-modal-input>
|
||||
<livewire:project.delete-project :disabled="!$project->isEmpty()" :project_id="$project->id" />
|
||||
@can('delete', $project)
|
||||
<livewire:project.delete-project :disabled="!$project->isEmpty()" :project_id="$project->id" />
|
||||
@endcan
|
||||
</div>
|
||||
<div class="text-xs truncate subtitle lg:text-sm">{{ $project->name }}.</div>
|
||||
<div class="grid gap-2 lg:grid-cols-2">
|
||||
|
Reference in New Issue
Block a user