From bd309a07aa2504bdaae634aa15efbe2174da1a4b Mon Sep 17 00:00:00 2001 From: Jules Date: Fri, 20 Mar 2026 12:23:54 +0100 Subject: [PATCH] =?UTF-8?q?Systeme=20de=20licence=20via=20Vision=20?= =?UTF-8?q?=E2=80=94=20remplace=20le=20mot=20de=20passe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cote Vision (LXC 112) : - Table licenses (cle, client, produit, actif, timeout hors-ligne, expiration) - API api/license.php (verification publique + CRUD admin) - Onglet Licences dans le dashboard avec modal creation/edition Cote Imposing Tool : - Au lancement, verifie la licence en ligne via vision.copydev.fr - Cache local (.license) pour le mode hors-ligne - Timeout configurable dans Vision (defaut 72h) - Passe le timeout → message "contactez CopyDev contact@copydev.fr" - Premiere activation necessite une connexion internet - Plus de mot de passe Licence de test : COPYDEV-IMPOS-TEST-2026 Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 1 + main.py | 153 +++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 131 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 4adc4ba..a0515c8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ build/output/ venv/ .venv/ *.egg-info/ +.license diff --git a/main.py b/main.py index 7832506..6212941 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,11 @@ import sys import os -import hashlib +import json +import time +import urllib.request +import urllib.error +import urllib.parse # Support PyInstaller (chemin des resources) if getattr(sys, 'frozen', False): @@ -17,30 +21,133 @@ sys.path.insert(0, BUNDLE_DIR) from PyQt6.QtWidgets import QApplication, QInputDialog, QMessageBox, QLineEdit -# Hash SHA-256 du mot de passe — jamais en clair dans le code -_PASSWORD_HASH = "f3dd6962a99a2fe2e03cd8e5cfa7dafb12570bdcca1b6590e27d95dcccdf1f58" - -def _hash_password(pwd): - return hashlib.sha256(pwd.encode("utf-8")).hexdigest() +LICENSE_API = "https://vision.copydev.fr/api/license.php" +LICENSE_FILE = os.path.join(BASE_DIR, ".license") -def _check_auth(): - """Demander le mot de passe au lancement.""" - max_attempts = 3 - for attempt in range(max_attempts): - pwd, ok = QInputDialog.getText( - None, - "Copydev Imposing Tool", - f"Mot de passe requis ({max_attempts - attempt} essai(s) restant(s)) :", - QLineEdit.EchoMode.Password, - ) - if not ok: - return False - if _hash_password(pwd) == _PASSWORD_HASH: +def _load_license_cache(): + """Charger le cache de licence local.""" + if not os.path.exists(LICENSE_FILE): + return None + try: + with open(LICENSE_FILE, "r") as f: + return json.load(f) + except Exception: + return None + + +def _save_license_cache(data): + """Sauvegarder le cache de licence local.""" + try: + with open(LICENSE_FILE, "w") as f: + json.dump(data, f) + except Exception: + pass + + +def _check_license_online(key): + """Verifier la licence aupres du serveur Vision.""" + try: + url = f"{LICENSE_API}?key={urllib.parse.quote(key)}" + req = urllib.request.Request(url, headers={"User-Agent": "CopydevImposingTool/1.0"}) + with urllib.request.urlopen(req, timeout=10) as resp: + data = json.loads(resp.read().decode()) + if data.get("valid"): + cache = { + "key": key, + "client": data.get("client", ""), + "product": data.get("product", ""), + "offline_timeout_hours": data.get("offline_timeout_hours", 72), + "last_validated": time.time(), + } + _save_license_cache(cache) + return True, data.get("client", "") + return False, "Licence invalide" + except urllib.error.HTTPError as e: + try: + body = json.loads(e.read().decode()) + return False, body.get("error", "Erreur serveur") + except Exception: + return False, f"Erreur HTTP {e.code}" + except Exception as e: + return None, str(e) # None = hors-ligne + + +def _check_license(): + """Verifier la licence au lancement.""" + cache = _load_license_cache() + + # Si on a un cache, essayer de valider en ligne + if cache and cache.get("key"): + key = cache["key"] + result, msg = _check_license_online(key) + + if result is True: + # Licence valide en ligne return True - if attempt < max_attempts - 1: - QMessageBox.warning(None, "Erreur", "Mot de passe incorrect.") - QMessageBox.critical(None, "Acces refuse", "Trop de tentatives. L'application va se fermer.") + + if result is False: + # Licence refusee par le serveur + QMessageBox.critical( + None, "Licence invalide", + f"Votre licence a ete refusee :\n{msg}\n\n" + "Contactez CopyDev : contact@copydev.fr" + ) + return False + + # result is None = hors-ligne, verifier le timeout + timeout_hours = cache.get("offline_timeout_hours", 72) + last_validated = cache.get("last_validated", 0) + elapsed_hours = (time.time() - last_validated) / 3600 + + if elapsed_hours < timeout_hours: + remaining = timeout_hours - elapsed_hours + return True + + # Timeout depasse + QMessageBox.critical( + None, "Licence expiree hors-ligne", + f"Impossible de verifier votre licence depuis {int(elapsed_hours)}h.\n" + f"Le delai hors-ligne autorise est de {timeout_hours}h.\n\n" + "Connectez-vous a internet ou contactez CopyDev :\n" + "contact@copydev.fr" + ) + return False + + # Pas de cache — demander la cle de licence + key, ok = QInputDialog.getText( + None, + "Copydev Imposing Tool — Activation", + "Entrez votre cle de licence :", + QLineEdit.EchoMode.Normal, + ) + if not ok or not key.strip(): + return False + + key = key.strip() + result, msg = _check_license_online(key) + + if result is True: + QMessageBox.information( + None, "Licence activee", + f"Bienvenue, {msg} !\nVotre licence est activee." + ) + return True + + if result is False: + QMessageBox.critical( + None, "Licence invalide", + f"{msg}\n\nContactez CopyDev : contact@copydev.fr" + ) + return False + + # Hors-ligne a la premiere activation + QMessageBox.critical( + None, "Connexion requise", + "Impossible de contacter le serveur de licences.\n" + "Une connexion internet est necessaire pour la premiere activation.\n\n" + "Contactez CopyDev : contact@copydev.fr" + ) return False @@ -55,7 +162,7 @@ def main(): if os.path.exists(icon_path): app.setWindowIcon(QIcon(icon_path)) - if not _check_auth(): + if not _check_license(): sys.exit(1) from app.main_window import MainWindow