""" Pydantic-Schemas fuer alle API-Eingaben/-Ausgaben. Strikte Validierung ist Teil des Hardening-Konzepts (6.6): Laenge, Typ und erlaubte Zeichen werden hier durchgesetzt, bevor irgendein Wert die Business-Logik oder die Datenbank erreicht. """ from __future__ import annotations import re from typing import Literal from pydantic import BaseModel, Field, field_validator USERNAME_RE = re.compile(r"^[a-zA-Z0-9._-]{3,64}$") HOSTNAME_LABEL_RE = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$") class LoginRequest(BaseModel): username: str = Field(min_length=3, max_length=64) password: str = Field(min_length=1, max_length=256) @field_validator("username") @classmethod def check_username(cls, v: str) -> str: if not USERNAME_RE.match(v): raise ValueError("Ungueltiger Benutzername") return v class TotpLoginRequest(BaseModel): pending_token: str code: str = Field(min_length=6, max_length=64) class TotpConfirmRequest(BaseModel): code: str = Field(min_length=6, max_length=6, pattern=r"^\d{6}$") class ChangePasswordRequest(BaseModel): current_password: str = Field(min_length=1, max_length=256) new_password: str = Field(min_length=12, max_length=256) class UserCreateRequest(BaseModel): username: str = Field(min_length=3, max_length=64) initial_password: str = Field(min_length=12, max_length=256) is_admin: bool = False @field_validator("username") @classmethod def check_username(cls, v: str) -> str: if not USERNAME_RE.match(v): raise ValueError("Ungueltiger Benutzername") return v class HostGroupCreateRequest(BaseModel): name: str = Field(min_length=1, max_length=128) description: str | None = Field(default=None, max_length=1024) class HostCreateRequest(BaseModel): host_group_id: int hostname: str = Field(min_length=1, max_length=128) address: str = Field(min_length=1, max_length=255) protocol: Literal["ssh", "rdp"] port: int = Field(gt=0, le=65535) os_type: Literal["linux", "windows"] ssh_host_key_fingerprint: str | None = Field(default=None, max_length=512) ssh_username: str | None = Field(default=None, max_length=128) rdp_username: str | None = Field(default=None, max_length=128) rdp_domain: str | None = Field(default=None, max_length=128) rdp_require_nla: bool = True clipboard_enabled: bool = True file_transfer_enabled: bool = True @field_validator("hostname") @classmethod def check_hostname(cls, v: str) -> str: if not HOSTNAME_LABEL_RE.match(v): raise ValueError("Ungueltiger Hostname") return v class RoleGrantRequest(BaseModel): user_id: int host_group_id: int role_name: Literal[ "ssh_connect", "rdp_connect", "file_transfer", "clipboard", "session_recording_view", "admin_hostgroup", ] expires_at: str | None = None class SshKeyCreateRequest(BaseModel): label: str = Field(min_length=1, max_length=128) owner_user_id: int | None = None private_key_pem: str = Field(min_length=1, max_length=32_768) public_key: str = Field(min_length=1, max_length=8192) key_type: Literal["ed25519", "rsa-3072", "rsa-4096", "ca-cert"] class ConnectRequest(BaseModel): host_id: int class RdpCredentialsRequest(BaseModel): password: str = Field(min_length=1, max_length=512)