"""Gemeinsame Helfer der Lizenzserver-Blueprints.""" from __future__ import annotations import json from typing import Any from urllib.parse import urljoin from flask import url_for from tesm_core import audit from tesm_core.auth.models import current_user from tesm_core.auth.service import client_ip from tesm_core.extension import core, db, transaction import tesm_licensing as lic from ..services import master_key def log_event( action: str, *, target: str = "", detail: str = "", severity: str = "info", scope: str = "lizenz", ) -> None: """Schreibt ins Aenderungsprotokoll **und** ins laufende Protokoll.""" extension = core() with transaction() as conn: audit.log( conn, actor=current_user.username or "system", actor_id=getattr(current_user, "id", None) or None, action=action, target=target, detail=detail, ip=client_ip(), severity=severity, ) live = extension.registry.get("live_log") if live is not None: text = f"{action} {target}".strip() + (f" -- {detail}" if detail else "") if severity in {"warning", "critical"}: live.warning(scope, text) else: live.info(scope, text) def signing_key() -> master_key.MasterKey: """Signaturschluessel dieser Installation (erzeugt ihn beim ersten Aufruf).""" return master_key.ensure(core().registry["master_key_path"]) def vendor_info() -> dict[str, Any]: """Anbieterdaten, wie sie in jede Lizenz eingebettet werden.""" values = core().settings.get_many( db(), [ "vendor_name", "vendor_email", "vendor_phone", "vendor_address", "vendor_logo_base64", ], ) return { "name": str(values["vendor_name"] or ""), "email": str(values["vendor_email"] or ""), "phone": str(values["vendor_phone"] or ""), "address": str(values["vendor_address"] or ""), "logo_base64": str(values["vendor_logo_base64"] or ""), } def master_endpoint() -> str: return str(core().settings.get(db(), "master_endpoint") or "").rstrip("/") def customer_facing_url(endpoint: str, **values: Any) -> str: """Baut Links, die ein Kunde tatsaechlich oeffnen soll. ``url_for(..., _external=True)`` nimmt den Host-Header der aktuellen Anfrage. Bei einem intern angemeldeten Administrator ist das oft eine interne IP -- fuer den Browser oder das Mailprogramm eines Kunden unbrauchbar. Darum kommt die Basis aus der Einstellung ``master_endpoint``; nur wenn die leer ist (frische Installation), faellt es auf das Host-Header-Verhalten zurueck. """ base = master_endpoint() if not base: return url_for(endpoint, _external=True, **values) return urljoin(base + "/", url_for(endpoint, **values).lstrip("/")) def can_issue_license_server_licenses() -> bool: """Darf diese Instanz Lizenzen fuer weitere Lizenzserver ausstellen? Das haengt an der Rolle der **eigenen** Lizenzdatei, nicht an einem Gruppenrecht. Ein Gruppenrecht koennte sich ein lokaler Administrator eines Sub-Lizenzservers selbst einraeumen; eine von woanders signierte Lizenzdatei nicht. Zusaetzlich verlangt es echte Administratorrechte -- das Ausstellen einer Lizenzserver-Lizenz erzeugt eine neue Vertrauensgrenze und ist damit keine delegierbare Alltagsaufgabe. """ manager = core().license if manager is None: return bool(getattr(current_user, "is_admin", False)) return bool( getattr(current_user, "is_admin", False) and manager.active() and manager.state.role == "master" ) def module_labels() -> dict[str, str]: return dict(lic.MODULE_LABELS) def type_labels() -> dict[str, str]: return dict(lic.LICENSE_TYPE_LABELS) def role_labels() -> dict[str, str]: return dict(lic.ROLE_LABELS) def license_filename(license_id: str, customer_name: str) -> str: safe = "".join( char if char.isalnum() or char in "-_" else "-" for char in (customer_name or "kunde") ).strip("-") return f"tesm-lizenz-{safe.lower() or 'kunde'}-{license_id[:8]}.json" def bundle_bytes(bundle: dict[str, Any]) -> bytes: return (json.dumps(bundle, indent=2, ensure_ascii=False, sort_keys=True) + "\n").encode("utf-8")