Merge branch 'develop' into feature/react-admin

This commit is contained in:
Matthew Penner 2021-02-07 16:16:22 -07:00
commit a87fef37ec
77 changed files with 1082 additions and 839 deletions

125
app/Models/AuditLog.php Normal file
View file

@ -0,0 +1,125 @@
<?php
namespace Pterodactyl\Models;
use Ramsey\Uuid\Uuid;
use Illuminate\Http\Request;
use Illuminate\Container\Container;
/**
* @property int $id
* @property string $uuid
* @property bool $is_system
* @property int|null $user_id
* @property int|null $server_id
* @property string $action
* @property string|null $subaction
* @property array $device
* @property array $metadata
* @property \Carbon\CarbonImmutable $created_at
* @property \Pterodactyl\Models\User|null $user
* @property \Pterodactyl\Models\Server|null $server
*/
class AuditLog extends Model
{
public const UPDATED_AT = null;
public const SERVER__FILESYSTEM_DOWNLOAD = 'server:filesystem.download';
public const SERVER__FILESYSTEM_WRITE = 'server:filesystem.write';
public const SERVER__FILESYSTEM_DELETE = 'server:filesystem.delete';
public const SERVER__FILESYSTEM_RENAME = 'server:filesystem.rename';
public const SERVER__FILESYSTEM_COMPRESS = 'server:filesystem.compress';
public const SERVER__FILESYSTEM_DECOMPRESS = 'server:filesystem.decompress';
public const SERVER__FILESYSTEM_PULL = 'server:filesystem.pull';
public const SERVER__BACKUP_STARTED = 'server:backup.started';
public const SERVER__BACKUP_FAILED = 'server:backup.failed';
public const SERVER__BACKUP_COMPELTED = 'server:backup.completed';
public const SERVER__BACKUP_DELETED = 'server:backup.deleted';
public const SERVER__BACKUP_RESTORE_STARTED = 'server:backup.restore.started';
public const SERVER__BACKUP_RESTORE_COMPLETED = 'server:backup.restore.completed';
public const SERVER__BACKUP_RESTORE_FAILED = 'server:backup.restore.failed';
/**
* @var string[]
*/
public static $validationRules = [
'uuid' => 'required|uuid',
'action' => 'required|string|max:191',
'subaction' => 'nullable|string|max:191',
'device' => 'array',
'device.ip_address' => 'ip',
'device.user_agent' => 'string',
'metadata' => 'array',
];
/**
* @var string
*/
protected $table = 'audit_logs';
/**
* @var bool
*/
protected $immutableDates = true;
/**
* @var string[]
*/
protected $casts = [
'device' => 'array',
'metadata' => 'array',
];
/**
* @var string[]
*/
protected $guarded = [
'id',
'created_at',
];
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function server()
{
return $this->belongsTo(Server::class);
}
/**
* Creates a new AuditLog model and returns it, attaching device information and the
* currently authenticated user if available. This model is not saved at this point, so
* you can always make modifications to it as needed before saving.
*
* @return $this
*/
public static function instance(string $action, array $metadata, bool $isSystem = false)
{
/** @var \Illuminate\Http\Request $request */
$request = Container::getInstance()->make('request');
if ($isSystem || !$request instanceof Request) {
$request = null;
}
return (new self())->fill([
'uuid' => Uuid::uuid4()->toString(),
'is_system' => $isSystem,
'user_id' => ($request && $request->user()) ? $request->user()->id : null,
'server_id' => null,
'action' => $action,
'device' => $request ? [
'ip_address' => $request->getClientIp(),
'user_agent' => $request->userAgent(),
] : [],
'metadata' => $metadata,
]);
}
}

View file

@ -39,9 +39,9 @@ class Permission extends Model
public const ACTION_BACKUP_READ = 'backup.read';
public const ACTION_BACKUP_CREATE = 'backup.create';
public const ACTION_BACKUP_UPDATE = 'backup.update';
public const ACTION_BACKUP_DELETE = 'backup.delete';
public const ACTION_BACKUP_DOWNLOAD = 'backup.download';
public const ACTION_BACKUP_RESTORE = 'backup.restore';
public const ACTION_ALLOCATION_READ = 'allocation.read';
public const ACTION_ALLOCATION_CREATE = 'allocation.create';
@ -155,9 +155,9 @@ class Permission extends Model
'keys' => [
'create' => 'Allows a user to create new backups for this server.',
'read' => 'Allows a user to view all backups that exist for this server.',
'update' => '',
'delete' => 'Allows a user to remove backups from the system.',
'download' => 'Allows a user to download backups.',
'download' => 'Allows a user to download a backup for the server. Danger: this allows a user to access all files for the server in the backup.',
'restore' => 'Allows a user to restore a backup for the server. Danger: this allows the user to delete all of the server files in the process.',
],
],

View file

