File transfer
This commit is contained in:
201
app/Addons/AdvancedAdmin/Services/Roles/RoleService.php
Normal file
201
app/Addons/AdvancedAdmin/Services/Roles/RoleService.php
Normal file
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author Ball Studios <https://git.balls.studio>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace Pterodactyl\Addons\AdvancedAdmin\Services\Roles;
|
||||
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Pterodactyl\Addons\AdvancedAdmin\Models\Role;
|
||||
use Pterodactyl\Addons\AdvancedAdmin\Models\RoleAuditLog;
|
||||
use Pterodactyl\Addons\AdvancedAdmin\Models\RolePermission;
|
||||
use Pterodactyl\Addons\AdvancedAdmin\Models\ServerPermissionOverride;
|
||||
use Pterodactyl\Addons\AdvancedAdmin\Models\ServerRoleAssignment;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class RoleService
|
||||
{
|
||||
/**
|
||||
* All known Pterodactyl permission keys (validated against).
|
||||
*/
|
||||
public const KNOWN_PERMISSIONS = [
|
||||
'websocket.connect',
|
||||
'control.console', 'control.start', 'control.stop', 'control.restart',
|
||||
'user.read', 'user.create', 'user.update', 'user.delete',
|
||||
'file.read', 'file.read-content', 'file.create', 'file.update',
|
||||
'file.delete', 'file.archive', 'file.sftp',
|
||||
'backup.read', 'backup.create', 'backup.delete', 'backup.download', 'backup.restore',
|
||||
'allocation.read', 'allocation.create', 'allocation.update', 'allocation.delete',
|
||||
'startup.read', 'startup.update', 'startup.docker-image',
|
||||
'database.read', 'database.create', 'database.update', 'database.delete', 'database.view-password',
|
||||
'schedule.read', 'schedule.create', 'schedule.update', 'schedule.delete',
|
||||
'settings.read', 'settings.rename', 'settings.reinstall',
|
||||
'activity.read',
|
||||
];
|
||||
|
||||
public function create(array $data, int $actorId): Role
|
||||
{
|
||||
return DB::transaction(function () use ($data, $actorId) {
|
||||
$role = Role::create([
|
||||
'name' => $data['name'],
|
||||
'description' => $data['description'] ?? null,
|
||||
'color' => $data['color'] ?? '#6366f1',
|
||||
'priority' => $data['priority'] ?? 0,
|
||||
'is_default' => $data['is_default'] ?? false,
|
||||
'created_by' => $actorId,
|
||||
]);
|
||||
|
||||
if (!empty($data['permissions'])) {
|
||||
$this->syncPermissions($role, $data['permissions'], $actorId);
|
||||
}
|
||||
|
||||
RoleAuditLog::record($actorId, 'role.create', 'role', $role->id, ['name' => $role->name]);
|
||||
return $role;
|
||||
});
|
||||
}
|
||||
|
||||
public function update(Role $role, array $data, int $actorId): Role
|
||||
{
|
||||
return DB::transaction(function () use ($role, $data, $actorId) {
|
||||
$role->update(array_filter([
|
||||
'name' => $data['name'] ?? null,
|
||||
'description' => $data['description'] ?? null,
|
||||
'color' => $data['color'] ?? null,
|
||||
'priority' => $data['priority'] ?? null,
|
||||
'is_default' => $data['is_default'] ?? null,
|
||||
], fn ($v) => $v !== null));
|
||||
|
||||
if (isset($data['permissions'])) {
|
||||
$this->syncPermissions($role, $data['permissions'], $actorId);
|
||||
}
|
||||
|
||||
RoleAuditLog::record($actorId, 'role.update', 'role', $role->id, $data);
|
||||
return $role->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
public function delete(Role $role, int $actorId, bool $force = false): void
|
||||
{
|
||||
if ($role->isInUse() && !$force) {
|
||||
throw ValidationException::withMessages([
|
||||
'role' => ['This role is still assigned to users. Pass force=true to delete anyway.'],
|
||||
]);
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($role, $actorId) {
|
||||
RoleAuditLog::record($actorId, 'role.delete', 'role', $role->id, ['name' => $role->name]);
|
||||
$role->delete();
|
||||
});
|
||||
}
|
||||
|
||||
public function syncPermissions(Role $role, array $permissions, int $actorId): void
|
||||
{
|
||||
// Validate all permission names
|
||||
$invalid = array_diff(array_keys($permissions), self::KNOWN_PERMISSIONS);
|
||||
if (!empty($invalid)) {
|
||||
throw ValidationException::withMessages([
|
||||
'permissions' => ['Unknown permissions: ' . implode(', ', $invalid)],
|
||||
]);
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($role, $permissions, $actorId) {
|
||||
$role->permissions()->delete();
|
||||
foreach ($permissions as $perm => $value) {
|
||||
if (!in_array($value, ['allow', 'deny'], true)) continue;
|
||||
RolePermission::create([
|
||||
'role_id' => $role->id,
|
||||
'permission' => $perm,
|
||||
'value' => $value,
|
||||
]);
|
||||
}
|
||||
RoleAuditLog::record($actorId, 'role.permissions_updated', 'role', $role->id, ['permissions' => $permissions]);
|
||||
});
|
||||
}
|
||||
|
||||
public function assignToUser(int $serverId, int $userId, int $roleId, int $actorId): ServerRoleAssignment
|
||||
{
|
||||
// Prevent privilege escalation: actor's max role priority must be >= target role priority
|
||||
$targetRole = Role::findOrFail($roleId);
|
||||
$actorMaxPrio = $this->getMaxPriorityForUser($actorId, $serverId);
|
||||
|
||||
if ($targetRole->priority > $actorMaxPrio) {
|
||||
throw ValidationException::withMessages([
|
||||
'role' => ['You cannot assign a role with higher priority than your own.'],
|
||||
]);
|
||||
}
|
||||
|
||||
$assignment = ServerRoleAssignment::firstOrCreate([
|
||||
'server_id' => $serverId,
|
||||
'user_id' => $userId,
|
||||
'role_id' => $roleId,
|
||||
], [
|
||||
'assigned_by' => $actorId,
|
||||
]);
|
||||
|
||||
RoleAuditLog::record($actorId, 'role.assign', 'user', $userId, [
|
||||
'server_id' => $serverId,
|
||||
'role_id' => $roleId,
|
||||
'role_name' => $targetRole->name,
|
||||
]);
|
||||
|
||||
return $assignment;
|
||||
}
|
||||
|
||||
public function unassignFromUser(int $serverId, int $userId, int $roleId, int $actorId): void
|
||||
{
|
||||
ServerRoleAssignment::query()
|
||||
->where('server_id', $serverId)
|
||||
->where('user_id', $userId)
|
||||
->where('role_id', $roleId)
|
||||
->delete();
|
||||
|
||||
RoleAuditLog::record($actorId, 'role.unassign', 'user', $userId, [
|
||||
'server_id' => $serverId,
|
||||
'role_id' => $roleId,
|
||||
]);
|
||||
}
|
||||
|
||||
public function setOverride(
|
||||
int $serverId,
|
||||
int $userId,
|
||||
string $permission,
|
||||
string $value,
|
||||
int $actorId
|
||||
): ServerPermissionOverride {
|
||||
if (!in_array($permission, self::KNOWN_PERMISSIONS, true)) {
|
||||
throw ValidationException::withMessages(['permission' => ['Unknown permission key.']]);
|
||||
}
|
||||
if (!in_array($value, ['allow', 'deny', 'unset'], true)) {
|
||||
throw ValidationException::withMessages(['value' => ['Value must be allow, deny, or unset.']]);
|
||||
}
|
||||
|
||||
$override = ServerPermissionOverride::updateOrCreate(
|
||||
['server_id' => $serverId, 'user_id' => $userId, 'permission' => $permission],
|
||||
['value' => $value, 'set_by' => $actorId]
|
||||
);
|
||||
|
||||
RoleAuditLog::record($actorId, 'override.set', 'user', $userId, [
|
||||
'server_id' => $serverId,
|
||||
'permission' => $permission,
|
||||
'value' => $value,
|
||||
]);
|
||||
|
||||
return $override;
|
||||
}
|
||||
|
||||
private function getMaxPriorityForUser(int $userId, int $serverId): int
|
||||
{
|
||||
// If admin, return PHP_INT_MAX
|
||||
$user = \Pterodactyl\Models\User::find($userId);
|
||||
if ($user && $user->root_admin) return PHP_INT_MAX;
|
||||
|
||||
return ServerRoleAssignment::query()
|
||||
->where('server_id', $serverId)
|
||||
->where('user_id', $userId)
|
||||
->with('role')
|
||||
->get()
|
||||
->max(fn ($a) => $a->role->priority ?? 0) ?? 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user