refactor(rbac): migrate API permission checks to resource:action format

Replace all legacy dotted permission codes (vms.view, proxmox.manage, ...)
in PermissionChecker/has_permission across app/api with the new
resource:action format (vm:view, server:manage, ...), per LEGACY_PERMISSION_MAP.
All 33 codes used by the API are now registered in the permission registry.

Also switch the /api/auth/me admin branch to get_all_permissions_v2 so the
endpoint emits permissions in the new format only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-20 02:07:48 +05:00
parent 57513c4dc9
commit de967c35e0
19 changed files with 220 additions and 220 deletions

View File

@@ -37,7 +37,7 @@ def check_vm_access(db: Session, current_user: User, server_id: int, vmid: int)
if current_user.role and current_user.role.permissions:
perms = current_user.role.permissions
# If user has full vms:view, they can access all VMs
has_full_view = perms.get('vms.view', False) or perms.get('vms:view', False)
has_full_view = perms.get('vm:view', False) or perms.get('vms:view', False)
has_own_only = perms.get('vms:view:own', False) or perms.get('vms.view.own', False)
if has_full_view and not has_own_only:

View File

@@ -59,7 +59,7 @@ def _get_password_client(server: ProxmoxServer, override_password: str = None) -
@router.get("/api/cluster/topology")
def get_cluster_topology(
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.view")),
current_user: User = Depends(PermissionChecker("server:view")),
):
"""
Получить все серверы, сгруппированные по кластеру.
@@ -94,7 +94,7 @@ def get_cluster_topology(
async def create_cluster(
body: dict,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.cluster.manage")),
current_user: User = Depends(PermissionChecker("cluster:manage")),
):
"""
Создать новый Proxmox corosync кластер на указанной ноде.
@@ -175,7 +175,7 @@ async def create_cluster(
def get_cluster_join_info(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.cluster.manage")),
current_user: User = Depends(PermissionChecker("cluster:manage")),
):
"""
Получить fingerprint и join-данные с кластерной ноды.
@@ -213,7 +213,7 @@ def get_cluster_join_info(
def pre_join_check(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.cluster.manage")),
current_user: User = Depends(PermissionChecker("cluster:manage")),
):
"""
Проверить ноду перед присоединением к кластеру:
@@ -287,7 +287,7 @@ def pre_join_check(
async def prepare_join(
body: dict,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.cluster.manage")),
current_user: User = Depends(PermissionChecker("cluster:manage")),
):
"""
Создать резервные копии всех VM/LXC на ноде через vzdump и удалить их,
@@ -446,7 +446,7 @@ async def prepare_join(
async def join_cluster(
body: dict,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.cluster.manage")),
current_user: User = Depends(PermissionChecker("cluster:manage")),
):
"""
Присоединить standalone-ноду к существующему кластеру.
@@ -580,7 +580,7 @@ def eject_cluster_node(
server_id: int,
body: dict,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.cluster.manage")),
current_user: User = Depends(PermissionChecker("cluster:manage")),
):
"""
Удалить ноду из кластера Proxmox.

View File

@@ -356,7 +356,7 @@ def _do_lxc_deploy(task_id: int, req: LXCDeployRequest, user_id: int, username:
def list_lxc_templates(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view")),
current_user: User = Depends(PermissionChecker("vm:view")),
):
"""
Список LXC CT-шаблонов (vztmpl) на хранилищах выбранного Proxmox сервера.
@@ -382,7 +382,7 @@ def list_lxc_storages(
server_id: int,
node: Optional[str] = None,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view")),
current_user: User = Depends(PermissionChecker("vm:view")),
):
"""
Список хранилищ ноды, пригодных для rootfs LXC контейнера.
@@ -422,7 +422,7 @@ def list_lxc_storages(
async def deploy_lxc_from_template(
req: LXCDeployRequest,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.create")),
current_user: User = Depends(PermissionChecker("vm:create")),
):
"""
Асинхронное создание LXC контейнера из CT-шаблона.
@@ -438,7 +438,7 @@ async def deploy_lxc_from_template(
raise HTTPException(status_code=400, detail="Укажите имя контейнера")
# Resolve owner / SSH keys
is_admin = current_user.has_permission("users.manage") or current_user.is_admin
is_admin = current_user.has_permission("user:manage") or current_user.is_admin
instance_owner_id = current_user.id
if req.owner_id and req.owner_id != current_user.id:
if not is_admin:

View File

@@ -18,7 +18,7 @@ router = APIRouter()
def get_server_nodes(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.view"))
current_user: User = Depends(PermissionChecker("server:view"))
):
"""List nodes for a Proxmox server"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -41,7 +41,7 @@ def list_node_networks(
server_id: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.view"))
current_user: User = Depends(PermissionChecker("server:view"))
):
"""List all network interfaces on a node, with IPAM linkage info."""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -84,7 +84,7 @@ async def create_node_network(
node: str,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Create a new network interface on a node."""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -124,7 +124,7 @@ async def update_node_network(
iface: str,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Update a network interface on a node."""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -155,7 +155,7 @@ def delete_node_network(
node: str,
iface: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Delete a network interface from a node."""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -184,7 +184,7 @@ def apply_node_network(
server_id: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Apply pending network configuration changes on a node."""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -213,7 +213,7 @@ def revert_node_network(
server_id: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Revert pending network configuration changes on a node."""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()

View File

@@ -20,7 +20,7 @@ router = APIRouter()
def get_sdn_status(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.view"))
current_user: User = Depends(PermissionChecker("server:view"))
):
"""Check if SDN is available on the server"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -52,7 +52,7 @@ def get_sdn_status(
def get_sdn_zones(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.view"))
current_user: User = Depends(PermissionChecker("server:view"))
):
"""Get all SDN zones"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -74,7 +74,7 @@ async def create_sdn_zone(
server_id: int,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Create a new SDN zone"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -112,7 +112,7 @@ def delete_sdn_zone(
server_id: int,
zone: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Delete an SDN zone"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -140,7 +140,7 @@ def delete_sdn_zone(
def get_sdn_vnets(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.view"))
current_user: User = Depends(PermissionChecker("server:view"))
):
"""Get all SDN VNets"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -162,7 +162,7 @@ async def create_sdn_vnet(
server_id: int,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Create a new SDN VNet"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -201,7 +201,7 @@ def delete_sdn_vnet(
server_id: int,
vnet: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Delete an SDN VNet"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -230,7 +230,7 @@ def get_sdn_subnets(
server_id: int,
vnet: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.view"))
current_user: User = Depends(PermissionChecker("server:view"))
):
"""Get subnets for a VNet"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -253,7 +253,7 @@ async def create_sdn_subnet(
vnet: str,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Create a subnet in a VNet"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -317,7 +317,7 @@ async def create_sdn_subnet(
def apply_sdn_changes(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Apply pending SDN changes"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -347,7 +347,7 @@ async def update_sdn_zone(
zone: str,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Update an existing SDN zone"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -376,7 +376,7 @@ async def update_sdn_vnet(
vnet: str,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Update an existing SDN VNet"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -406,7 +406,7 @@ def delete_sdn_subnet(
subnet_cidr: str,
delete_ipam_network: bool = False,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Delete a subnet from a VNet. subnet_cidr uses '-' instead of '/' (e.g. 10.0.0.0-24)."""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()

View File

@@ -29,7 +29,7 @@ router = APIRouter()
def list_proxmox_servers(
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.view"))
current_user: User = Depends(PermissionChecker("server:view"))
):
"""Получить список всех Proxmox серверов (фильтруется по активному workspace)"""
from ...api.workspaces import get_workspace_server_ids
@@ -83,7 +83,7 @@ def list_proxmox_servers(
def create_proxmox_server(
server_data: ProxmoxServerCreate,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.servers.add"))
current_user: User = Depends(PermissionChecker("server:create"))
):
"""Добавить новый Proxmox сервер"""
# Разрешаем несколько серверов с одинаковым IP (например, разные порты или кластеры)
@@ -152,7 +152,7 @@ def create_proxmox_server(
def get_proxmox_server(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.view"))
current_user: User = Depends(PermissionChecker("server:view"))
):
"""Получить информацию о конкретном Proxmox сервере"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -165,7 +165,7 @@ def get_proxmox_server(
def get_server_cluster_info(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.view"))
current_user: User = Depends(PermissionChecker("server:view"))
):
"""
Получить информацию о кластерном режиме сервера.
@@ -216,7 +216,7 @@ def update_proxmox_server(
server_id: int,
server_data: ProxmoxServerUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.servers.edit"))
current_user: User = Depends(PermissionChecker("server:update"))
):
"""Обновить Proxmox сервер"""
from ...proxmox import clear_server_cache
@@ -290,7 +290,7 @@ def update_proxmox_server(
def delete_proxmox_server(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.servers.delete"))
current_user: User = Depends(PermissionChecker("server:delete"))
):
"""Удалить Proxmox сервер и связанные OS Templates"""
from ...proxmox import clear_server_cache
@@ -378,7 +378,7 @@ def delete_proxmox_server(
@router.post("/api/servers/test", status_code=status.HTTP_200_OK)
def test_proxmox_credentials(
payload: ProxmoxServerCreate,
current_user: User = Depends(PermissionChecker("proxmox.view"))
current_user: User = Depends(PermissionChecker("server:view"))
):
"""Проверить произвольные учётные данные Proxmox без сохранения в БД."""
try:
@@ -410,7 +410,7 @@ def test_proxmox_credentials(
def test_proxmox_connection(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.view"))
current_user: User = Depends(PermissionChecker("server:view"))
):
"""Проверить подключение к Proxmox серверу"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -443,7 +443,7 @@ def test_proxmox_connection(
def get_server_resources(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""API для получения всех ресурсов (VM + LXC) с Proxmox сервера"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -514,7 +514,7 @@ def get_server_resources(
@router.get("/api/resources/all")
def get_all_resources(
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""API для получения всех ресурсов со всех Proxmox серверов"""
proxmox_servers = db.query(ProxmoxServer).all()
@@ -595,7 +595,7 @@ def get_storages(
node: str = Query(..., description="Node name"),
content_type: str = Query(None, description="Filter by content type (images, rootdir, vztmpl, etc)"),
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.view"))
current_user: User = Depends(PermissionChecker("server:view"))
):
"""Получить список хранилищ на ноде Proxmox"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -633,7 +633,7 @@ def get_node_status(
server_id: int,
node: str = Query(..., description="Node name"),
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить текущий статус ноды Proxmox (хоста)"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -662,7 +662,7 @@ def get_node_rrddata(
node: str = Query(..., description="Node name"),
timeframe: str = Query("hour", regex="^(hour|day|week|month|year)$"),
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить исторические данные ноды для графиков"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()

View File

@@ -31,7 +31,7 @@ def get_vm_snapshots(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Get all snapshots for a VM"""
# VPS-style user isolation
@@ -60,7 +60,7 @@ async def create_vm_snapshot(
node: str,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Create a VM snapshot"""
# VPS-style user isolation
@@ -114,7 +114,7 @@ def delete_vm_snapshot(
snapname: str,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Delete a VM snapshot"""
# VPS-style user isolation
@@ -161,7 +161,7 @@ def rollback_vm_snapshot(
node: str,
start: bool = False,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Rollback a VM to a snapshot"""
# VPS-style user isolation
@@ -208,7 +208,7 @@ def get_container_snapshots(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Get all snapshots for a container"""
# VPS-style user isolation
@@ -237,7 +237,7 @@ async def create_container_snapshot(
node: str,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Create a container snapshot"""
# VPS-style user isolation
@@ -290,7 +290,7 @@ def delete_container_snapshot(
snapname: str,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Delete a container snapshot"""
# VPS-style user isolation
@@ -337,7 +337,7 @@ def rollback_container_snapshot(
node: str,
start: bool = False,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Rollback a container to a snapshot"""
# VPS-style user isolation
@@ -385,7 +385,7 @@ def get_snapshot_archives(
limit: int = Query(50, ge=1, le=200),
offset: int = Query(0, ge=0),
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("logs.view"))
current_user: User = Depends(PermissionChecker("log:view"))
):
"""
Get archived snapshots from deleted VMs/containers.
@@ -432,7 +432,7 @@ def get_snapshot_archives(
def get_snapshot_archive_detail(
archive_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("logs.view"))
current_user: User = Depends(PermissionChecker("log:view"))
):
"""Get full details of an archived snapshot including config"""
archive = db.query(VMSnapshotArchive).filter(VMSnapshotArchive.id == archive_id).first()

View File

@@ -85,7 +85,7 @@ def get_task_status(
upid: str,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить статус задачи по UPID"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -115,7 +115,7 @@ def get_task_log(
start: int = 0,
limit: int = 50,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить лог задачи по UPID"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()

View File

@@ -58,7 +58,7 @@ class VMOwnerUpdate(BaseModel):
@router.post("/api/sync-vms")
def sync_vms_now(
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""
Force immediate synchronization of VMs/containers from all Proxmox servers.
@@ -89,7 +89,7 @@ def sync_vms_now(
def get_all_virtual_machines(
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""
API для получения всех VM/LXC в плоском списке для таблицы.
@@ -145,7 +145,7 @@ def get_all_virtual_machines(
query = query.filter(VMInstance.server_id.in_(workspace_server_ids))
# VPS-style user isolation: users with 'user' role see only their own instances
# Check if user has 'vms.view' but not 'vms.view:all' (or is user role)
# Check if user has 'vm:view' but not 'vms.view:all' (or is user role)
user_role = current_user.role.name if current_user.role else None
is_limited_user = user_role == 'user'
@@ -154,7 +154,7 @@ def get_all_virtual_machines(
perms = current_user.role.permissions or {}
# If user has vms:view:own but not vms:view (full), filter by owner
has_view_own = perms.get('vms:view:own', False) or perms.get('vms.view.own', False)
has_view_all = perms.get('vms:view', False) or perms.get('vms.view', False)
has_view_all = perms.get('vms:view', False) or perms.get('vm:view', False)
# Limited users can only see their own VMs
if is_limited_user or (has_view_own and not has_view_all):
@@ -288,7 +288,7 @@ def execute_vm_command(
command: str = Query(..., description="Команда для выполнения"),
timeout: int = Query(30, ge=1, le=300, description="Таймаут в секундах"),
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.console"))
current_user: User = Depends(PermissionChecker("vm:console"))
):
"""
Выполнить команду на VM через QEMU guest agent
@@ -325,7 +325,7 @@ def execute_vm_script(
interpreter: str = Form("/bin/bash", description="Путь к интерпретатору"),
timeout: int = Query(60, ge=1, le=600, description="Таймаут в секундах"),
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.console"))
current_user: User = Depends(PermissionChecker("vm:console"))
):
"""
Выполнить bash скрипт на VM через QEMU guest agent
@@ -388,7 +388,7 @@ def clone_vm_endpoint(
node: str,
body: CloneRequest,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.create"))
current_user: User = Depends(PermissionChecker("vm:create"))
):
"""Клонировать существующую VM (qemu) — фоновая задача."""
from ...models import DeployTask
@@ -419,7 +419,7 @@ def clone_container_endpoint(
node: str,
body: CloneRequest,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.create"))
current_user: User = Depends(PermissionChecker("vm:create"))
):
"""Клонировать существующий LXC контейнер — фоновая задача."""
from ...models import DeployTask
@@ -451,7 +451,7 @@ def reinstall_vm_endpoint(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.delete"))
current_user: User = Depends(PermissionChecker("vm:delete"))
):
"""Переустановить VM/LXC из исходного шаблона — фоновая задача."""
from ...models import DeployTask, OSTemplate
@@ -504,7 +504,7 @@ def change_vm_password_endpoint(
node: str,
body: ChangePasswordRequest,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.console"))
current_user: User = Depends(PermissionChecker("vm:console"))
):
"""Сменить пароль на VM через QEMU guest agent — фоновая задача."""
from ...models import DeployTask
@@ -544,7 +544,7 @@ def change_container_password_endpoint(
node: str,
body: ChangePasswordRequest,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.console"))
current_user: User = Depends(PermissionChecker("vm:console"))
):
"""Сменить пароль в LXC через pct exec/chpasswd — фоновая задача."""
from ...models import DeployTask
@@ -595,10 +595,10 @@ def control_vm(
# Проверка прав в зависимости от действия
permission_map = {
'start': 'vms.start',
'stop': 'vms.stop',
'shutdown': 'vms.stop',
'restart': 'vms.restart',
'start': 'vm:start',
'stop': 'vm:stop',
'shutdown': 'vm:stop',
'restart': 'vm:restart',
}
if not current_user.has_permission(permission_map[action]):
raise HTTPException(
@@ -738,7 +738,7 @@ async def delete_vm(
node: str,
force: bool = Query(False, description="Force delete without saving config"),
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.delete"))
current_user: User = Depends(PermissionChecker("vm:delete"))
):
"""Удалить VM"""
from ...i18n import t
@@ -940,7 +940,7 @@ async def delete_vm(
def get_ha_status(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить статус HA кластера и список ресурсов в HA"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -977,7 +977,7 @@ def get_resource_ha_status(
vm_type: str,
vmid: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить HA статус конкретной VM или контейнера"""
if vm_type not in ['vm', 'ct']:
@@ -1031,7 +1031,7 @@ def add_resource_to_ha(
state: str = Query("started", description="Target state: started, stopped, enabled, disabled, ignored"),
comment: str = Query(None, description="Comment"),
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Добавить VM или контейнер в HA"""
if vm_type not in ['vm', 'ct']:
@@ -1094,7 +1094,7 @@ def remove_resource_from_ha(
vm_type: str,
vmid: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Удалить VM или контейнер из HA"""
if vm_type not in ['vm', 'ct']:
@@ -1145,7 +1145,7 @@ async def delete_container(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.delete"))
current_user: User = Depends(PermissionChecker("vm:delete"))
):
"""Удалить LXC контейнер"""
from ...i18n import t
@@ -1269,7 +1269,7 @@ def get_vm_config(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить конфигурацию VM"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -1296,7 +1296,7 @@ async def update_vm_config(
node: str,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Обновить конфигурацию VM (CPU, Memory, etc.)"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -1331,7 +1331,7 @@ async def resize_vm_disk(
node: str,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Изменить размер диска VM"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -1384,7 +1384,7 @@ async def resize_container_disk(
node: str,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Изменить размер диска контейнера"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -1441,7 +1441,7 @@ def get_container_config(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить конфигурацию LXC контейнера"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -1468,7 +1468,7 @@ async def update_container_config(
node: str,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Обновить конфигурацию LXC контейнера"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -1512,10 +1512,10 @@ def control_container(
# Проверка прав в зависимости от действия
permission_map = {
'start': 'vms.start',
'stop': 'vms.stop',
'shutdown': 'vms.stop',
'restart': 'vms.restart',
'start': 'vm:start',
'stop': 'vm:stop',
'shutdown': 'vm:stop',
'restart': 'vm:restart',
}
require_permission(current_user, permission_map[action])
@@ -1594,7 +1594,7 @@ def control_container(
def get_server_nodes(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.view"))
current_user: User = Depends(PermissionChecker("server:view"))
):
"""Получить список нод Proxmox сервера вместе с идентификатором кластера"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -1639,7 +1639,7 @@ def get_vm_status(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить детальный статус VM (CPU, RAM, Disk, Network)"""
require_vm_access(db, current_user, server_id, vmid)
@@ -1666,7 +1666,7 @@ def get_container_status(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить детальный статус LXC контейнера"""
require_vm_access(db, current_user, server_id, vmid)
@@ -1693,7 +1693,7 @@ def get_vm_interfaces(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить сетевые интерфейсы и IP адреса VM через QEMU guest agent"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -1719,7 +1719,7 @@ def get_container_interfaces(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить сетевые интерфейсы и IP адреса контейнера"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -1745,7 +1745,7 @@ def get_container_status_api(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить статус контейнера"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -1772,7 +1772,7 @@ def get_vm_rrddata(
node: str,
timeframe: str = Query("hour", regex="^(hour|day|week|month|year)$"),
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить исторические данные VM для графиков (CPU, RAM, Network, Disk IO)"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -1799,7 +1799,7 @@ def get_container_rrddata(
node: str,
timeframe: str = Query("hour", regex="^(hour|day|week|month|year)$"),
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Получить исторические данные контейнера для графиков"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -1827,7 +1827,7 @@ def get_vm_vnc(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.console"))
current_user: User = Depends(PermissionChecker("vm:console"))
):
"""Получить VNC данные для подключения к VM"""
import requests
@@ -1914,7 +1914,7 @@ def get_container_vnc(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.console"))
current_user: User = Depends(PermissionChecker("vm:console"))
):
"""Получить VNC данные для подключения к LXC контейнеру"""
import requests
@@ -2017,7 +2017,7 @@ async def vnc_websocket_proxy(
if not user:
await websocket.close(code=4001, reason="Authentication required")
return
if not PermissionEngine.has_permission(user, "vms.console"):
if not PermissionEngine.has_permission(user, "vm:console"):
await websocket.close(code=4003, reason="Permission denied")
return
if not check_vm_access(db, user, server_id, vmid):
@@ -2158,7 +2158,7 @@ def get_saved_vm_config(
server_id: int,
vmid: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Get saved VM configuration from database"""
instance = get_vm_instance(db, server_id, vmid)
@@ -2190,7 +2190,7 @@ def get_saved_vm_config(
def get_all_lxc_templates(
server_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("templates.view"))
current_user: User = Depends(PermissionChecker("template:view"))
):
"""Получить список LXC шаблонов со всех нод кластера с информацией о shared storage"""
logger.info(f"[LXC API] Request for ALL templates from server_id={server_id}")
@@ -2219,7 +2219,7 @@ def get_available_lxc_templates(
server_id: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("templates.view"))
current_user: User = Depends(PermissionChecker("template:view"))
):
"""Получить список шаблонов доступных для загрузки из репозитория"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -2247,7 +2247,7 @@ async def download_lxc_template(
server_id: int,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("templates.manage"))
current_user: User = Depends(PermissionChecker("template:manage"))
):
"""Скачать шаблон LXC из репозитория"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -2318,7 +2318,7 @@ def get_vm_terminal(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.console"))
current_user: User = Depends(PermissionChecker("vm:console"))
):
"""Получить данные для xterm.js терминального подключения к VM"""
import requests
@@ -2391,7 +2391,7 @@ def get_container_terminal(
vmid: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.console"))
current_user: User = Depends(PermissionChecker("vm:console"))
):
"""Получить данные для xterm.js терминального подключения к LXC контейнеру"""
import requests
@@ -2465,7 +2465,7 @@ async def exec_in_container(
node: str,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.console"))
current_user: User = Depends(PermissionChecker("vm:console"))
):
"""Выполнить команду в LXC контейнере через pct exec"""
server = db.query(ProxmoxServer).filter(ProxmoxServer.id == server_id).first()
@@ -2520,7 +2520,7 @@ async def terminal_websocket_fixed(
if not user:
await websocket.close(code=4001, reason="Authentication required")
return
if not PermissionEngine.has_permission(user, "vms.console"):
if not PermissionEngine.has_permission(user, "vm:console"):
await websocket.close(code=4003, reason="Permission denied")
return
if not check_vm_access(db, user, server_id, vmid):
@@ -2693,7 +2693,7 @@ async def terminal_websocket_fixed(
def create_bulk_operation(
request: BulkOperationRequest,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.vm.manage"))
current_user: User = Depends(PermissionChecker("vm:manage"))
):
"""
Create a bulk operation task.
@@ -2717,7 +2717,7 @@ def create_bulk_operation(
# For delete action, require higher permission
if request.action == 'delete':
if not check_permission(current_user, "vms.delete"):
if not check_permission(current_user, "vm:delete"):
raise HTTPException(status_code=403, detail="Delete permission required")
# Convert to list of dicts
@@ -2842,7 +2842,7 @@ def update_vm_notes(
node: str,
body: NotesRequest,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Обновить заметку (description) у VM."""
require_vm_access(db, current_user, server_id, vmid)
@@ -2872,7 +2872,7 @@ def update_container_notes(
node: str,
body: NotesRequest,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Обновить заметку (description) у LXC."""
require_vm_access(db, current_user, server_id, vmid)
@@ -2902,7 +2902,7 @@ def list_node_isos(
server_id: int,
node: str,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("vms.view"))
current_user: User = Depends(PermissionChecker("vm:view"))
):
"""Список ISO-образов на ноде."""
server = _resolve_server(db, server_id)
@@ -2917,7 +2917,7 @@ def attach_iso_endpoint(
node: str,
body: IsoAttachRequest,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Подключить ISO (CD-ROM) к VM."""
require_vm_access(db, current_user, server_id, vmid)
@@ -2942,7 +2942,7 @@ def detach_iso_endpoint(
node: str,
body: IsoDetachRequest,
db: Session = Depends(get_db),
current_user: User = Depends(PermissionChecker("proxmox.manage"))
current_user: User = Depends(PermissionChecker("server:manage"))
):
"""Отключить ISO (CD-ROM) у VM."""
require_vm_access(db, current_user, server_id, vmid)