Files
test-main/licensing/license_manager.py
Claude Project Manager 08ed938105 Initial commit
2025-07-03 21:11:05 +02:00

450 Zeilen
17 KiB
Python

"""
Lizenzverwaltungsfunktionalität für den Social Media Account Generator.
"""
import os
import json
import time
import uuid
import hmac
import hashlib
import logging
import requests
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional, Tuple, Union
logger = logging.getLogger("license_manager")
class LicenseManager:
"""Klasse zur Verwaltung von Softwarelizenzen."""
CONFIG_FILE = os.path.join("config", "license.json")
LICENSE_SERVER_URL = "https://api.example.com/license" # Platzhalter - in der Produktion anpassen
def __init__(self):
"""Initialisiert den LicenseManager und lädt die Konfiguration."""
self.license_data = self.load_license_data()
self.machine_id = self.get_machine_id()
# Stelle sicher, dass das Konfigurationsverzeichnis existiert
os.makedirs(os.path.dirname(self.CONFIG_FILE), exist_ok=True)
# Prüfe die Lizenz beim Start
self.verify_license()
def load_license_data(self) -> Dict[str, Any]:
"""Lädt die Lizenzdaten aus der Konfigurationsdatei."""
if not os.path.exists(self.CONFIG_FILE):
return {
"key": "",
"activation_date": "",
"expiry_date": "",
"status": "inactive",
"status_text": "Keine Lizenz aktiviert",
"features": [],
"last_online_check": "",
"signature": ""
}
try:
with open(self.CONFIG_FILE, "r", encoding="utf-8") as f:
license_data = json.load(f)
logger.info(f"Lizenzdaten geladen: Status '{license_data.get('status', 'unbekannt')}'")
return license_data
except Exception as e:
logger.error(f"Fehler beim Laden der Lizenzdaten: {e}")
return {
"key": "",
"activation_date": "",
"expiry_date": "",
"status": "inactive",
"status_text": "Fehler beim Laden der Lizenz",
"features": [],
"last_online_check": "",
"signature": ""
}
def save_license_data(self) -> bool:
"""Speichert die Lizenzdaten in die Konfigurationsdatei."""
try:
with open(self.CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(self.license_data, f, indent=2)
logger.info("Lizenzdaten gespeichert")
return True
except Exception as e:
logger.error(f"Fehler beim Speichern der Lizenzdaten: {e}")
return False
def get_license_info(self) -> Dict[str, Any]:
"""Gibt die aktuellen Lizenzdaten zurück."""
return self.license_data
def get_machine_id(self) -> str:
"""
Generiert eine eindeutige Maschinen-ID.
Returns:
Eindeutige Maschinen-ID
"""
try:
# Versuche, eine eindeutige Hardware-ID zu generieren
# In der Produktion sollte dies mit einer robusteren Methode ersetzt werden
machine_id_file = os.path.join("config", ".machine_id")
if os.path.exists(machine_id_file):
# Bestehende ID laden
with open(machine_id_file, "r") as f:
return f.read().strip()
else:
# Neue ID generieren
machine_id = str(uuid.uuid4())
with open(machine_id_file, "w") as f:
f.write(machine_id)
return machine_id
except Exception as e:
logger.error(f"Fehler bei der Generierung der Maschinen-ID: {e}")
# Fallback: UUID auf Basis der aktuellen Zeit
return str(uuid.uuid5(uuid.NAMESPACE_DNS, f"fallback-{time.time()}"))
def is_licensed(self) -> bool:
"""
Überprüft, ob eine gültige Lizenz vorhanden ist.
Returns:
True, wenn eine gültige Lizenz vorhanden ist, sonst False
"""
# Prüfe den Status der Lizenz
if self.license_data["status"] not in ["active", "trial"]:
return False
# Prüfe, ob die Lizenz abgelaufen ist
if self.license_data["expiry_date"]:
try:
expiry_date = datetime.fromisoformat(self.license_data["expiry_date"])
if datetime.now() > expiry_date:
logger.warning("Lizenz ist abgelaufen")
self.license_data["status"] = "expired"
self.license_data["status_text"] = "Lizenz abgelaufen"
self.save_license_data()
return False
except Exception as e:
logger.error(f"Fehler beim Parsen des Ablaufdatums: {e}")
return False
# Prüfe, ob regelmäßige Online-Verifizierung erforderlich ist
if self.license_data["last_online_check"]:
try:
last_check = datetime.fromisoformat(self.license_data["last_online_check"])
max_offline_days = 7 # Maximale Tage ohne Online-Check
if datetime.now() > last_check + timedelta(days=max_offline_days):
logger.warning(f"Letzte Online-Überprüfung ist mehr als {max_offline_days} Tage her")
# Versuche, eine Online-Überprüfung durchzuführen
if not self.online_verification():
self.license_data["status"] = "verification_required"
self.license_data["status_text"] = "Online-Überprüfung erforderlich"
self.save_license_data()
return False
except Exception as e:
logger.error(f"Fehler bei der Überprüfung der Online-Verifizierung: {e}")
# Prüfe die Signatur (in der Produktion sollte dies erweitert werden)
if not self.verify_signature():
logger.warning("Ungültige Lizenzsignatur")
self.license_data["status"] = "invalid"
self.license_data["status_text"] = "Ungültige Lizenz (manipuliert)"
self.save_license_data()
return False
return True
def verify_license(self) -> bool:
"""
Überprüft die aktuelle Lizenz.
Returns:
True, wenn die Lizenz gültig ist, sonst False
"""
# Lizenzschlüssel vorhanden?
if not self.license_data["key"]:
logger.info("Kein Lizenzschlüssel vorhanden")
self.license_data["status"] = "inactive"
self.license_data["status_text"] = "Keine Lizenz aktiviert"
self.save_license_data()
return False
return self.is_licensed()
def create_signature(self, data: str) -> str:
"""
Erstellt eine Signatur für die angegebenen Daten.
Args:
data: Zu signierende Daten
Returns:
Signatur als Hexadezimalstring
"""
# In der Produktion sollte ein sicherer Schlüssel verwendet werden
secret_key = "development_secret_key"
# HMAC-SHA256-Signatur erstellen
signature = hmac.new(
secret_key.encode(),
data.encode(),
hashlib.sha256
).hexdigest()
return signature
def verify_signature(self) -> bool:
"""
Überprüft die Signatur der Lizenzdaten.
Returns:
True, wenn die Signatur gültig ist, sonst False
"""
if not self.license_data["signature"]:
return False
# Daten für die Signaturprüfung vorbereiten
data_to_verify = f"{self.license_data['key']}|{self.machine_id}|{self.license_data['activation_date']}|{self.license_data['expiry_date']}"
# Signatur erstellen
computed_signature = self.create_signature(data_to_verify)
# Signatur vergleichen
return computed_signature == self.license_data["signature"]
def online_verification(self) -> bool:
"""
Führt eine Online-Überprüfung der Lizenz durch.
Returns:
True, wenn die Überprüfung erfolgreich war, sonst False
"""
if not self.license_data["key"]:
return False
try:
# Daten für die Lizenzüberprüfung
verification_data = {
"license_key": self.license_data["key"],
"machine_id": self.machine_id,
"product_version": "1.0.0", # In der Produktion aus einer Konfiguration laden
"timestamp": time.time()
}
# Anfrage an den Lizenzserver senden
response = requests.post(
self.LICENSE_SERVER_URL + "/verify",
json=verification_data,
timeout=10
)
if response.status_code == 200:
result = response.json()
if result.get("status") == "active":
# Lizenz ist gültig
logger.info("Online-Lizenzüberprüfung erfolgreich")
# Aktualisiere das Datum der letzten Überprüfung
self.license_data["last_online_check"] = datetime.now().isoformat()
self.save_license_data()
return True
else:
# Lizenz ist ungültig
logger.warning(f"Lizenz ungültig: {result.get('message', 'Unbekannter Fehler')}")
self.license_data["status"] = result.get("status", "invalid")
self.license_data["status_text"] = result.get("message", "Lizenz ungültig")
self.save_license_data()
return False
else:
logger.warning(f"Fehler bei der Online-Überprüfung: HTTP {response.status_code}")
return False
except requests.RequestException as e:
logger.error(f"Netzwerkfehler bei der Online-Überprüfung: {e}")
# Bei Verbindungsproblemen sollte die lokale Lizenz weiterhin gültig bleiben
# In der Produktion kann hier eine Begrenzung der Offline-Zeit implementiert werden
return True
except Exception as e:
logger.error(f"Unerwarteter Fehler bei der Online-Überprüfung: {e}")
return False
def activate_license(self, license_key: str) -> Tuple[bool, str]:
"""
Aktiviert eine Lizenz mit dem angegebenen Schlüssel.
Args:
license_key: Zu aktivierender Lizenzschlüssel
Returns:
(Erfolg, Nachricht)
"""
if not license_key:
return False, "Bitte geben Sie einen Lizenzschlüssel ein."
try:
# In der Produktionsumgebung sollte hier eine Online-Aktivierung erfolgen
# Für Entwicklungszwecke implementieren wir eine einfache lokale Aktivierung
# Simulierte Online-Aktivierung
activation_data = {
"license_key": license_key,
"machine_id": self.machine_id,
"product_version": "1.0.0",
"timestamp": time.time()
}
# Nur für Entwicklung: Prüfe, ob der Lizenzschlüssel bekannt ist
if license_key.startswith("DEV-"):
# Entwicklungslizenzen haben unbegrenzte Laufzeit
expiry_date = (datetime.now() + timedelta(days=365)).isoformat()
activation_response = {
"status": "active",
"message": "Entwicklungslizenz aktiviert",
"activation_date": datetime.now().isoformat(),
"expiry_date": expiry_date,
"features": ["all"]
}
elif license_key.startswith("TRIAL-"):
# Trial-Lizenzen haben begrenzte Laufzeit
expiry_date = (datetime.now() + timedelta(days=30)).isoformat()
activation_response = {
"status": "trial",
"message": "Trial-Lizenz aktiviert (30 Tage)",
"activation_date": datetime.now().isoformat(),
"expiry_date": expiry_date,
"features": ["basic"]
}
else:
# Alle anderen Schlüssel simulieren eine Online-Aktivierung
try:
# Anfrage an den Lizenzserver senden
response = requests.post(
self.LICENSE_SERVER_URL + "/activate",
json=activation_data,
timeout=10
)
if response.status_code == 200:
activation_response = response.json()
else:
logger.warning(f"Fehler bei der Lizenzaktivierung: HTTP {response.status_code}")
return False, f"Fehler bei der Lizenzaktivierung: HTTP {response.status_code}"
except requests.RequestException as e:
logger.error(f"Netzwerkfehler bei der Lizenzaktivierung: {e}")
return False, f"Netzwerkfehler bei der Lizenzaktivierung: {e}"
except Exception as e:
logger.error(f"Unerwarteter Fehler bei der Lizenzaktivierung: {e}")
return False, f"Unerwarteter Fehler bei der Lizenzaktivierung: {e}"
# Lizenzdaten aktualisieren
self.license_data["key"] = license_key
self.license_data["status"] = activation_response.get("status", "inactive")
self.license_data["status_text"] = activation_response.get("message", "Unbekannter Status")
self.license_data["activation_date"] = activation_response.get("activation_date", datetime.now().isoformat())
self.license_data["expiry_date"] = activation_response.get("expiry_date", "")
self.license_data["features"] = activation_response.get("features", [])
self.license_data["last_online_check"] = datetime.now().isoformat()
# Signatur erstellen
data_to_sign = f"{self.license_data['key']}|{self.machine_id}|{self.license_data['activation_date']}|{self.license_data['expiry_date']}"
self.license_data["signature"] = self.create_signature(data_to_sign)
# Lizenzdaten speichern
self.save_license_data()
logger.info(f"Lizenz '{license_key}' erfolgreich aktiviert: {self.license_data['status_text']}")
return True, self.license_data["status_text"]
except Exception as e:
error_msg = f"Fehler bei der Lizenzaktivierung: {e}"
logger.error(error_msg)
return False, error_msg
def deactivate_license(self) -> Tuple[bool, str]:
"""
Deaktiviert die aktuelle Lizenz.
Returns:
(Erfolg, Nachricht)
"""
if not self.license_data["key"]:
return False, "Keine Lizenz aktiviert"
old_key = self.license_data["key"]
try:
# Online-Deaktivierung simulieren
deactivation_data = {
"license_key": self.license_data["key"],
"machine_id": self.machine_id,
"timestamp": time.time()
}
# Anfrage für die Produktionsumgebung
# response = requests.post(
# self.LICENSE_SERVER_URL + "/deactivate",
# json=deactivation_data,
# timeout=10
# )
# Lizenzdaten zurücksetzen
self.license_data = {
"key": "",
"activation_date": "",
"expiry_date": "",
"status": "inactive",
"status_text": "Keine Lizenz aktiviert",
"features": [],
"last_online_check": "",
"signature": ""
}
# Lizenzdaten speichern
self.save_license_data()
logger.info(f"Lizenz '{old_key}' erfolgreich deaktiviert")
return True, "Lizenz erfolgreich deaktiviert"
except Exception as e:
error_msg = f"Fehler bei der Lizenzdeaktivierung: {e}"
logger.error(error_msg)
return False, error_msg
def has_feature(self, feature_name: str) -> bool:
"""
Überprüft, ob die aktuelle Lizenz eine bestimmte Funktion unterstützt.
Args:
feature_name: Name der zu überprüfenden Funktion
Returns:
True, wenn die Funktion unterstützt wird, sonst False
"""
if not self.is_licensed():
return False
# "all" bedeutet, dass alle Funktionen unterstützt werden
if "all" in self.license_data["features"]:
return True
return feature_name in self.license_data["features"]