152 lines
4.2 KiB
Python
152 lines
4.2 KiB
Python
|
|
"""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 != "")
|