feat(v2.0.2): Corrections de sécurité critiques et fonctionnalité de réactivation des devis

- Correction de 14 vulnérabilités SQL (8 critiques, 6 moyennes)
- Suppression de la fonction autocomplete non utilisée
- Migration complète vers PDO avec requêtes préparées
- Ajout du bouton 'Réactiver' pour les devis archivés (statut 20 → 1)
- Conversion des appels $.ajax en fetch API (vanilla JS)
- Correction des erreurs JavaScript empêchant l'attachement d'événements
- Mise à jour de la documentation (README.md et TODO.md)

Sécurité: Utilisation systématique de intval() et requêtes préparées PDO
UI: Nouveau bouton vert dans la grille 2x2 des actions sur devis archivés
Historique: Traçabilité dans devis_histo lors de la réactivation
This commit is contained in:
2025-09-12 20:25:48 +02:00
parent eabb4bf67a
commit 443b0509df
16 changed files with 4355 additions and 3318 deletions

View File

@@ -182,6 +182,85 @@ class Database {
// Debug désactivé pour les connexions
return;
}
/**
* Récupère un enregistrement par ID de manière sécurisée
*/
public function getById($table, $id, $columns = '*') {
// Liste blanche des tables autorisées
$allowedTables = [
'clients', 'devis', 'devis_lignes', 'produits', 'marches',
'users', 'contacts', 'marches_listes', 'marches_produits',
'devis_histo', 'medias', 'y_pages', 'z_logs', 'z_sessions'
];
if (!in_array($table, $allowedTables)) {
throw new Exception("Table non autorisée : $table");
}
$sql = "SELECT $columns FROM $table WHERE rowid = :id";
$stmt = $this->pdo->prepare($sql);
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* Supprime un enregistrement par ID de manière sécurisée
*/
public function deleteById($table, $id) {
// Liste blanche des tables autorisées pour suppression
$allowedTables = [
'clients', 'devis', 'devis_lignes', 'contacts',
'devis_histo', 'medias', 'z_logs', 'z_sessions',
'users', 'infos', 'marches', 'marches_listes', 'produits'
];
if (!in_array($table, $allowedTables)) {
throw new Exception("Suppression non autorisée sur la table : $table");
}
$sql = "DELETE FROM $table WHERE rowid = :id";
$stmt = $this->pdo->prepare($sql);
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
return $stmt->execute();
}
/**
* Recherche sécurisée dans un champ
*/
public function searchByField($table, $field, $value, $orderBy = null) {
// Liste blanche des tables et colonnes autorisées
$allowedSearches = [
'clients' => ['libelle', 'raison_sociale', 'ville', 'cp', 'email', 'contact_nom', 'contact_prenom'],
'produits' => ['reference', 'designation', 'famille'],
'devis' => ['num_devis', 'num_facture', 'opportunite'],
'users' => ['username', 'firstname', 'lastname', 'email'],
'marches' => ['libelle', 'description']
];
if (!isset($allowedSearches[$table]) || !in_array($field, $allowedSearches[$table])) {
throw new Exception("Recherche non autorisée : $table.$field");
}
$sql = "SELECT * FROM $table WHERE $field LIKE :value";
if ($orderBy && in_array($orderBy, $allowedSearches[$table])) {
$sql .= " ORDER BY $orderBy";
}
$stmt = $this->pdo->prepare($sql);
$searchValue = '%' . $value . '%';
$stmt->bindParam(':value', $searchValue, PDO::PARAM_STR);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Fonction autocompleteSearch supprimée car non utilisée
// L'autocomplétion est gérée côté client dans l'application
}
function getinfos($sql, $dbn = "gen", $format = "normal") {