Files
AccountForger-neuerUpload/licensing/session_manager.py
Claude Project Manager 04585e95b6 Initial commit
2025-08-01 23:50:28 +02:00

440 Zeilen
16 KiB
Python

"""
Session Manager für die Lizenz-Session-Verwaltung mit Heartbeat.
"""
import threading
import time
import logging
import json
import os
import requests
from datetime import datetime
from typing import Optional, Dict, Any
from .api_client import LicenseAPIClient
from .hardware_fingerprint import HardwareFingerprint
logger = logging.getLogger("session_manager")
logger.setLevel(logging.DEBUG)
# Füge Console Handler hinzu falls noch nicht vorhanden
if not logger.handlers:
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)
class SessionManager:
"""Verwaltet die Lizenz-Session und Heartbeat."""
SESSION_FILE = os.path.join("config", ".session_data")
HEARTBEAT_INTERVAL = 60 # Sekunden
def __init__(self, api_client: Optional[LicenseAPIClient] = None):
"""
Initialisiert den Session Manager.
Args:
api_client: Optional vorkonfigurierter API Client
"""
self.api_client = api_client or LicenseAPIClient()
self.hardware_fingerprint = HardwareFingerprint()
self.session_token: Optional[str] = None
self.license_key: Optional[str] = None
self.activation_id: Optional[int] = None
self.heartbeat_thread: Optional[threading.Thread] = None
self.stop_heartbeat = threading.Event()
self.is_active = False
# Lade Session-IP-Konfiguration
self._load_ip_config()
# Session-Daten laden falls vorhanden
self._load_session_data()
def _save_session_data(self) -> None:
"""Speichert die aktuelle Session-Daten."""
try:
os.makedirs("config", exist_ok=True)
session_data = {
"session_token": self.session_token,
"license_key": self.license_key,
"activation_id": self.activation_id,
"timestamp": datetime.now().isoformat()
}
with open(self.SESSION_FILE, 'w') as f:
json.dump(session_data, f)
logger.debug("Session-Daten gespeichert")
except Exception as e:
logger.error(f"Fehler beim Speichern der Session-Daten: {e}")
def _load_session_data(self) -> None:
"""Lädt gespeicherte Session-Daten."""
if os.path.exists(self.SESSION_FILE):
try:
with open(self.SESSION_FILE, 'r') as f:
data = json.load(f)
self.session_token = data.get("session_token")
self.license_key = data.get("license_key")
self.activation_id = data.get("activation_id")
logger.info("Session-Daten geladen")
except Exception as e:
logger.warning(f"Fehler beim Laden der Session-Daten: {e}")
def _clear_session_data(self) -> None:
"""Löscht die gespeicherten Session-Daten."""
try:
if os.path.exists(self.SESSION_FILE):
os.remove(self.SESSION_FILE)
logger.debug("Session-Daten gelöscht")
except Exception as e:
logger.error(f"Fehler beim Löschen der Session-Daten: {e}")
def start_session(self, license_key: str, activation_id: Optional[int] = None) -> Dict[str, Any]:
"""
Startet eine neue Session für die Lizenz.
Args:
license_key: Der Lizenzschlüssel
activation_id: Optional die Aktivierungs-ID
Returns:
Dictionary mit Session-Informationen oder Fehler
"""
if self.is_active:
logger.warning("Session läuft bereits")
return {
"success": False,
"error": "Session already active"
}
# Hardware-Info sammeln
hw_hash = self.hardware_fingerprint.get_or_create_fingerprint()
machine_name = self.hardware_fingerprint.get_machine_name()
# IP-Adresse ermitteln
client_ip = self._get_session_ip()
logger.info(f"Starte Session für Lizenz: {license_key[:4]}...")
logger.debug(f"Session-Parameter: machine_name={machine_name}, hw_hash={hw_hash[:8]}..., ip={client_ip}")
# Session-Start API Call mit IP-Adresse
result = self.api_client.start_session(
license_key=license_key,
machine_id=machine_name,
hardware_hash=hw_hash,
version="1.0.0", # TODO: Version aus config lesen
ip_address=client_ip # NEU: IP-Adresse hinzugefügt
)
logger.debug(f"Session-Start Response: {result}")
if result.get("success"):
data = result.get("data", {})
# Prüfe ob die Session wirklich erfolgreich war
if data.get("success") is False:
# Session wurde abgelehnt
error_msg = data.get("message", "Session start failed")
logger.error(f"Session abgelehnt: {error_msg}")
return {
"success": False,
"error": error_msg,
"code": "SESSION_REJECTED"
}
self.session_token = data.get("session_token")
self.license_key = license_key
self.activation_id = activation_id or data.get("activation_id")
self.is_active = True if self.session_token else False
# Session-Daten speichern
self._save_session_data()
# Heartbeat starten
self._start_heartbeat()
logger.info(f"Session erfolgreich gestartet: {self.session_token}")
# Update-Info prüfen
if data.get("update_available"):
logger.info(f"Update verfügbar: {data.get('latest_version')}")
return {
"success": True,
"session_token": self.session_token,
"update_info": {
"available": data.get("update_available", False),
"version": data.get("latest_version"),
"download_url": data.get("download_url")
}
}
else:
error = result.get("error", "Unknown error")
logger.error(f"Session-Start fehlgeschlagen: {error}")
# Bei Konflikt (409) bedeutet es, dass bereits eine Session läuft
if result.get("status") == 409:
return {
"success": False,
"error": "Another session is already active for this license",
"code": "SESSION_CONFLICT"
}
return {
"success": False,
"error": error,
"code": result.get("code", "SESSION_START_FAILED")
}
def _start_heartbeat(self) -> None:
"""Startet den Heartbeat-Thread."""
if self.heartbeat_thread and self.heartbeat_thread.is_alive():
logger.warning("Heartbeat läuft bereits")
return
self.stop_heartbeat.clear()
self.heartbeat_thread = threading.Thread(
target=self._heartbeat_worker,
daemon=True,
name="LicenseHeartbeat"
)
self.heartbeat_thread.start()
logger.info("Heartbeat-Thread gestartet")
def _heartbeat_worker(self) -> None:
"""Worker-Funktion für den Heartbeat-Thread."""
logger.info(f"Heartbeat-Worker gestartet (Interval: {self.HEARTBEAT_INTERVAL}s)")
while not self.stop_heartbeat.is_set():
try:
# Warte das Interval oder bis Stop-Signal
if self.stop_heartbeat.wait(self.HEARTBEAT_INTERVAL):
break
# Sende Heartbeat
if self.session_token and self.license_key:
logger.debug("Sende Heartbeat...")
result = self.api_client.session_heartbeat(
session_token=self.session_token,
license_key=self.license_key
)
if result.get("success"):
logger.debug("Heartbeat erfolgreich")
else:
logger.error(f"Heartbeat fehlgeschlagen: {result.get('error')}")
# Bei bestimmten Fehlern Session beenden
if result.get("status") in [401, 404]:
logger.error("Session ungültig, beende...")
self.end_session()
break
else:
logger.warning("Keine Session-Daten für Heartbeat")
except Exception as e:
logger.error(f"Fehler im Heartbeat-Worker: {e}")
logger.info("Heartbeat-Worker beendet")
def end_session(self) -> Dict[str, Any]:
"""
Beendet die aktuelle Session.
Returns:
Dictionary mit Informationen über die beendete Session
"""
if not self.is_active:
logger.warning("Keine aktive Session zum Beenden")
return {
"success": False,
"error": "No active session"
}
logger.info("Beende Session...")
# Heartbeat stoppen
self.stop_heartbeat.set()
if self.heartbeat_thread:
self.heartbeat_thread.join(timeout=5)
# Session beenden API Call
result = {"success": True}
if self.session_token:
result = self.api_client.end_session(self.session_token)
if result.get("success"):
logger.info("Session erfolgreich beendet")
else:
logger.error(f"Fehler beim Beenden der Session: {result.get('error')}")
# Session-Daten löschen
self.session_token = None
self.license_key = None
self.activation_id = None
self.is_active = False
self._clear_session_data()
return result
def resume_session(self) -> bool:
"""
Versucht eine gespeicherte Session fortzusetzen.
Returns:
True wenn erfolgreich, False sonst
"""
if self.is_active:
logger.info("Session läuft bereits")
return True
if not self.session_token or not self.license_key:
logger.info("Keine gespeicherten Session-Daten vorhanden")
return False
logger.info("Versuche Session fortzusetzen...")
# Teste mit Heartbeat ob Session noch gültig ist
result = self.api_client.session_heartbeat(
session_token=self.session_token,
license_key=self.license_key
)
if result.get("success"):
logger.info("Session erfolgreich fortgesetzt")
self.is_active = True
self._start_heartbeat()
return True
else:
logger.warning("Gespeicherte Session ungültig")
self._clear_session_data()
return False
def is_session_active(self) -> bool:
"""
Prüft ob eine Session aktiv ist.
Returns:
True wenn aktiv, False sonst
"""
return self.is_active
def get_session_info(self) -> Dict[str, Any]:
"""
Gibt Informationen über die aktuelle Session zurück.
Returns:
Dictionary mit Session-Informationen
"""
return {
"active": self.is_active,
"session_token": self.session_token[:8] + "..." if self.session_token else None,
"license_key": self.license_key[:4] + "..." if self.license_key else None,
"activation_id": self.activation_id,
"heartbeat_interval": self.HEARTBEAT_INTERVAL
}
def set_heartbeat_interval(self, seconds: int) -> None:
"""
Setzt das Heartbeat-Interval.
Args:
seconds: Interval in Sekunden (min 30, max 300)
"""
if 30 <= seconds <= 300:
self.HEARTBEAT_INTERVAL = seconds
logger.info(f"Heartbeat-Interval auf {seconds}s gesetzt")
# Restart Heartbeat wenn aktiv
if self.is_active:
self.stop_heartbeat.set()
if self.heartbeat_thread:
self.heartbeat_thread.join(timeout=5)
self._start_heartbeat()
else:
logger.warning(f"Ungültiges Heartbeat-Interval: {seconds}")
def _load_ip_config(self) -> None:
"""Lädt die IP-Konfiguration aus license_config.json."""
config_path = os.path.join("config", "license_config.json")
self.session_ip_mode = "auto" # Default
self.ip_fallback = "0.0.0.0"
try:
if os.path.exists(config_path):
with open(config_path, 'r') as f:
config = json.load(f)
self.session_ip_mode = config.get("session_ip_mode", "auto")
self.ip_fallback = config.get("ip_fallback", "0.0.0.0")
logger.debug(f"IP-Konfiguration geladen: mode={self.session_ip_mode}, fallback={self.ip_fallback}")
except Exception as e:
logger.warning(f"Fehler beim Laden der IP-Konfiguration: {e}")
def _get_session_ip(self) -> str:
"""
Ermittelt die IP-Adresse für die Session basierend auf der Konfiguration.
TESTBETRIEB: Temporäre Lösung - wird durch Server-Ressourcenmanagement ersetzt
Returns:
Die IP-Adresse als String
"""
if self.session_ip_mode == "auto":
# TESTBETRIEB: Auto-Erkennung der öffentlichen IP
logger.info("TESTBETRIEB: Ermittle öffentliche IP-Adresse automatisch")
try:
response = requests.get("https://api.ipify.org?format=json", timeout=5)
if response.status_code == 200:
ip = response.json().get("ip")
logger.info(f"Öffentliche IP ermittelt: {ip}")
return ip
else:
logger.warning(f"IP-Ermittlung fehlgeschlagen: Status {response.status_code}")
except Exception as e:
logger.error(f"Fehler bei IP-Ermittlung: {e}")
# Fallback verwenden
logger.warning(f"Verwende Fallback-IP: {self.ip_fallback}")
return self.ip_fallback
elif self.session_ip_mode == "server_assigned":
# TODO: Implementierung für Server-zugewiesene IPs
logger.info("Server-assigned IP mode noch nicht implementiert, verwende Fallback")
return self.ip_fallback
elif self.session_ip_mode == "proxy":
# TODO: Proxy-IP verwenden wenn Proxy aktiv
logger.info("Proxy IP mode noch nicht implementiert, verwende Fallback")
return self.ip_fallback
else:
logger.warning(f"Unbekannter IP-Modus: {self.session_ip_mode}, verwende Fallback")
return self.ip_fallback
# Test-Funktion
if __name__ == "__main__":
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
print("=== Session Manager Test ===\n")
# Session Manager erstellen
session_mgr = SessionManager()
# Session-Info anzeigen
print("Aktuelle Session-Info:")
info = session_mgr.get_session_info()
for key, value in info.items():
print(f" {key}: {value}")
# Versuche gespeicherte Session fortzusetzen
print("\nVersuche Session fortzusetzen...")
if session_mgr.resume_session():
print(" ✓ Session fortgesetzt")
else:
print(" ✗ Keine gültige Session gefunden")
print("\n=== Test abgeschlossen ===")