220 lines
8.0 KiB
Python
220 lines
8.0 KiB
Python
import hashlib
|
|
import platform
|
|
import uuid
|
|
import requests
|
|
from datetime import datetime
|
|
from typing import Dict
|
|
from PyQt6.QtCore import QObject, pyqtSignal
|
|
import os
|
|
from dotenv import load_dotenv
|
|
import app.utils.paths as paths
|
|
|
|
# Load environment variables
|
|
load_dotenv(paths.resource_path(".env"))
|
|
|
|
class LicenseManager(QObject):
|
|
"""Gestionnaire de licences avec liaison matérielle"""
|
|
|
|
license_activated = pyqtSignal(str) # Signal émis lors de l'activation
|
|
license_expired = pyqtSignal()
|
|
|
|
def __init__(self, settings_manager):
|
|
super().__init__()
|
|
self.settings_manager = settings_manager
|
|
self.licensing_enabled = settings_manager.get_config("enable_licensing")
|
|
|
|
# Si le système de licence est désactivé, initialiser en mode gratuit
|
|
if not self.licensing_enabled:
|
|
self.hardware_id = None
|
|
self.license_key = ""
|
|
self.license_data = None
|
|
return
|
|
|
|
# Charger l'URL de l'API depuis .env
|
|
self.api_url = os.getenv("LICENSE_API_URL")
|
|
if not self.api_url:
|
|
raise ValueError("LICENSE_API_URL non définie dans le fichier .env")
|
|
|
|
self.hardware_id = self._get_hardware_id()
|
|
|
|
# Charger la licence sauvegardée
|
|
self.license_key = settings_manager.settings.value("license_key", "")
|
|
self.license_data = None
|
|
|
|
if self.license_key:
|
|
self._load_license_data()
|
|
|
|
def _get_hardware_id(self) -> str:
|
|
"""Génère un identifiant unique basé sur le matériel"""
|
|
# Combine plusieurs éléments matériels pour un ID unique
|
|
mac = ':'.join(['{:02x}'.format((uuid.getnode() >> elements) & 0xff)
|
|
for elements in range(0, 2*6, 2)][::-1])
|
|
system = platform.system()
|
|
machine = platform.machine()
|
|
processor = platform.processor()
|
|
|
|
# Créer un hash unique
|
|
unique_string = f"{mac}{system}{machine}{processor}"
|
|
return hashlib.sha256(unique_string.encode()).hexdigest()
|
|
|
|
def get_hardware_id(self) -> str:
|
|
"""Retourne le hardware ID pour l'afficher à l'utilisateur"""
|
|
return self.hardware_id
|
|
|
|
def activate_license(self, license_key: str) -> Dict:
|
|
"""
|
|
Active une licence avec le serveur
|
|
|
|
Returns:
|
|
dict: {"success": bool, "message": str, "data": dict}
|
|
"""
|
|
try:
|
|
response = requests.post(
|
|
f"{self.api_url}/activate",
|
|
json={
|
|
"license_key": license_key,
|
|
"hardware_id": self.hardware_id,
|
|
"app_version": self.settings_manager.get_config("app_version"),
|
|
"platform": platform.system()
|
|
},
|
|
timeout=10
|
|
)
|
|
|
|
data = response.json()
|
|
|
|
if response.status_code == 200 and data.get("success"):
|
|
# Sauvegarder la licence
|
|
self.license_key = license_key
|
|
self.license_data = data.get("license_data", {})
|
|
self.settings_manager.settings.setValue("license_key", license_key)
|
|
self.settings_manager.settings.setValue("license_data", self.license_data)
|
|
|
|
self.license_activated.emit(license_key)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Licence activée avec succès",
|
|
"data": self.license_data
|
|
}
|
|
else:
|
|
return {
|
|
"success": False,
|
|
"message": data.get("message", "Erreur d'activation"),
|
|
"data": None
|
|
}
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
return {
|
|
"success": False,
|
|
"message": f"Erreur de connexion au serveur: {str(e)}",
|
|
"data": None
|
|
}
|
|
|
|
def verify_license(self) -> bool:
|
|
"""Vérifie la validité de la licence avec le serveur"""
|
|
if not self.license_key:
|
|
return False
|
|
|
|
try:
|
|
response = requests.post(
|
|
f"{self.api_url}/verify",
|
|
json={
|
|
"license_key": self.license_key,
|
|
"hardware_id": self.hardware_id
|
|
},
|
|
timeout=10
|
|
)
|
|
|
|
data = response.json()
|
|
|
|
if response.status_code == 200 and data.get("valid"):
|
|
self.license_data = data.get("license_data", {})
|
|
self.settings_manager.settings.setValue("license_data", self.license_data)
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
except requests.exceptions.RequestException:
|
|
# En cas d'erreur réseau, utiliser les données en cache
|
|
return self._verify_offline()
|
|
|
|
def _verify_offline(self) -> bool:
|
|
"""Vérification hors ligne basique"""
|
|
if not self.license_key or not self.license_data:
|
|
return False
|
|
|
|
# Vérifier que le hardware_id correspond
|
|
if self.license_data.get("hardware_id") != self.hardware_id:
|
|
return False
|
|
|
|
# Vérifier la date d'expiration si applicable
|
|
expires_at = self.license_data.get("expires_at")
|
|
if expires_at:
|
|
expiry_date = datetime.fromisoformat(expires_at)
|
|
if datetime.now() > expiry_date:
|
|
self.license_expired.emit()
|
|
return False
|
|
|
|
return True
|
|
|
|
def _load_license_data(self):
|
|
"""Charge les données de licence depuis les settings"""
|
|
self.license_data = self.settings_manager.settings.value("license_data", {})
|
|
|
|
def is_activated(self) -> bool:
|
|
"""Vérifie si l'application est activée"""
|
|
# Si le système de licence est désactivé, toujours retourner False
|
|
if not self.licensing_enabled:
|
|
return False
|
|
|
|
return bool(self.license_key) and self.verify_license()
|
|
|
|
def get_license_type(self) -> str:
|
|
"""Retourne le type de licence (free, premium, etc.)"""
|
|
# Si le système de licence est désactivé, retourner "free"
|
|
if not self.licensing_enabled:
|
|
return None
|
|
|
|
if not self.license_data:
|
|
return None
|
|
return self.license_data.get("type", None)
|
|
|
|
def is_feature_available(self, feature_id: str) -> bool:
|
|
"""
|
|
Vérifie si une fonctionnalité est disponible
|
|
|
|
Args:
|
|
feature_id: Identifiant de la fonctionnalité (ex: "advanced_export")
|
|
"""
|
|
# Si le système de licence est désactivé, toutes les fonctionnalités sont disponibles
|
|
if not self.licensing_enabled:
|
|
return True
|
|
|
|
# Si pas de licence, vérifier dans la config des features gratuites
|
|
if not self.is_activated():
|
|
free_features = self.settings_manager.get_config("features_by_license").get(None, [])
|
|
return feature_id in free_features
|
|
|
|
# Vérifier les features autorisées par la licence
|
|
license_type = self.get_license_type()
|
|
features_by_type = self.settings_manager.get_config("features_by_license")
|
|
allowed_features = features_by_type.get(license_type, [])
|
|
|
|
return feature_id in allowed_features
|
|
|
|
def get_license_info(self) -> Dict:
|
|
"""Retourne les informations de la licence"""
|
|
if not self.license_data:
|
|
return {
|
|
"type": None,
|
|
"status": "inactive",
|
|
"expires_at": None
|
|
}
|
|
|
|
return {
|
|
"type": self.get_license_type(),
|
|
"status": "active" if self.is_activated() else "inactive",
|
|
"expires_at": self.license_data.get("expires_at"),
|
|
"email": self.license_data.get("email"),
|
|
"activated_at": self.license_data.get("activated_at")
|
|
} |