mirror of
https://github.com/pelican-dev/panel.git
synced 2026-05-04 18:00:48 +03:00
Add changes from upstream (#2076)
Co-authored-by: DaneEveritt <dane@daneeveritt.com>
This commit is contained in:
65
app/Enums/ResourceLimit.php
Normal file
65
app/Enums/ResourceLimit.php
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Cache\RateLimiting\Limit;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Webmozart\Assert\Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic resource throttler for individual servers. This is applied in addition
|
||||||
|
* to existing rate limits and allows the code to slow down speedy users that might
|
||||||
|
* be creating resources a little too quickly for comfort. This throttle generally
|
||||||
|
* only applies to creation flows, and not general view/edit/delete flows.
|
||||||
|
*/
|
||||||
|
enum ResourceLimit: string
|
||||||
|
{
|
||||||
|
case Websocket = 'websocket';
|
||||||
|
case AllocationCreate = 'allocation-create';
|
||||||
|
case BackupRestore = 'backup-restore';
|
||||||
|
case DatabaseCreate = 'database-create';
|
||||||
|
case ScheduleCreate = 'schedule-create';
|
||||||
|
case SubuserCreate = 'subuser-create';
|
||||||
|
case FilePull = 'file-pull';
|
||||||
|
|
||||||
|
public function throttleKey(): string
|
||||||
|
{
|
||||||
|
return "api.client:server-resource:{$this->name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a middleware that will throttle the specific resource by server. This
|
||||||
|
* throttle applies to any user making changes to that resource on the specific
|
||||||
|
* server, it is NOT per-user.
|
||||||
|
*/
|
||||||
|
public function middleware(): string
|
||||||
|
{
|
||||||
|
return ThrottleRequests::using($this->throttleKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function limit(): Limit
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Websocket => Limit::perMinute(5),
|
||||||
|
self::BackupRestore => Limit::perMinutes(15, 3),
|
||||||
|
self::DatabaseCreate => Limit::perMinute(2),
|
||||||
|
self::SubuserCreate => Limit::perMinutes(15, 10),
|
||||||
|
self::FilePull => Limit::perMinutes(10, 5),
|
||||||
|
default => Limit::perMinute(2),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function boot(): void
|
||||||
|
{
|
||||||
|
foreach (self::cases() as $case) {
|
||||||
|
RateLimiter::for($case->throttleKey(), function (Request $request) use ($case) {
|
||||||
|
Assert::isInstanceOf($server = $request->route()->parameter('server'), Server::class);
|
||||||
|
|
||||||
|
return $case->limit()->by($server->uuid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,11 +18,11 @@ final class GithubSchema extends OAuthSchema
|
|||||||
public function getSetupSteps(): array
|
public function getSetupSteps(): array
|
||||||
{
|
{
|
||||||
return array_merge([
|
return array_merge([
|
||||||
Step::make('Register new Github OAuth App')
|
Step::make('Register new GitHub OAuth App')
|
||||||
->schema([
|
->schema([
|
||||||
TextEntry::make('create_application')
|
TextEntry::make('create_application')
|
||||||
->hiddenLabel()
|
->hiddenLabel()
|
||||||
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://github.com/settings/developers" target="_blank">Github Developer Dashboard</x-filament::link>, go to <b>OAuth Apps</b> and click on <b>New OAuth App</b>.</p><p>Enter an <b>Application name</b> (e.g. your panel name), set <b>Homepage URL</b> to your panel url and enter the below url as <b>Authorization callback URL</b>.</p>'))),
|
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://github.com/settings/developers" target="_blank">GitHub Developer Dashboard</x-filament::link>, go to <b>OAuth Apps</b> and click on <b>New OAuth App</b>.</p><p>Enter an <b>Application name</b> (e.g. your panel name), set <b>Homepage URL</b> to your panel url and enter the below url as <b>Authorization callback URL</b>.</p>'))),
|
||||||
TextInput::make('_noenv_callback')
|
TextInput::make('_noenv_callback')
|
||||||
->label('Authorization callback URL')
|
->label('Authorization callback URL')
|
||||||
->dehydrated()
|
->dehydrated()
|
||||||
|
|||||||
@@ -253,13 +253,11 @@ class CreateNode extends CreateRecord
|
|||||||
->columnSpan(2),
|
->columnSpan(2),
|
||||||
TextInput::make('upload_size')
|
TextInput::make('upload_size')
|
||||||
->label(trans('admin/node.upload_limit'))
|
->label(trans('admin/node.upload_limit'))
|
||||||
->helperText(trans('admin/node.upload_limit_help.0'))
|
->hintIcon('tabler-question-mark', trans('admin/node.upload_limit_help'))
|
||||||
->hintIcon('tabler-question-mark', trans('admin/node.upload_limit_help.1'))
|
|
||||||
->columnSpan(1)
|
->columnSpan(1)
|
||||||
->numeric()->required()
|
->numeric()->required()
|
||||||
->default(256)
|
->default(256)
|
||||||
->minValue(1)
|
->minValue(1)
|
||||||
->maxValue(1024)
|
|
||||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB'),
|
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB'),
|
||||||
TextInput::make('daemon_sftp')
|
TextInput::make('daemon_sftp')
|
||||||
->columnSpan(1)
|
->columnSpan(1)
|
||||||
|
|||||||
@@ -319,10 +319,10 @@ class EditNode extends EditRecord
|
|||||||
'lg' => 1,
|
'lg' => 1,
|
||||||
])
|
])
|
||||||
->label(trans('admin/node.upload_limit'))
|
->label(trans('admin/node.upload_limit'))
|
||||||
->hintIcon('tabler-question-mark', trans('admin/node.upload_limit_help.0') . trans('admin/node.upload_limit_help.1'))
|
->hintIcon('tabler-question-mark', trans('admin/node.upload_limit_help'))
|
||||||
->numeric()->required()
|
->numeric()
|
||||||
|
->required()
|
||||||
->minValue(1)
|
->minValue(1)
|
||||||
->maxValue(1024)
|
|
||||||
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB'),
|
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB'),
|
||||||
TextInput::make('daemon_sftp')
|
TextInput::make('daemon_sftp')
|
||||||
->columnSpan([
|
->columnSpan([
|
||||||
|
|||||||
@@ -83,15 +83,21 @@ class BackupController extends ClientApiController
|
|||||||
// how best to allow a user to create a backup that is locked without also preventing
|
// how best to allow a user to create a backup that is locked without also preventing
|
||||||
// them from just filling up a server with backups that can never be deleted?
|
// them from just filling up a server with backups that can never be deleted?
|
||||||
if ($request->user()->can(SubuserPermission::BackupDelete, $server)) {
|
if ($request->user()->can(SubuserPermission::BackupDelete, $server)) {
|
||||||
$action->setIsLocked((bool) $request->input('is_locked'));
|
$action->setIsLocked($request->boolean('is_locked'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$backup = $action->handle($server, $request->input('name'));
|
$backup = Activity::event('server:backup.start')->transaction(function ($log) use ($action, $server, $request) {
|
||||||
|
$server->backups()->lockForUpdate();
|
||||||
|
|
||||||
Activity::event('server:backup.start')
|
$backup = $action->handle($server, $request->input('name'));
|
||||||
->subject($backup)
|
|
||||||
->property(['name' => $backup->name, 'locked' => (bool) $request->input('is_locked')])
|
$log->subject($backup)->property([
|
||||||
->log();
|
'name' => $backup->name,
|
||||||
|
'locked' => $request->boolean('is_locked'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $backup;
|
||||||
|
});
|
||||||
|
|
||||||
return $this->fractal->item($backup)
|
return $this->fractal->item($backup)
|
||||||
->transformWith($this->getTransformer(BackupTransformer::class))
|
->transformWith($this->getTransformer(BackupTransformer::class))
|
||||||
|
|||||||
@@ -59,12 +59,15 @@ class DatabaseController extends ClientApiController
|
|||||||
*/
|
*/
|
||||||
public function store(StoreDatabaseRequest $request, Server $server): array
|
public function store(StoreDatabaseRequest $request, Server $server): array
|
||||||
{
|
{
|
||||||
$database = $this->deployDatabaseService->handle($server, $request->validated());
|
$database = Activity::event('server:database.create')->transaction(function ($log) use ($request, $server) {
|
||||||
|
$server->databases()->lockForUpdate();
|
||||||
|
|
||||||
Activity::event('server:database.create')
|
$database = $this->deployDatabaseService->handle($server, $request->validated());
|
||||||
->subject($database)
|
|
||||||
->property('name', $database->database)
|
$log->subject($database)->property('name', $database->database);
|
||||||
->log();
|
|
||||||
|
return $database;
|
||||||
|
});
|
||||||
|
|
||||||
return $this->fractal->item($database)
|
return $this->fractal->item($database)
|
||||||
->parseIncludes(['password'])
|
->parseIncludes(['password'])
|
||||||
|
|||||||
@@ -107,16 +107,19 @@ class NetworkAllocationController extends ClientApiController
|
|||||||
*/
|
*/
|
||||||
public function store(NewAllocationRequest $request, Server $server): array
|
public function store(NewAllocationRequest $request, Server $server): array
|
||||||
{
|
{
|
||||||
if ($server->allocations()->count() >= $server->allocation_limit) {
|
$allocation = Activity::event('server:allocation.create')->transaction(function ($log) use ($server) {
|
||||||
throw new DisplayException('Cannot assign additional allocations to this server: limit has been reached.');
|
$server->allocations()->lockForUpdate();
|
||||||
}
|
|
||||||
|
|
||||||
$allocation = $this->assignableAllocationService->handle($server);
|
if ($server->allocations->count() >= $server->allocation_limit) {
|
||||||
|
throw new DisplayException('Cannot assign additional allocations to this server: limit has been reached.');
|
||||||
|
}
|
||||||
|
|
||||||
Activity::event('server:allocation.create')
|
$allocation = $this->assignableAllocationService->handle($server);
|
||||||
->subject($allocation)
|
|
||||||
->property('allocation', $allocation->address)
|
$log->subject($allocation)->property('allocation', $allocation->address);
|
||||||
->log();
|
|
||||||
|
return $allocation;
|
||||||
|
});
|
||||||
|
|
||||||
return $this->fractal->item($allocation)
|
return $this->fractal->item($allocation)
|
||||||
->transformWith($this->getTransformer(AllocationTransformer::class))
|
->transformWith($this->getTransformer(AllocationTransformer::class))
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ class BackupRemoteUploadController extends Controller
|
|||||||
*/
|
*/
|
||||||
private function getConfiguredMaxPartSize(): int
|
private function getConfiguredMaxPartSize(): int
|
||||||
{
|
{
|
||||||
$maxPartSize = (int) config('backups.max_part_size', self::DEFAULT_MAX_PART_SIZE);
|
$maxPartSize = config('backups.max_part_size', self::DEFAULT_MAX_PART_SIZE);
|
||||||
if ($maxPartSize <= 0) {
|
if ($maxPartSize <= 0) {
|
||||||
$maxPartSize = self::DEFAULT_MAX_PART_SIZE;
|
$maxPartSize = self::DEFAULT_MAX_PART_SIZE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class Node extends Model implements Validatable
|
|||||||
'daemon_listen' => ['required', 'numeric', 'between:1,65535'],
|
'daemon_listen' => ['required', 'numeric', 'between:1,65535'],
|
||||||
'daemon_connect' => ['required', 'numeric', 'between:1,65535'],
|
'daemon_connect' => ['required', 'numeric', 'between:1,65535'],
|
||||||
'maintenance_mode' => ['boolean'],
|
'maintenance_mode' => ['boolean'],
|
||||||
'upload_size' => ['int', 'between:1,1024'],
|
'upload_size' => ['int', 'min:1'],
|
||||||
'tags' => ['array'],
|
'tags' => ['array'],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Enums\ResourceLimit;
|
||||||
use Illuminate\Cache\RateLimiting\Limit;
|
use Illuminate\Cache\RateLimiting\Limit;
|
||||||
use Illuminate\Foundation\Http\Middleware\TrimStrings;
|
use Illuminate\Foundation\Http\Middleware\TrimStrings;
|
||||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||||
@@ -98,5 +99,7 @@ class RouteServiceProvider extends ServiceProvider
|
|||||||
config('http.rate_limit.application')
|
config('http.rate_limit.application')
|
||||||
)->by($key);
|
)->by($key);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ResourceLimit::boot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,6 +133,9 @@ class DaemonServerRepository extends DaemonRepository
|
|||||||
* make it easier to revoke tokens on the fly. This ensures that the JTI key is formatted
|
* make it easier to revoke tokens on the fly. This ensures that the JTI key is formatted
|
||||||
* correctly and avoids any costly mistakes in the codebase.
|
* correctly and avoids any costly mistakes in the codebase.
|
||||||
*
|
*
|
||||||
|
* @deprecated
|
||||||
|
* @see self::deauthorize()
|
||||||
|
*
|
||||||
* @throws ConnectionException
|
* @throws ConnectionException
|
||||||
*/
|
*/
|
||||||
public function revokeUserJTI(int $id): void
|
public function revokeUserJTI(int $id): void
|
||||||
@@ -143,6 +146,21 @@ class DaemonServerRepository extends DaemonRepository
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deauthorizes a user (disconnects websockets and SFTP) on the Wings instance for the server.
|
||||||
|
*
|
||||||
|
* @throws ConnectionException
|
||||||
|
*/
|
||||||
|
public function deauthorize(string $user): void
|
||||||
|
{
|
||||||
|
$this->getHttpClient()->post('/api/deauthorize-user', [
|
||||||
|
'json' => [
|
||||||
|
'user' => $user,
|
||||||
|
'servers' => [$this->server->uuid],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function getInstallLogs(): string
|
public function getInstallLogs(): string
|
||||||
{
|
{
|
||||||
return $this->getHttpClient()
|
return $this->getHttpClient()
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class DetailsModificationService
|
|||||||
// websockets.
|
// websockets.
|
||||||
if ($server->owner_id !== $owner) {
|
if ($server->owner_id !== $owner) {
|
||||||
try {
|
try {
|
||||||
$this->serverRepository->setServer($server)->revokeUserJTI($owner);
|
$this->serverRepository->setServer($server)->deauthorize($server->user->uuid);
|
||||||
} catch (ConnectionException) {
|
} catch (ConnectionException) {
|
||||||
// Do nothing. A failure here is not ideal, but it is likely to be caused by daemon
|
// Do nothing. A failure here is not ideal, but it is likely to be caused by daemon
|
||||||
// being offline, or in an entirely broken state. Remember, these tokens reset every
|
// being offline, or in an entirely broken state. Remember, these tokens reset every
|
||||||
|
|||||||
@@ -78,7 +78,10 @@ class ServerDeletionService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$server->allocations()->update(['server_id' => null]);
|
$server->allocations()->update([
|
||||||
|
'server_id' => null,
|
||||||
|
'notes' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
$server->delete();
|
$server->delete();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class SubuserDeletionService
|
|||||||
event(new SubUserRemoved($subuser->server, $subuser->user));
|
event(new SubUserRemoved($subuser->server, $subuser->user));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id);
|
$this->serverRepository->setServer($server)->deauthorize($subuser->user->uuid);
|
||||||
} catch (ConnectionException $exception) {
|
} catch (ConnectionException $exception) {
|
||||||
// Don't block this request if we can't connect to the daemon instance.
|
// Don't block this request if we can't connect to the daemon instance.
|
||||||
logger()->warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]);
|
logger()->warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]);
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class SubuserUpdateService
|
|||||||
$subuser->update(['permissions' => $cleanedPermissions]);
|
$subuser->update(['permissions' => $cleanedPermissions]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id);
|
$this->serverRepository->setServer($server)->deauthorize($subuser->user->uuid);
|
||||||
} catch (ConnectionException $exception) {
|
} catch (ConnectionException $exception) {
|
||||||
// Don't block this request if we can't connect to the daemon instance. Chances are it is
|
// Don't block this request if we can't connect to the daemon instance. Chances are it is
|
||||||
// offline and the token will be invalid once daemon boots back.
|
// offline and the token will be invalid once daemon boots back.
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||||
'node.maintenance' => \App\Http\Middleware\MaintenanceMiddleware::class,
|
'node.maintenance' => \App\Http\Middleware\MaintenanceMiddleware::class,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$middleware->priority([
|
||||||
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
]);
|
||||||
})
|
})
|
||||||
->withSingletons([
|
->withSingletons([
|
||||||
\Illuminate\Contracts\Console\Kernel::class => \App\Console\Kernel::class,
|
\Illuminate\Contracts\Console\Kernel::class => \App\Console\Kernel::class,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\Remote\Backups\BackupRemoteUploadController;
|
||||||
use App\Models\Backup;
|
use App\Models\Backup;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -10,16 +11,16 @@ return [
|
|||||||
|
|
||||||
// This value is used to determine the lifespan of UploadPart presigned urls that daemon
|
// This value is used to determine the lifespan of UploadPart presigned urls that daemon
|
||||||
// uses to upload backups to S3 storage. Value is in minutes, so this would default to an hour.
|
// uses to upload backups to S3 storage. Value is in minutes, so this would default to an hour.
|
||||||
'presigned_url_lifespan' => env('BACKUP_PRESIGNED_URL_LIFESPAN', 60),
|
'presigned_url_lifespan' => (int) env('BACKUP_PRESIGNED_URL_LIFESPAN', 60),
|
||||||
|
|
||||||
// This value defines the maximal size of a single part for the S3 multipart upload during backups
|
// This value defines the maximal size of a single part for the S3 multipart upload during backups
|
||||||
// The maximal part size must be given in bytes. The default value is 5GB.
|
// The maximal part size must be given in bytes. The default value is 5GB.
|
||||||
// Note that 5GB is the maximum for a single part when using AWS S3.
|
// Note that 5GB is the maximum for a single part when using AWS S3.
|
||||||
'max_part_size' => env('BACKUP_MAX_PART_SIZE', 5 * 1024 * 1024 * 1024),
|
'max_part_size' => (int) env('BACKUP_MAX_PART_SIZE', BackupRemoteUploadController::DEFAULT_MAX_PART_SIZE),
|
||||||
|
|
||||||
// The time to wait before automatically failing a backup, time is in minutes and defaults
|
// The time to wait before automatically failing a backup, time is in minutes and defaults
|
||||||
// to 6 hours. To disable this feature, set the value to `0`.
|
// to 6 hours. To disable this feature, set the value to `0`.
|
||||||
'prune_age' => env('BACKUP_PRUNE_AGE', 360),
|
'prune_age' => (int) env('BACKUP_PRUNE_AGE', 360),
|
||||||
|
|
||||||
// Defines the backup creation throttle limits for users. In this default example, we allow
|
// Defines the backup creation throttle limits for users. In this default example, we allow
|
||||||
// a user to create two (successful or pending) backups per 10 minutes. Even if they delete
|
// a user to create two (successful or pending) backups per 10 minutes. Even if they delete
|
||||||
@@ -27,8 +28,8 @@ return [
|
|||||||
//
|
//
|
||||||
// Set the period to "0" to disable this throttle. The period is defined in seconds.
|
// Set the period to "0" to disable this throttle. The period is defined in seconds.
|
||||||
'throttles' => [
|
'throttles' => [
|
||||||
'limit' => env('BACKUP_THROTTLE_LIMIT', 2),
|
'limit' => (int) env('BACKUP_THROTTLE_LIMIT', 2),
|
||||||
'period' => env('BACKUP_THROTTLE_PERIOD', 600),
|
'period' => (int) env('BACKUP_THROTTLE_PERIOD', 600),
|
||||||
],
|
],
|
||||||
|
|
||||||
'disks' => [
|
'disks' => [
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ return [
|
|||||||
*/
|
*/
|
||||||
'rate_limit' => [
|
'rate_limit' => [
|
||||||
'client_period' => 1,
|
'client_period' => 1,
|
||||||
'client' => env('APP_API_CLIENT_RATELIMIT', 720),
|
'client' => env('APP_API_CLIENT_RATELIMIT', 120),
|
||||||
|
|
||||||
'application_period' => 1,
|
'application_period' => 1,
|
||||||
'application' => env('APP_API_APPLICATION_RATELIMIT', 240),
|
'application' => env('APP_API_APPLICATION_RATELIMIT', 240),
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ To start contributing to Pelican Panel, you need to have a basic understanding o
|
|||||||
|
|
||||||
* [PHP](https://php.net) & [Laravel](https://laravel.com)
|
* [PHP](https://php.net) & [Laravel](https://laravel.com)
|
||||||
* [Livewire](https://laravel-livewire.com) & [Filament](https://filamentphp.com)
|
* [Livewire](https://laravel-livewire.com) & [Filament](https://filamentphp.com)
|
||||||
* [Git](https://git-scm.com) & [Github](https://github.com)
|
* [Git](https://git-scm.com) & [GitHub](https://github.com)
|
||||||
|
|
||||||
## Dev Environment Setup
|
## Dev Environment Setup
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
DB::table('allocations')
|
||||||
|
->whereNull('server_id')
|
||||||
|
->update(['notes' => null]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
// Not needed
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -61,10 +61,7 @@ return [
|
|||||||
|
|
||||||
'tags' => 'Tags',
|
'tags' => 'Tags',
|
||||||
'upload_limit' => 'Upload Limit',
|
'upload_limit' => 'Upload Limit',
|
||||||
'upload_limit_help' => [
|
'upload_limit_help' => 'Enter the maximum size of files that can be uploaded through the web-based file manager.',
|
||||||
'Enter the maximum size of files that can be uploaded through the web-based file manager.',
|
|
||||||
'Make sure your webserver supports file uploads of this size!',
|
|
||||||
],
|
|
||||||
'sftp_port' => 'SFTP Port',
|
'sftp_port' => 'SFTP Port',
|
||||||
'sftp_alias' => 'SFTP Alias',
|
'sftp_alias' => 'SFTP Alias',
|
||||||
'sftp_alias_help' => 'Display alias for the SFTP address. Leave empty to use the Node FQDN.',
|
'sftp_alias_help' => 'Display alias for the SFTP address. Leave empty to use the Node FQDN.',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\ResourceLimit;
|
||||||
use App\Http\Controllers\Api\Client;
|
use App\Http\Controllers\Api\Client;
|
||||||
use App\Http\Middleware\Activity\AccountSubject;
|
use App\Http\Middleware\Activity\AccountSubject;
|
||||||
use App\Http\Middleware\Activity\ServerSubject;
|
use App\Http\Middleware\Activity\ServerSubject;
|
||||||
@@ -50,7 +51,9 @@ Route::prefix('/account')->middleware(AccountSubject::class)->group(function ()
|
|||||||
*/
|
*/
|
||||||
Route::prefix('/servers/{server:uuid}')->middleware([ServerSubject::class, AuthenticateServerAccess::class, ResourceBelongsToServer::class])->group(function () {
|
Route::prefix('/servers/{server:uuid}')->middleware([ServerSubject::class, AuthenticateServerAccess::class, ResourceBelongsToServer::class])->group(function () {
|
||||||
Route::get('/', [Client\Servers\ServerController::class, 'index'])->name('api:client:server.view');
|
Route::get('/', [Client\Servers\ServerController::class, 'index'])->name('api:client:server.view');
|
||||||
Route::get('/websocket', Client\Servers\WebsocketController::class)->name('api:client:server.ws');
|
Route::middleware([ResourceLimit::Websocket->middleware()])
|
||||||
|
->get('/websocket', Client\Servers\WebsocketController::class)
|
||||||
|
->name('api:client:server.ws');
|
||||||
Route::get('/resources', Client\Servers\ResourceUtilizationController::class)->name('api:client:server.resources');
|
Route::get('/resources', Client\Servers\ResourceUtilizationController::class)->name('api:client:server.resources');
|
||||||
Route::get('/activity', Client\Servers\ActivityLogController::class)->name('api:client:server.activity');
|
Route::get('/activity', Client\Servers\ActivityLogController::class)->name('api:client:server.activity');
|
||||||
|
|
||||||
@@ -59,7 +62,8 @@ Route::prefix('/servers/{server:uuid}')->middleware([ServerSubject::class, Authe
|
|||||||
|
|
||||||
Route::prefix('/databases')->group(function () {
|
Route::prefix('/databases')->group(function () {
|
||||||
Route::get('/', [Client\Servers\DatabaseController::class, 'index']);
|
Route::get('/', [Client\Servers\DatabaseController::class, 'index']);
|
||||||
Route::post('/', [Client\Servers\DatabaseController::class, 'store']);
|
Route::middleware([ResourceLimit::DatabaseCreate->middleware()])
|
||||||
|
->post('/', [Client\Servers\DatabaseController::class, 'store']);
|
||||||
Route::post('/{database}/rotate-password', [Client\Servers\DatabaseController::class, 'rotatePassword']);
|
Route::post('/{database}/rotate-password', [Client\Servers\DatabaseController::class, 'rotatePassword']);
|
||||||
Route::delete('/{database}', [Client\Servers\DatabaseController::class, 'delete']);
|
Route::delete('/{database}', [Client\Servers\DatabaseController::class, 'delete']);
|
||||||
});
|
});
|
||||||
@@ -76,13 +80,15 @@ Route::prefix('/servers/{server:uuid}')->middleware([ServerSubject::class, Authe
|
|||||||
Route::post('/delete', [Client\Servers\FileController::class, 'delete']);
|
Route::post('/delete', [Client\Servers\FileController::class, 'delete']);
|
||||||
Route::post('/create-folder', [Client\Servers\FileController::class, 'create']);
|
Route::post('/create-folder', [Client\Servers\FileController::class, 'create']);
|
||||||
Route::post('/chmod', [Client\Servers\FileController::class, 'chmod']);
|
Route::post('/chmod', [Client\Servers\FileController::class, 'chmod']);
|
||||||
Route::post('/pull', [Client\Servers\FileController::class, 'pull'])->middleware(['throttle:10,5']);
|
Route::middleware([ResourceLimit::FilePull->middleware()])
|
||||||
|
->post('/pull', [Client\Servers\FileController::class, 'pull']);
|
||||||
Route::get('/upload', Client\Servers\FileUploadController::class);
|
Route::get('/upload', Client\Servers\FileUploadController::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::prefix('/schedules')->group(function () {
|
Route::prefix('/schedules')->group(function () {
|
||||||
Route::get('/', [Client\Servers\ScheduleController::class, 'index']);
|
Route::get('/', [Client\Servers\ScheduleController::class, 'index']);
|
||||||
Route::post('/', [Client\Servers\ScheduleController::class, 'store']);
|
Route::middleware([ResourceLimit::ScheduleCreate->middleware()])
|
||||||
|
->post('/', [Client\Servers\ScheduleController::class, 'store']);
|
||||||
Route::get('/{schedule}', [Client\Servers\ScheduleController::class, 'view']);
|
Route::get('/{schedule}', [Client\Servers\ScheduleController::class, 'view']);
|
||||||
Route::post('/{schedule}', [Client\Servers\ScheduleController::class, 'update']);
|
Route::post('/{schedule}', [Client\Servers\ScheduleController::class, 'update']);
|
||||||
Route::post('/{schedule}/execute', [Client\Servers\ScheduleController::class, 'execute']);
|
Route::post('/{schedule}/execute', [Client\Servers\ScheduleController::class, 'execute']);
|
||||||
@@ -95,7 +101,8 @@ Route::prefix('/servers/{server:uuid}')->middleware([ServerSubject::class, Authe
|
|||||||
|
|
||||||
Route::prefix('/network/allocations')->group(function () {
|
Route::prefix('/network/allocations')->group(function () {
|
||||||
Route::get('/', [Client\Servers\NetworkAllocationController::class, 'index']);
|
Route::get('/', [Client\Servers\NetworkAllocationController::class, 'index']);
|
||||||
Route::post('/', [Client\Servers\NetworkAllocationController::class, 'store']);
|
Route::middleware([ResourceLimit::AllocationCreate->middleware()])
|
||||||
|
->post('/', [Client\Servers\NetworkAllocationController::class, 'store']);
|
||||||
Route::post('/{allocation}', [Client\Servers\NetworkAllocationController::class, 'update']);
|
Route::post('/{allocation}', [Client\Servers\NetworkAllocationController::class, 'update']);
|
||||||
Route::post('/{allocation}/primary', [Client\Servers\NetworkAllocationController::class, 'setPrimary']);
|
Route::post('/{allocation}/primary', [Client\Servers\NetworkAllocationController::class, 'setPrimary']);
|
||||||
Route::delete('/{allocation}', [Client\Servers\NetworkAllocationController::class, 'delete']);
|
Route::delete('/{allocation}', [Client\Servers\NetworkAllocationController::class, 'delete']);
|
||||||
@@ -103,7 +110,8 @@ Route::prefix('/servers/{server:uuid}')->middleware([ServerSubject::class, Authe
|
|||||||
|
|
||||||
Route::prefix('/users')->group(function () {
|
Route::prefix('/users')->group(function () {
|
||||||
Route::get('/', [Client\Servers\SubuserController::class, 'index']);
|
Route::get('/', [Client\Servers\SubuserController::class, 'index']);
|
||||||
Route::post('/', [Client\Servers\SubuserController::class, 'store']);
|
Route::middleware([ResourceLimit::SubuserCreate->middleware()])
|
||||||
|
->post('/', [Client\Servers\SubuserController::class, 'store']);
|
||||||
Route::get('/{user:uuid}', [Client\Servers\SubuserController::class, 'view']);
|
Route::get('/{user:uuid}', [Client\Servers\SubuserController::class, 'view']);
|
||||||
Route::post('/{user:uuid}', [Client\Servers\SubuserController::class, 'update']);
|
Route::post('/{user:uuid}', [Client\Servers\SubuserController::class, 'update']);
|
||||||
Route::delete('/{user:uuid}', [Client\Servers\SubuserController::class, 'delete']);
|
Route::delete('/{user:uuid}', [Client\Servers\SubuserController::class, 'delete']);
|
||||||
@@ -116,7 +124,8 @@ Route::prefix('/servers/{server:uuid}')->middleware([ServerSubject::class, Authe
|
|||||||
Route::get('/{backup:uuid}/download', [Client\Servers\BackupController::class, 'download']);
|
Route::get('/{backup:uuid}/download', [Client\Servers\BackupController::class, 'download']);
|
||||||
Route::put('/{backup:uuid}/rename', [Client\Servers\BackupController::class, 'rename']);
|
Route::put('/{backup:uuid}/rename', [Client\Servers\BackupController::class, 'rename']);
|
||||||
Route::post('/{backup:uuid}/lock', [Client\Servers\BackupController::class, 'toggleLock']);
|
Route::post('/{backup:uuid}/lock', [Client\Servers\BackupController::class, 'toggleLock']);
|
||||||
Route::post('/{backup:uuid}/restore', [Client\Servers\BackupController::class, 'restore']);
|
Route::middleware([ResourceLimit::BackupRestore->middleware()])
|
||||||
|
->post('/{backup:uuid}/restore', [Client\Servers\BackupController::class, 'restore']);
|
||||||
Route::delete('/{backup:uuid}', [Client\Servers\BackupController::class, 'delete']);
|
Route::delete('/{backup:uuid}', [Client\Servers\BackupController::class, 'delete']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ While Pelican is in beta, we only provide security fixes for the most recent bet
|
|||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
Please report any vulnerabilities via _one_ of the following methods:
|
Please report any vulnerabilities via _one_ of the following methods:
|
||||||
- [Create a security advisory on Github](https://github.com/pelican-dev/panel/security/advisories/new)
|
|
||||||
- Send an e-mail to team@pelican.dev
|
- [Create a security advisory on GitHub](https://github.com/pelican-dev/panel/security/advisories/new)
|
||||||
|
- Send an e-mail to <team@pelican.dev>
|
||||||
|
|
||||||
Include steps to reproduce, affected versions, impact, and a proof of concept if available.
|
Include steps to reproduce, affected versions, impact, and a proof of concept if available.
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class DeleteSubuserTest extends ClientApiIntegrationTestCase
|
|||||||
'permissions' => [SubuserPermission::WebsocketConnect],
|
'permissions' => [SubuserPermission::WebsocketConnect],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$mock->expects('setServer->revokeUserJTI')->with($subuser->id)->andReturnUndefined();
|
$mock->expects('setServer->deauthorize')->with($subuser->uuid)->andReturnUndefined();
|
||||||
|
|
||||||
$this->actingAs($user)->deleteJson($this->link($server) . "/users/$subuser->uuid")->assertNoContent();
|
$this->actingAs($user)->deleteJson($this->link($server) . "/users/$subuser->uuid")->assertNoContent();
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ class DeleteSubuserTest extends ClientApiIntegrationTestCase
|
|||||||
'permissions' => [SubuserPermission::WebsocketConnect],
|
'permissions' => [SubuserPermission::WebsocketConnect],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$mock->expects('setServer->revokeUserJTI')->with($subuser->id)->andReturnUndefined();
|
$mock->expects('setServer->deauthorize')->with($subuser->uuid)->andReturnUndefined();
|
||||||
|
|
||||||
$this->actingAs($user)->deleteJson($this->link($server) . "/users/$subuser->uuid")->assertNoContent();
|
$this->actingAs($user)->deleteJson($this->link($server) . "/users/$subuser->uuid")->assertNoContent();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class SubuserAuthorizationTest extends ClientApiIntegrationTestCase
|
|||||||
|
|
||||||
$this->instance(DaemonServerRepository::class, $mock = \Mockery::mock(DaemonServerRepository::class));
|
$this->instance(DaemonServerRepository::class, $mock = \Mockery::mock(DaemonServerRepository::class));
|
||||||
if ($method === 'DELETE') {
|
if ($method === 'DELETE') {
|
||||||
$mock->expects('setServer->revokeUserJTI')->with($internal->id)->andReturnUndefined();
|
$mock->expects('setServer->deauthorize')->with($internal->uuid)->andReturnUndefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This route is acceptable since they're accessing a subuser on their own server.
|
// This route is acceptable since they're accessing a subuser on their own server.
|
||||||
|
|||||||
Reference in New Issue
Block a user