450 Zeilen
17 KiB
Python
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"] |