Files
Nexus/server/utils/rdp_config.py
T

152 lines
4.2 KiB
Python
Raw Normal View History

"""Server-level RDP settings (stored in ``servers.extra_attrs``)."""
from __future__ import annotations
from dataclasses import dataclass
from server.domain.models import Server
RDP_ENABLED_ATTR = "rdp_enabled"
RDP_PORT_ATTR = "rdp_port"
RDP_USERNAME_ATTR = "rdp_username"
RDP_PASSWORD_ATTR = "rdp_password"
DEFAULT_RDP_PORT = 3389
@dataclass(frozen=True)
class RdpConfig:
enabled: bool
port: int
username: str
password_set: bool
def _attrs(server: Server) -> dict:
raw = server.extra_attrs
return raw if isinstance(raw, dict) else {}
def normalize_rdp_port(value: object | None) -> int:
if value is None or value == "":
return DEFAULT_RDP_PORT
try:
port = int(value)
except (TypeError, ValueError):
return DEFAULT_RDP_PORT
if 1 <= port <= 65535:
return port
return DEFAULT_RDP_PORT
def get_rdp_config(server: Server) -> RdpConfig:
attrs = _attrs(server)
enabled = bool(attrs.get(RDP_ENABLED_ATTR))
port = normalize_rdp_port(attrs.get(RDP_PORT_ATTR))
username = str(attrs.get(RDP_USERNAME_ATTR) or "").strip()
password_set = bool(str(attrs.get(RDP_PASSWORD_ATTR) or "").strip())
return RdpConfig(
enabled=enabled,
port=port,
username=username,
password_set=password_set,
)
def get_rdp_password_plain(server: Server) -> str:
enc = str(_attrs(server).get(RDP_PASSWORD_ATTR) or "").strip()
if not enc:
return ""
from server.infrastructure.database.crypto import decrypt_value
try:
return decrypt_value(enc)
except Exception:
return ""
def merge_rdp_into_extra_attrs(
extra_attrs: dict | None,
*,
enabled: bool | None = None,
port: int | None = None,
username: str | None = None,
password_enc: str | None = None,
clear_password: bool = False,
) -> dict | None:
if (
enabled is None
and port is None
and username is None
and password_enc is None
and not clear_password
):
return extra_attrs
out = dict(extra_attrs or {})
if enabled is not None:
out[RDP_ENABLED_ATTR] = bool(enabled)
if port is not None:
out[RDP_PORT_ATTR] = normalize_rdp_port(port)
if username is not None:
out[RDP_USERNAME_ATTR] = username.strip()
if clear_password:
out.pop(RDP_PASSWORD_ATTR, None)
elif password_enc is not None:
out[RDP_PASSWORD_ATTR] = password_enc
return out
def apply_rdp_payload(data: dict, existing_extra: dict | None = None) -> None:
"""Pop RDP API fields from payload and merge into ``extra_attrs``."""
enabled = data.pop("rdp_enabled", None)
port = data.pop("rdp_port", None)
username = data.pop("rdp_username", None)
password = data.pop("rdp_password", None)
if enabled is None and port is None and username is None and password is None:
return
from server.infrastructure.database.crypto import encrypt_value
existing = dict(existing_extra or data.get("extra_attrs") or {})
password_enc = None
clear_password = False
if password is not None:
plain = str(password).strip()
if plain:
password_enc = encrypt_value(plain)
else:
clear_password = True
data["extra_attrs"] = merge_rdp_into_extra_attrs(
existing,
enabled=enabled,
port=port if port is not None else None,
username=username if username is not None else None,
password_enc=password_enc,
clear_password=clear_password,
)
def build_guacamole_connect_string(
hostname: str,
port: int,
username: str,
password: str,
) -> str:
"""Key=value string for guacamole-common-js ``client.connect()``."""
pairs = [
("hostname", hostname.strip()),
("port", str(normalize_rdp_port(port))),
("username", username.strip()),
("password", password),
("security", "any"),
("ignore-cert", "true"),
("disable-wallpaper", "true"),
("disable-theming", "true"),
("enable-font-smoothing", "true"),
("enable-full-window-drag", "false"),
("enable-desktop-composition", "false"),
("enable-menu-animations", "false"),
]
return "&".join(f"{k}={v}" for k, v in pairs if v != "")