diff --git a/.gitignore b/.gitignore index c0590ff..6543188 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,41 @@ -.env -*.txt +# ----- Secrets / configs locales ----- +# Ne versionne JAMAIS de .env (mets un .env.example à la place) +*.env + +# ----- Python ----- +.venv/ __pycache__/ *.pyc +*.pyo +*.pyd *.log -.DS_Store -# Ignorer tous les fichiers de travail dans Excel/dev -Excel/dev/* + +# ----- PyInstaller (builds) ----- +build/ +dist/ +*.spec +*.toc +*.pkg +*.pyz +*.zip + +# ----- Excel ----- +# Fichiers temporaires d'Excel (~$fichier.xlsm) +~$*.xls* +~$*.xlsm +# On versionne 1 seul fichier prod "pivot" ; on ignore les exports datés Excel/prod/Ratio_prod_20*.xlsm !Excel/prod/Ratio_prod.xlsm + +# ----- Dossiers Auth livrables ----- +# On n’embarque pas les exe ni .env dans le repo (tu livreras ça en zip) +Excel/**/Auth/*.exe +Excel/**/Auth/*.dll +Excel/**/Auth/*.pdb +Excel/**/Auth/.env + +# ----- Systèmes / IDE ----- +.DS_Store +Thumbs.db +.idea/ +.vscode/ \ No newline at end of file diff --git a/.idea/Ratio_Inventaires.iml b/.idea/Ratio_Inventaires.iml index b6731d8..5c9e85e 100644 --- a/.idea/Ratio_Inventaires.iml +++ b/.idea/Ratio_Inventaires.iml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..2e02e87 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + mariadb + true + org.mariadb.jdbc.Driver + jdbc:mariadb://162.19.78.131:3306/Acces + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 05172fb..5bcdc71 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/Excel/dev/Auth/auth_cli.py b/Excel/dev/Auth/auth_cli.py new file mode 100644 index 0000000..b84bdd5 --- /dev/null +++ b/Excel/dev/Auth/auth_cli.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +import os, sys, argparse, datetime +import mysql.connector, bcrypt +from dotenv import load_dotenv + +def read_stdin() -> str: + data = sys.stdin.read() + return data.rstrip("\r\n") + +def main(): + load_dotenv() + + ap = argparse.ArgumentParser(description="Auth bcrypt locale pour Excel") + ap.add_argument("--user", required=True, help="NomUtilisateur (clé primaire)") + ap.add_argument("--password-stdin", action="store_true", + help="Lire le mot de passe via STDIN (recommandé)") + args = ap.parse_args() + + if not args.password_stdin: + print("ERR|Utiliser --password-stdin et fournir le mot de passe via STDIN", flush=True) + return 2 + + password = read_stdin() + if not password: + print("ERR|Mot de passe vide", flush=True) + return 2 + + # Récupération des infos de connexion depuis .env + host = os.getenv("DB_HOST") + db = os.getenv("DB_NAME") + user = os.getenv("DB_USER") + pwd = os.getenv("DB_PASSWORD") + + try: + conn = mysql.connector.connect( + host=host, database=db, user=user, password=pwd, connection_timeout=5 + ) + cur = conn.cursor(dictionary=True) + cur.execute(""" + SELECT NomUtilisateur, Nom_complet, Site, MotDePasseHash, DateExpiration + FROM Utilisateurs + WHERE NomUtilisateur = %s + LIMIT 1 + """, (args.user,)) + row = cur.fetchone() + except Exception as e: + print("ERR|Connexion base impossible", flush=True) + return 3 + finally: + try: + cur.close() + conn.close() + except Exception: + pass + + if not row or not row.get("MotDePasseHash"): + print("ERR|Utilisateur ou mot de passe invalide", flush=True) + return 1 + + try: + ok = bcrypt.checkpw(password.encode("utf-8"), + row["MotDePasseHash"].encode("ascii")) + except Exception: + ok = False + + if not ok: + print("ERR|Utilisateur ou mot de passe invalide", flush=True) + return 1 + + # Contrôle expiration (si renseignée) + exp = row.get("DateExpiration") + if exp is not None: + today = datetime.date.today() + exp_date = exp if isinstance(exp, datetime.date) else getattr(exp, "date", lambda: today)() + if exp_date < today: + print(f"ERR|Compte expiré le {exp_date.isoformat()}", flush=True) + return 2 + + site = row.get("Site") or "" + nom = row.get("Nom_complet") or "" + print(f"OK|Site={site}|NomComplet={nom}", flush=True) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/Excel/dev/Ratio_dev.xlsm b/Excel/dev/Ratio_dev.xlsm new file mode 100644 index 0000000..5cd51d6 Binary files /dev/null and b/Excel/dev/Ratio_dev.xlsm differ diff --git a/Excel/prod/Ratio_prod.xlsm b/Excel/prod/Ratio_prod.xlsm index da601b7..5cd51d6 100644 Binary files a/Excel/prod/Ratio_prod.xlsm and b/Excel/prod/Ratio_prod.xlsm differ diff --git a/Scripts/maj_prod_ratio.bat b/Scripts/maj_prod_ratio.bat index a1e4cc0..ed89eda 100644 --- a/Scripts/maj_prod_ratio.bat +++ b/Scripts/maj_prod_ratio.bat @@ -1,39 +1,112 @@ @echo off -setlocal +setlocal EnableExtensions EnableDelayedExpansion -REM === Définition des chemins === -set "DEV=C:\Users\miche\PycharmProjects\Ratio_Inventaires\Excel\dev\Ratio_dev.xlsm" -set "PROD_DIR=C:\Users\miche\PycharmProjects\Ratio_Inventaires\Excel\prod" -set "INSTALL_CLIENT=C:\Users\miche\PycharmProjects\Fichiers_Install_Clients\Ratio_Inventaires\Excel" +REM === PARAMÈTRES A ADAPTER UNE FOIS === +set "ROOT=C:\Users\miche\PycharmProjects\Ratio_Inventaires" +set "DEV_XLSM=%ROOT%\Excel\dev\Ratio_dev.xlsm" +set "DEV_AUTH_PY=%ROOT%\Excel\dev\Auth\auth_cli.py" +set "VENV_PY=%ROOT%\.venv\Scripts\python.exe" -REM === Création du nom de fichier daté === -set "DATESTAMP=%date:~6,4%_%date:~3,2%_%date:~0,2%" +set "PROD_DIR=%ROOT%\Excel\prod" +set "PROD_XLSM=%PROD_DIR%\Ratio_prod.xlsm" +set "PROD_AUTH_DIR=%PROD_DIR%\Auth" + +set "INSTALL_CLIENT=%USERPROFILE%\Desktop\Install_Ratio_Inv" REM <- dossier prêt à livrer au client +set "CLIENT_AUTH_DIR=%INSTALL_CLIENT%\Auth" + +REM === DATE ISO robuste (indépendant des paramètres régionaux) === +for /f %%i in ('powershell -NoProfile -Command "(Get-Date).ToString(\"yyyy_MM_dd\")"') do set "DATESTAMP=%%i" set "ARCHIVE_FILE=%PROD_DIR%\Ratio_prod_%DATESTAMP%.xlsm" -echo Sauvegarde de la version actuelle de PROD... -if exist "%PROD_DIR%\Ratio_prod.xlsm" copy /Y "%PROD_DIR%\Ratio_prod.xlsm" "%ARCHIVE_FILE%" - -echo Mise à jour du fichier Ratio_prod.xlsm depuis DEV... -copy /Y "%DEV%" "%PROD_DIR%\Ratio_prod.xlsm" - -echo Copie vers le dossier INSTALL_CLIENT (version sans date)... -copy /Y "%PROD_DIR%\Ratio_prod.xlsm" "%INSTALL_CLIENT%\Ratio_prod.xlsm" +echo. +echo === [1/6] Préparation des dossiers ======================================== +if not exist "%PROD_DIR%" mkdir "%PROD_DIR%" +if not exist "%PROD_AUTH_DIR%" mkdir "%PROD_AUTH_DIR%" +if not exist "%INSTALL_CLIENT%" mkdir "%INSTALL_CLIENT%" +if not exist "%CLIENT_AUTH_DIR%" mkdir "%CLIENT_AUTH_DIR%" echo. -echo ✔️ Mise à jour complète effectuée (archive, prod, client) -pause -echo Nettoyage des anciennes archives (on garde les 5 plus récentes)... - -pushd "%PROD_DIR%" -setlocal EnableDelayedExpansion -set count=0 - -for /f "delims=" %%f in ('dir /b /o-d "Ratio_prod_????_??_??.xlsm"') do ( - set /a count+=1 - if !count! gtr 5 ( - echo Suppression de l'ancienne archive : %%f - del "%%f" - ) +echo === [2/6] (Optionnel) Build de auth_cli.exe avec PyInstaller ============== +if exist "%VENV_PY%" ( + echo Compilation via venv: %VENV_PY% + "%VENV_PY%" -m PyInstaller --onefile --clean --distpath "%PROD_AUTH_DIR%" "%DEV_AUTH_PY%" + if errorlevel 1 ( + echo [WARN] Echec de compilation. On continue avec l'exe existant s'il est présent. + ) +) else ( + echo [WARN] VENV introuvable: %VENV_PY% -> build ignore ) + +echo. +echo === [3/6] Sauvegarde de la prod courante ================================== +if exist "%PROD_XLSM%" ( + echo Sauvegarde -> "%ARCHIVE_FILE%" + copy /Y "%PROD_XLSM%" "%ARCHIVE_FILE%" >nul +) else ( + echo Pas de prod courante a sauvegarder. +) + +echo. +echo === [4/6] Mise a jour de la prod depuis le dev ============================ +if not exist "%DEV_XLSM%" ( + echo [ERREUR] Fichier DEV introuvable: %DEV_XLSM% + goto :EOF +) +copy /Y "%DEV_XLSM%" "%PROD_XLSM%" >nul +if errorlevel 1 ( + echo [ERREUR] Copie du XLSM vers PROD echouee. + goto :EOF +) + +REM On s'assure que l'exe est bien present et on deblocque Windows (SmartScreen) +if exist "%PROD_AUTH_DIR%\auth_cli.exe" ( + powershell -NoProfile -Command "Unblock-File -Path '%PROD_AUTH_DIR%\auth_cli.exe'" 2>nul +) else ( + echo [ERREUR] auth_cli.exe absent dans %PROD_AUTH_DIR% + echo Lance d'abord la compilation ou copie l'exe manuellement. + goto :EOF +) + +REM Le .env doit exister dans PROD\Auth (modele ou reel) +if not exist "%PROD_AUTH_DIR%\.env" ( + echo [WARN] Aucun .env dans %PROD_AUTH_DIR% -> Pense a y mettre DB_HOST/DB_NAME/DB_USER/DB_PASSWORD +) + +echo. +echo === [5/6] Préparation du dossier client =================================== +copy /Y "%PROD_XLSM%" "%INSTALL_CLIENT%\Ratio_prod.xlsm" >nul +if errorlevel 1 ( + echo [ERREUR] Copie du XLSM vers INSTALL_CLIENT echouee. + goto :EOF +) + +copy /Y "%PROD_AUTH_DIR%\auth_cli.exe" "%CLIENT_AUTH_DIR%\auth_cli.exe" >nul +powershell -NoProfile -Command "Unblock-File -Path '%CLIENT_AUTH_DIR%\auth_cli.exe'" 2>nul + +if exist "%PROD_AUTH_DIR%\.env" ( + copy /Y "%PROD_AUTH_DIR%\.env" "%CLIENT_AUTH_DIR%\.env" >nul +) else ( + echo [INFO] Pas de .env a copier (client). Tu pourras le remplir a la main. +) + +echo. +echo === [6/6] Nettoyage: on garde 5 archives les plus recentes =============== +pushd "%PROD_DIR%" +set count=0 +for /f "delims=" %%f in ('dir /b /o-d "Ratio_prod_20??_??_??.xlsm"') do ( + set /a count+=1 + if !count! gtr 5 ( + echo Suppression: %%f + del "%%f" + ) +) +popd + +echo. +echo [OK] Release terminee : +echo - PROD : "%PROD_XLSM%" +echo - AUTH : "%PROD_AUTH_DIR%\auth_cli.exe" + ".env" +echo - CLIENT: "%INSTALL_CLIENT%\Ratio_prod.xlsm" + "Auth\*" +echo. +pause endlocal -popd \ No newline at end of file diff --git a/bcrypt_check.py b/bcrypt_check.py new file mode 100644 index 0000000..896c317 --- /dev/null +++ b/bcrypt_check.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import sys, argparse, bcrypt + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--hash", required=True) + ap.add_argument("--password-stdin", action="store_true") + args = ap.parse_args() + + if not args.password_stdin: + print("ERR") + return 2 + + password = sys.stdin.read().rstrip("\r\n") + try: + ok = bcrypt.checkpw(password.encode("utf-8"), + args.hash.encode("ascii")) + except Exception: + print("ERR") + return 2 + + print("OK" if ok else "ERR") + return 0 if ok else 1 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/last_ids.txt b/last_ids.txt new file mode 100644 index 0000000..a3bb71b --- /dev/null +++ b/last_ids.txt @@ -0,0 +1,3 @@ +Meudon:3 +Roissy:8 +Saclay:12 diff --git a/migre_bcrypt.py b/migre_bcrypt.py new file mode 100644 index 0000000..70e77ea --- /dev/null +++ b/migre_bcrypt.py @@ -0,0 +1,44 @@ +import os, sys, bcrypt, mysql.connector +from dotenv import load_dotenv + +load_dotenv() + +conn = mysql.connector.connect( + host=os.getenv("DB_HOST"), + database=os.getenv("DB_NAME"), + user=os.getenv("DB_USER"), + password=os.getenv("DB_PASSWORD"), +) +try: + cur = conn.cursor(dictionary=True) + cur.execute(""" + SELECT NomUtilisateur, MotDePasse + FROM Utilisateurs + WHERE MotDePasseHash IS NULL OR MotDePasseHash = '' + """) + rows = cur.fetchall() + print(f"{len(rows)} utilisateur(s) à migrer…") + + for r in rows: + login = r["NomUtilisateur"] + plain = (r["MotDePasse"] or "").encode("utf-8") + if not plain: + print(f"- {login}: mot de passe vide — ignoré") + continue + hashed = bcrypt.hashpw(plain, bcrypt.gensalt(rounds=12)).decode("ascii") + cur.execute(""" + UPDATE Utilisateurs + SET MotDePasseHash = %s + WHERE NomUtilisateur = %s + """, (hashed, login)) + print(f"- {login}: OK") + conn.commit() + print("Migration terminée.") +except Exception as e: + conn.rollback() + print("Erreur:", e) + sys.exit(1) +finally: + cur.close() + conn.close() + diff --git a/mkhash.py b/mkhash.py new file mode 100644 index 0000000..717b671 --- /dev/null +++ b/mkhash.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +import sys, getpass, bcrypt + +def main(): + if len(sys.argv) > 1: + password = sys.argv[1] + else: + password = getpass.getpass("Mot de passe: ") + h = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt(rounds=12)).decode("ascii") + print(h) + +if __name__ == "__main__": + main()