Générateur de mots de passe
Mots de passe aléatoires, passphrases et mots de passe temporaires
diff --git a/.gitignore b/.gitignore
index b59570c..dce6236 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,10 +9,6 @@ cmdb/snapshots/
# Certificats générés (contiennent des clés privées)
cert-generator/LISTS_ALL_CERT/
-# Images et fichiers importés dans les templates email (contenu utilisateur)
-password-generator/images/
-password-generator/files/
-
# === Environnement / build ===
.venv/
__pycache__/
diff --git a/README.md b/README.md
index 54f279d..4298614 100644
--- a/README.md
+++ b/README.md
@@ -5,9 +5,8 @@ Portail d'outils pour administrateurs systèmes et réseau, servi par un serveur
- **cmdb** — inventaire (machines, réseaux, IP/MAC, salles & baies), recherche Active Directory, import/export CSV, snapshots de la base
- **cert-generator** — génération de certificats SSL (clés RSA, CSR)
- **switch-config** — mapper de VLAN et export de configuration switch
-- **password-generator** — génération de mots de passe aléatoires, passphrases et mots de passe temporaires (préfixe configurable, envoi par mail via l'AD + templates)
-Le tout accessible depuis un seul serveur, avec navigation entre les apps.
+Le tout accessible depuis un seul serveur, avec navigation entre les 3 apps.
## Prérequis
diff --git a/cert-generator/dashboard.html b/cert-generator/dashboard.html
index f527804..e64b99d 100644
--- a/cert-generator/dashboard.html
+++ b/cert-generator/dashboard.html
@@ -184,7 +184,6 @@
Mots de passe aléatoires, passphrases et mots de passe temporaires
par des data
- URI (lecture du fichier sur disque). Les images data:/http(s): sont laissées
- telles quelles. Rend le mail HTML autonome au moment de l'envoi."""
-
- def repl(m):
- fname = os.path.basename(m.group(2)) # anti-traversal
- path = os.path.join(PWGEN_IMG, fname)
- if not os.path.isfile(path):
- return m.group(0)
- mime = mimetypes.guess_type(path)[0] or "application/octet-stream"
- with open(path, "rb") as f:
- data = base64.b64encode(f.read()).decode("ascii")
- return 'src="data:%s;base64,%s"' % (mime, data)
-
- return _LOCAL_IMG_SRC.sub(repl, html)
-
-
-def attach_files(msg, urls):
- """Joint au mail les fichiers du dossier password-generator/files/ dont l'URL
- figure dans la liste `urls` (pièces jointes du template). Le nom présenté au
- destinataire est le nom d'origine (suffixe aléatoire de stockage retiré)."""
- seen = set()
- for url in urls or []:
- fname = os.path.basename(str(url)) # anti-traversal
- if not fname or fname in seen:
- continue
- seen.add(fname)
- path = os.path.join(PWGEN_FILES, fname)
- if not os.path.isfile(path):
- continue
- ctype = mimetypes.guess_type(path)[0] or "application/octet-stream"
- maintype, _, subtype = ctype.partition("/")
- disp = re.sub(r"-[0-9a-f]{8}(\.[^.]+)$", r"\1", fname) # nom d'origine lisible
- with open(path, "rb") as f:
- msg.add_attachment(
- f.read(), maintype=maintype, subtype=subtype or "octet-stream", filename=disp
- )
-
-
class Handler(SimpleHTTPRequestHandler):
def log_message(self, *a):
pass
@@ -286,10 +106,6 @@ class Handler(SimpleHTTPRequestHandler):
return self._send_db(200, C.db_bytes(), C.db_version())
if self.path == "/db/version":
return self._json({"version": C.db_version()})
- # VLANs = réseaux CMDB (table networks) → consommés par le mapper switch
- if self.path == "/api/vlans":
- with C.DB_LOCK:
- return self._json(read_vlans())
if self.path == "/db/snapshots":
return self._json({"snapshots": C.list_snapshots()})
if self.path.startswith("/db/snapshot?"):
@@ -361,23 +177,6 @@ class Handler(SimpleHTTPRequestHandler):
)
except Exception as ex:
return self._json({"error": str(ex)}, 500)
- # envoi de mail (générateur de mots de passe)
- if self.path == "/mail/send":
- try:
- return self.mail_send()
- except Exception as e:
- return self._err(e, 500)
- # upload d'image / de fichier joint (générateur de mots de passe)
- if self.path == "/pwgen/upload-image":
- try:
- return self.pwgen_upload_image()
- except Exception as e:
- return self._err(e, 500)
- if self.path == "/pwgen/upload-file":
- try:
- return self.pwgen_upload_file()
- except Exception as e:
- return self._err(e, 500)
# API certs (écriture)
api = self._cert_api()
try:
@@ -393,113 +192,49 @@ class Handler(SimpleHTTPRequestHandler):
return self._err(e, 500)
self._err("route inconnue: %s" % self.path, 404)
- # ---------- mail (générateur de mots de passe) ----------
- def mail_send(self):
- """Envoie un mail texte via le serveur SMTP fourni par la page.
-
- Body JSON : { smtp: {host, port, security, user, pass, from, from_name},
- to, subject, body }
- security : none | starttls | ssl
- """
- d = self._body()
- smtp = d.get("smtp") or {}
- to = (d.get("to") or "").strip()
- host = (smtp.get("host") or "").strip()
- if not to or "@" not in to:
- raise ValueError("destinataire invalide: %r" % to)
- if not host:
- raise ValueError("serveur SMTP non configuré")
- security = (smtp.get("security") or "starttls").lower()
- try:
- port = int(smtp.get("port") or 0)
- except (TypeError, ValueError):
- port = 0
- if not port:
- port = {"ssl": 465, "starttls": 587}.get(security, 25)
- sender = (smtp.get("from") or smtp.get("user") or "").strip()
- if not sender:
- raise ValueError("adresse expéditeur non configurée")
-
- msg = EmailMessage()
- from_name = (smtp.get("from_name") or "").strip()
- msg["From"] = formataddr((from_name, sender)) if from_name else sender
- msg["To"] = to
- msg["Subject"] = d.get("subject") or ""
- msg.set_content(d.get("body") or "")
- html = (d.get("html") or "").strip()
- if html:
- # les images de templates stockées sur disque sont ré-intégrées en
- # data URI pour que le mail reçu soit autonome (pas de lien vers le
- # serveur, images visibles hors du réseau).
- html = inline_local_images(html)
- msg.add_alternative(html, subtype="html")
- # pièces jointes du template (ex : .rdp de connexion)
- attach_files(msg, d.get("attachments"))
-
- if security == "ssl":
- server = smtplib.SMTP_SSL(host, port, timeout=15)
- else:
- server = smtplib.SMTP(host, port, timeout=15)
- try:
- if security == "starttls":
- server.starttls()
- user = (smtp.get("user") or "").strip()
- if user:
- server.login(user, smtp.get("pass") or "")
- server.send_message(msg)
- finally:
- try:
- server.quit()
- except Exception:
- pass
- self._json({"ok": True, "to": to})
-
- # ---------- uploads de templates (disque réel) ----------
- def _save_upload(self, dest_dir, url_prefix, max_bytes, default_ext, kind):
- """Stocke un fichier posté ({name, b64}) sur le disque avec un nom sûr
- (radical assaini + suffixe aléatoire) et renvoie son URL servie."""
- d = self._body()
- raw = (d.get("name") or kind).strip()
- try:
- data = base64.b64decode(d.get("b64") or "", validate=True)
- except (ValueError, TypeError):
- raise ValueError("%s invalide (base64)" % kind)
- if not data:
- raise ValueError("%s vide" % kind)
- if len(data) > max_bytes:
- raise ValueError("%s trop lourd (max %d Mo)" % (kind, max_bytes // (1024 * 1024)))
- base, _, ext = raw.rpartition(".")
- ext = re.sub(r"[^A-Za-z0-9]", "", ext).lower()[:8] or default_ext
- stem = re.sub(r"[^A-Za-z0-9._-]", "_", base or kind)[:40].strip("._-") or kind
- fname = "%s-%s.%s" % (stem, os.urandom(4).hex(), ext)
- os.makedirs(dest_dir, exist_ok=True)
- with open(os.path.join(dest_dir, fname), "wb") as f:
- f.write(data)
- self._json({"ok": True, "url": url_prefix + fname, "name": fname})
-
- def pwgen_upload_image(self):
- """Image de template → password-generator/images/ (ré-intégrée en data URI à l'envoi)."""
- self._save_upload(PWGEN_IMG, PWGEN_IMG_URL, MAX_IMG_BYTES, "png", "image")
-
- def pwgen_upload_file(self):
- """Fichier joignable (rdp, pdf…) → password-generator/files/ (joint au mail à l'envoi)."""
- self._save_upload(PWGEN_FILES, PWGEN_FILES_URL, MAX_FILE_BYTES, "bin", "fichier")
-
# ---------- certificats (disque réel) ----------
def cert_list(self):
ensure_work()
- sig = _cert_signature()
- with _CERT_CACHE_LOCK:
- if (
- sig is not None
- and sig == _CERT_CACHE["sig"]
- and _CERT_CACHE["payload"] is not None
- ):
- return _CERT_CACHE["payload"] # inchangé → réponse mémorisée
- payload = _build_cert_list()
- _CERT_CACHE["sig"] = sig
- _CERT_CACHE["payload"] = payload
- return payload
+ certs, archives = [], []
+ for entry in sorted(os.listdir(WORK)):
+ full = os.path.join(WORK, entry)
+ if entry == "archives":
+ if os.path.isdir(full):
+ for a in sorted(os.listdir(full)):
+ af = os.path.join(full, a)
+ if os.path.isfile(af):
+ st = os.stat(af)
+ archives.append(
+ {
+ "name": a,
+ "size": st.st_size,
+ "mtime": int(st.st_mtime * 1000),
+ }
+ )
+ continue
+ if not os.path.isdir(full):
+ continue
+ files, date = [], 0
+ for fn in sorted(os.listdir(full)):
+ ff = os.path.join(full, fn)
+ if not os.path.isfile(ff):
+ continue
+ st = os.stat(ff)
+ m = int(st.st_mtime * 1000)
+ date = max(date, m)
+ files.append(
+ {
+ "name": fn,
+ "ext": (fn.rsplit(".", 1)[-1] if "." in fn else "").lower(),
+ "size": st.st_size,
+ "mtime": m,
+ }
+ )
+ if files:
+ certs.append({"dir": entry, "files": files, "date": date})
+ archives.sort(key=lambda a: -a["mtime"])
+ certs.sort(key=lambda c: -c["date"])
+ return {"ok": True, "certs": certs, "archives": archives}
def cert_write_text(self):
d = self._body()
@@ -514,7 +249,6 @@ class Handler(SimpleHTTPRequestHandler):
newline="",
) as f:
f.write(content)
- invalidate_cert_cache()
self._json({"ok": True, "path": "LISTS_ALL_CERT/%s/" % dname})
def cert_write_bin(self):
@@ -526,7 +260,6 @@ class Handler(SimpleHTTPRequestHandler):
os.makedirs(os.path.dirname(target), exist_ok=True)
with open(target, "wb") as f:
f.write(base64.b64decode(d["b64"]))
- invalidate_cert_cache()
self._json({"ok": True, "path": "LISTS_ALL_CERT/%s" % "/".join(parts)})
def cert_delete_dir(self):
@@ -537,12 +270,10 @@ class Handler(SimpleHTTPRequestHandler):
shutil.rmtree(target)
if d.get("archives"):
self._rm_archives(dname)
- invalidate_cert_cache()
self._json({"ok": True})
def cert_delete_archives(self):
self._rm_archives(safe_name(self._body()["dir"]))
- invalidate_cert_cache()
self._json({"ok": True})
def _rm_archives(self, dname):
diff --git a/switch-config/switch_vlan_mapper.html b/switch-config/switch_vlan_mapper.html
index 7083125..999d3e7 100644
--- a/switch-config/switch_vlan_mapper.html
+++ b/switch-config/switch_vlan_mapper.html
@@ -596,6 +596,7 @@ button:active { transform: scale(0.98); }
background: #101216; color: #d4d4d4;
}
+
@@ -607,7 +608,6 @@ button:active { transform: scale(0.98); }