Systeme de licence via Vision — remplace le mot de passe
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) <noreply@anthropic.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ build/output/
|
||||
venv/
|
||||
.venv/
|
||||
*.egg-info/
|
||||
.license
|
||||
|
||||
153
main.py
153
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
|
||||
|
||||
Reference in New Issue
Block a user