Code/app/fiches/interface.py
Stéphan Peccini cef9c9d67b
feat(security): Analyse et corrections de sécurité complète
Corrections de sécurité (Bandit):
- Fix vulnérabilité HIGH Jinja2 XSS avec documentation de sécurité
- Ajout timeouts sur toutes les requêtes HTTP (17 occurrences)
  * utils/gitea.py: 3 timeouts (10s)
  * app/fiches/interface.py: 1 timeout (10s)
  * scripts/auto_ingest.py: 2 timeouts (10-30s)
  * scripts/generer_analyse.py: 10 timeouts (10-120s)
  * Corpus/generer_analyse.py: 2 timeouts (30-120s)

Documentation et configuration:
- docs/SECURITY.md: Guide complet de sécurité avec analyse Bandit
- docs/GUIDE_GITDOC.md: Guide d'utilisation GitDoc pour autocommit
- Configuration GitDoc complète dans settings.json.example
- Configuration .gitignore pour .vscode/settings.json
- Recommandations extensions VSCode (SonarLint, Snyk, GitDoc)
- Mise à jour README avec statut sécurité

Résultats Bandit:
- 0 vulnérabilités HIGH
- 0 vulnérabilités MEDIUM
- Tests: 67 passent, couverture 16%

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-08 08:57:22 +01:00

133 lines
5.7 KiB
Python

# === Constantes et imports ===
import os
import requests
import streamlit as st
from app.fiches.generer import generer_fiche
from app.fiches.utils import (
afficher_tickets_par_fiche,
doit_regenerer_fiche,
formulaire_creation_ticket_dynamique,
load_seuils,
rechercher_tickets_gitea,
)
from config import DEPOT_FICHES, ENV, FICHES_CRITICITE, GITEA_TOKEN, GITEA_URL, ORGANISATION
from utils.gitea import charger_arborescence_fiches
from utils.translations import _
from utils.widgets import html_expander
def interface_fiches() -> None:
"""Point d'entree principal de l'interface de visualisation des fiches et gestion des tickets."""
st.markdown(f"# {str(_('pages.fiches.title'))}")
html_expander(f"{str(_('pages.fiches.help'))}", content="\n".join(_("pages.fiches.help_content")), open_by_default=False, details_class="details_introduction")
st.markdown("---")
if "fiches_arbo" not in st.session_state:
st.session_state["fiches_arbo"] = charger_arborescence_fiches()
arbo = st.session_state.get("fiches_arbo", {})
if not arbo:
st.warning(str(_("pages.fiches.no_files")))
return
dossiers = sorted(arbo.keys(), key=lambda x: x.lower())
if "dossier_choisi" not in st.session_state or st.session_state["dossier_choisi"] not in dossiers:
st.session_state["dossier_choisi"] = str(_("pages.fiches.select_folder"))
try:
index_dossier = [str(_("pages.fiches.select_folder"))] + dossiers
idx = index_dossier.index(st.session_state["dossier_choisi"])
except ValueError:
idx = 0
st.session_state["dossier_choisi"] = st.selectbox(
str(_("pages.fiches.choose_category")),
[str(_("pages.fiches.select_folder"))] + dossiers,
index=idx
)
dossier_choisi = st.session_state["dossier_choisi"]
if dossier_choisi and dossier_choisi != str(_("pages.fiches.select_folder")):
fiches = arbo.get(dossier_choisi, [])
noms_fiches = [f['nom'] for f in fiches]
if "fiche_choisie" not in st.session_state or st.session_state["fiche_choisie"] not in noms_fiches:
st.session_state["fiche_choisie"] = str(_("pages.fiches.select_file"))
try:
index_fiche = [str(_("pages.fiches.select_file"))] + noms_fiches
idx_fiche = index_fiche.index(st.session_state["fiche_choisie"])
except ValueError:
idx_fiche = 0
st.session_state["fiche_choisie"] = st.selectbox(
str(_("pages.fiches.choose_file")),
[str(_("pages.fiches.select_file"))] + noms_fiches,
index=idx_fiche
)
fiche_choisie = st.session_state["fiche_choisie"]
if fiche_choisie and fiche_choisie != str(_("pages.fiches.select_file")):
fiche_info = next((f for f in fiches if f["nom"] == fiche_choisie), None)
if fiche_info:
try:
headers = {"Authorization": f"token {GITEA_TOKEN}"}
reponse_fiche = requests.get(fiche_info["download_url"], headers=headers, timeout=10)
reponse_fiche.raise_for_status()
md_source = reponse_fiche.text
if "seuils" not in st.session_state:
SEUILS = load_seuils("assets/config.yaml")
st.session_state["seuils"] = SEUILS
else:
SEUILS = st.session_state["seuils"]
nom_fiche = os.path.splitext(fiche_choisie)[0]
html_path = os.path.join("HTML", dossier_choisi, nom_fiche + ".html")
path_relative = f"Documents/{dossier_choisi}/{fiche_choisie}"
commits_url = f"{GITEA_URL}/repos/{ORGANISATION}/{DEPOT_FICHES}/commits?path={path_relative}&sha={ENV}"
regenerate = doit_regenerer_fiche(
html_path=html_path,
fiche_type=fiche_info.get("type", ""),
fiche_choisie=fiche_choisie,
commit_url=commits_url,
fichiers_criticite=FICHES_CRITICITE
)
if regenerate:
html_path = generer_fiche(md_source, dossier_choisi, fiche_choisie, SEUILS)
with open(html_path, encoding="utf-8") as f:
st.markdown(f.read(), unsafe_allow_html=True)
from utils.persistance import get_champ_statut
if not get_champ_statut("login") == "":
pdf_name = nom_fiche + ".pdf"
pdf_path = os.path.join("static", "Fiches", dossier_choisi, pdf_name)
if os.path.exists(pdf_path):
with open(pdf_path, "rb") as pdf_file:
st.download_button(
label=str(_("pages.fiches.download_pdf")),
data=pdf_file,
file_name=pdf_name,
mime="application/pdf",
help=str(_("pages.fiches.download_pdf")),
key="telecharger_fiche_pdf"
)
else:
st.warning(str(_("pages.fiches.pdf_unavailable")))
st.markdown(f"## {str(_('pages.fiches.ticket_management'))}")
afficher_tickets_par_fiche(rechercher_tickets_gitea(fiche_choisie))
formulaire_creation_ticket_dynamique(fiche_choisie)
except Exception as e:
st.error(f"{str(_('pages.fiches.loading_error'))} {e}")