diff --git a/Pseudonymisation_Gui_V5.py b/Pseudonymisation_Gui_V5.py index c6806b2..e6ea4ce 100644 --- a/Pseudonymisation_Gui_V5.py +++ b/Pseudonymisation_Gui_V5.py @@ -77,6 +77,18 @@ except Exception: VlmManager = None # type: ignore VlmConfig = None # type: ignore +# D-11 : VLM Ollama caché par défaut. Activé seulement en mode admin +# (env ANON_ADMIN=1 ou fichier .admin à côté de l'EXE). Empêche le bêta +# d'envoyer du contenu à Ollama externe sans en avoir conscience. +try: + from admin_mode import is_admin as _is_admin_mode +except Exception: + def _is_admin_mode() -> bool: + return False +if not _is_admin_mode(): + VlmManager = None # type: ignore + VlmConfig = None # type: ignore + try: import yaml except Exception: diff --git a/admin_mode.py b/admin_mode.py new file mode 100644 index 0000000..dc11eee --- /dev/null +++ b/admin_mode.py @@ -0,0 +1,73 @@ +"""Mode admin pour l'application Pseudonymisation (D-13). + +Le mode admin déverrouille des fonctionnalités cachées au bêta-testeur : +- VLM Ollama (D-11) — détection visuelle par LLM local +- Paramètres avancés sensibles (stopwords personnalisés, force_terms, etc.) +- Profils techniques (regex_overrides) + +Activation possible (par ordre de priorité) : +1. Variable d'environnement : `ANON_ADMIN=1` +2. Fichier `.admin` à la racine de l'application (à côté de l'EXE / du module) + +Pour désactiver : supprimer le fichier `.admin` et la variable d'env. + +Aucun mot de passe pour la v1.0 — c'est juste un verrou "interdit aux +distraits" qui empêche le bêta-testeur ou un utilisateur final de tomber +sur des options qui pourraient leak des données (envoi à Ollama externe, +modifications config critique). +""" +from __future__ import annotations + +import os +from pathlib import Path +from typing import Optional + +_ADMIN_CACHED: Optional[bool] = None + + +def _project_root() -> Path: + """Retourne le dossier racine de l'application (compat dev + EXE).""" + try: + return Path(__file__).parent.resolve() + except NameError: + return Path.cwd() + + +def is_admin(force_refresh: bool = False) -> bool: + """Retourne True si le mode admin est actif. + + Résultat caché en module (les vérifications coûtent presque rien mais + `is_admin()` peut être appelé dans des boucles serrées). `force_refresh` + permet de re-vérifier après un changement de configuration. + """ + global _ADMIN_CACHED + if _ADMIN_CACHED is not None and not force_refresh: + return _ADMIN_CACHED + + # Priorité 1 : variable d'env + env_val = os.environ.get("ANON_ADMIN", "").strip().lower() + if env_val in ("1", "true", "yes", "on"): + _ADMIN_CACHED = True + return True + + # Priorité 2 : fichier .admin + admin_file = _project_root() / ".admin" + if admin_file.exists(): + _ADMIN_CACHED = True + return True + + _ADMIN_CACHED = False + return False + + +def admin_required(feature_name: str = "fonctionnalité") -> None: + """Lève RuntimeError si pas admin. + + À utiliser comme garde au début d'une méthode sensible. + """ + if not is_admin(): + raise RuntimeError( + f"Mode admin requis pour {feature_name}. " + f"Activez via ANON_ADMIN=1 ou créez le fichier .admin " + f"à la racine de l'application." + )