@ -2,9 +2,11 @@
namespace Pterodactyl\Models;
use Closure;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Query\JoinClause;
use Znck\Eloquent\Traits\BelongsToThrough;
use Pterodactyl\Exceptions\Http\Server\ServerStateConflictException;
/**
* @property int $id
@ -14,8 +16,8 @@ use Znck\Eloquent\Traits\BelongsToThrough;
* @property int $node_id
* @property string $name
* @property string $description
* @property string|null $status
* @property bool $skip_scripts
* @property bool $suspended
* @property int $owner_id
* @property int $memory
* @property int $swap
@ -29,7 +31,6 @@ use Znck\Eloquent\Traits\BelongsToThrough;
* @property int $egg_id
* @property string $startup
* @property string $image
* @property int $installed
* @property int $allocation_limit
* @property int $database_limit
* @property int $backup_limit
@ -49,6 +50,7 @@ use Znck\Eloquent\Traits\BelongsToThrough;
* @property \Pterodactyl\Models\ServerTransfer $transfer
* @property \Pterodactyl\Models\Backup[]|\Illuminate\Database\Eloquent\Collection $backups
* @property \Pterodactyl\Models\Mount[]|\Illuminate\Database\Eloquent\Collection $mounts
* @property \Pterodactyl\Models\AuditLog[] $audits
*/
class Server extends Model
{
@ -61,9 +63,10 @@ class Server extends Model
*/
public const RESOURCE_NAME = 'server';
public const STATUS_INSTALLING = 0;
public const STATUS_INSTALLED = 1;
public const STATUS_INSTALL_FAILED = 2;
public const STATUS_INSTALLING = 'installing';
public const STATUS_INSTALL_FAILED = 'install_failed';
public const STATUS_SUSPENDED = 'suspended';
public const STATUS_RESTORING_BACKUP = 'restoring_backup';
/**
* The table associated with the model.
@ -79,6 +82,7 @@ class Server extends Model
* @var array
*/
protected $attributes = [
'status' => self::STATUS_INSTALLING,
'oom_disabled' => true,
];
@ -101,7 +105,7 @@ class Server extends Model
*
* @var array
*/
protected $guarded = ['id', 'installed', self::CREATED_AT, self::UPDATED_AT, 'deleted_at'];
protected $guarded = ['id', self::CREATED_AT, self::UPDATED_AT, 'deleted_at'];
/**
* @var array
@ -112,6 +116,7 @@ class Server extends Model
'name' => 'required|string|min:1|max:191',
'node_id' => 'required|exists:nodes,id',
'description' => 'string',
'status' => 'nullable|string',
'memory' => 'required|numeric|min:0',
'swap' => 'required|numeric|min:-1',
'io' => 'required|numeric|between:10,1000',
@ -125,7 +130,6 @@ class Server extends Model
'startup' => 'required|string',
'skip_scripts' => 'sometimes|boolean',
'image' => 'required|string|max:191',
'installed' => 'in:0,1,2',
'database_limit' => 'present|nullable|integer|min:0',
'allocation_limit' => 'sometimes|nullable|integer|min:0',
'backup_limit' => 'present|nullable|integer|min:0',
@ -139,7 +143,6 @@ class Server extends Model
protected $casts = [
'node_id' => 'integer',
'skip_scripts' => 'boolean',
'suspended' => 'boolean',
'owner_id' => 'integer',
'memory' => 'integer',
'swap' => 'integer',
@ -150,7 +153,6 @@ class Server extends Model
'allocation_id' => 'integer',
'nest_id' => 'integer',
'egg_id' => 'integer',
'installed' => 'integer',
'database_limit' => 'integer',
'allocation_limit' => 'integer',
'backup_limit' => 'integer',
@ -168,7 +170,12 @@ class Server extends Model
public function isInstalled(): bool
{
return $this->installed === 1;
return $this->status !== self::STATUS_INSTALLING && $this->status !== self::STATUS_INSTALL_FAILED;
}
public function isSuspended(): bool
{
return $this->status === self::STATUS_SUSPENDED;
}
/**
@ -320,4 +327,68 @@ class Server extends Model
{
return $this->hasManyThrough(Mount::class, MountServer::class, 'server_id', 'id', 'id', 'mount_id');
}
/**
* Returns a fresh AuditLog model for the server. This model is not saved to the
* database when created, so it is up to the caller to correctly store it as needed.
*
* @return \Pterodactyl\Models\AuditLog
*/
public function newAuditEvent(string $action, array $metadata = []): AuditLog
{
return AuditLog::instance($action, $metadata)->fill([
'server_id' => $this->id,
]);
}
/**
* Stores a new audit event for a server by using a transaction. If the transaction
* fails for any reason everything executed within will be rolled back. The callback
* passed in will receive the AuditLog model before it is saved and the second argument
* will be the current server instance. The callback should modify the audit entry as
* needed before finishing, any changes will be persisted.
*
* The response from the callback is returned to the caller.
*
* @return mixed
*
* @throws \Throwable
*/
public function audit(string $action, Closure $callback)
{
return $this->getConnection()->transaction(function () use ($action, $callback) {
$model = $this->newAuditEvent($action);
$response = $callback($model, $this);
$model->save();
return $response;
});
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function audits()
{
return $this->hasMany(AuditLog::class);
}
/**
* Checks if the server is currently in a user-accessible state. If not, an
* exception is raised. This should be called whenever something needs to make
* sure the server is not in a weird state that should block user access.
*
* @throws \Pterodactyl\Exceptions\Http\Server\ServerStateConflictException
*/
public function validateCurrentState()
{
if (
$this->isSuspended() ||
!$this->isInstalled() ||
$this->status === self::STATUS_RESTORING_BACKUP ||
!is_null($this->transfer)
) {
throw new ServerStateConflictException($this);
}
}
}