Initial commit
Dieser Commit ist enthalten in:
450
licensing/license_manager.py
Normale Datei
450
licensing/license_manager.py
Normale Datei
@ -0,0 +1,450 @@
|
||||
"""
|
||||
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"]
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren