Intégration Active Directory / LDAP¶
Date : 10 novembre 2025 Statut : À implémenter Priorité : Moyenne
📋 Vue d'ensemble¶
Ce document décrit la proposition d'intégration d'Active Directory (AD) ou LDAP pour la gestion automatisée de l'authentification et des droits des utilisateurs dans l'application Urbafix.
🎯 Objectifs¶
- Single Sign-On (SSO) : Les utilisateurs se connectent avec leurs identifiants AD
- Gestion centralisée : Pas de duplication des comptes et mots de passe
- Synchronisation automatique : Mise à jour des droits basée sur les groupes AD
- Mapping intelligent : Groupes AD → Services et Rôles applicatifs
- Haute disponibilité : Fallback sur authentification locale si AD indisponible
🔐 Options d'intégration¶
Option 1 : LDAP Authentication (Recommandé ⭐)¶
Description : Authentification directe contre l'Active Directory via LDAP.
Avantages : - ✅ Single Sign-On possible - ✅ Pas de gestion des mots de passe côté application - ✅ Synchronisation automatique à chaque connexion - ✅ Mapping Groupes AD → Rôles - ✅ Désactivation centralisée (utilisateur désactivé dans AD = pas d'accès)
Inconvénients : - ⚠️ Dépendance à la disponibilité de l'AD - ⚠️ Configuration réseau requise (ports, DNS) - ⚠️ Extension PHP LDAP nécessaire
Flux d'authentification :
1. Utilisateur entre email + password
2. Application vérifie si auth_type = 'ldap'
3. Connexion LDAP au serveur AD
4. Vérification des credentials
5. Récupération des groupes AD de l'utilisateur
6. Mapping Groupes → Services/Rôles
7. Mise à jour dans DB locale
8. Création de session
Option 2 : Synchronisation périodique¶
Description : Script cron qui synchronise les utilisateurs AD vers la base de données locale.
Avantages : - ✅ Fonctionne même si AD temporairement indisponible - ✅ Plus simple à mettre en place - ✅ Cache local des utilisateurs - ✅ Pas de latence lors de l'authentification
Inconvénients : - ⚠️ Pas de synchronisation en temps réel - ⚠️ Gestion des mots de passe toujours locale - ⚠️ Délai entre changement AD et application
Flux :
Option 3 : Authentification mixte (Hybride ⭐⭐)¶
Description : Combinaison des deux approches avec fallback.
Avantages : - ✅ Flexibilité maximale - ✅ Compte local de secours pour admin - ✅ Compatible avec utilisateurs externes (non-AD) - ✅ Haute disponibilité
Inconvénients : - ⚠️ Plus complexe à implémenter - ⚠️ Gestion de deux systèmes d'authentification
Flux :
1. Utilisateur entre credentials
2. Vérification type d'authentification (local ou ldap)
3. Si LDAP : tentative AD
4. Si échec AD et admin : fallback local
5. Si local : vérification DB classique
🏗️ Architecture proposée (Hybride)¶
Modifications de la base de données¶
Table users - Nouvelles colonnes¶
ALTER TABLE users
ADD COLUMN auth_type ENUM('local', 'ldap') DEFAULT 'local' AFTER email,
ADD COLUMN ldap_dn VARCHAR(500) NULL AFTER auth_type COMMENT 'Distinguished Name AD',
ADD COLUMN ldap_guid VARCHAR(100) NULL AFTER ldap_dn COMMENT 'GUID AD (identifiant unique)',
ADD COLUMN ldap_groups JSON NULL AFTER ldap_guid COMMENT 'Liste des groupes AD',
ADD COLUMN last_ldap_sync TIMESTAMP NULL AFTER ldap_groups COMMENT 'Dernière sync AD',
ADD INDEX idx_auth_type (auth_type),
ADD INDEX idx_ldap_guid (ldap_guid);
Table ldap_config - Configuration AD par mairie¶
CREATE TABLE ldap_config (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
mairie_id INT(11) NOT NULL,
-- Connexion serveur
server VARCHAR(255) NOT NULL COMMENT 'ldap://dc.domain.local ou ldaps://...',
port INT(5) DEFAULT 389 COMMENT '389 (LDAP) ou 636 (LDAPS)',
use_ssl BOOLEAN DEFAULT FALSE COMMENT 'Utiliser LDAPS',
use_tls BOOLEAN DEFAULT TRUE COMMENT 'Utiliser STARTTLS',
-- Authentification
base_dn VARCHAR(500) NOT NULL COMMENT 'DC=domain,DC=local',
bind_dn VARCHAR(500) NULL COMMENT 'CN=service_account,OU=Users,DC=domain,DC=local',
bind_password VARCHAR(500) NULL COMMENT 'Mot de passe chiffré',
-- Filtres de recherche
user_filter VARCHAR(500) DEFAULT '(&(objectClass=user)(sAMAccountName={username}))'
COMMENT 'Filtre de recherche utilisateur',
user_base_dn VARCHAR(500) NULL COMMENT 'OU=Users,DC=domain,DC=local (optionnel)',
group_filter VARCHAR(500) DEFAULT '(objectClass=group)' COMMENT 'Filtre groupes',
-- Attributs LDAP
username_attribute VARCHAR(50) DEFAULT 'sAMAccountName',
email_attribute VARCHAR(50) DEFAULT 'mail',
firstname_attribute VARCHAR(50) DEFAULT 'givenName',
lastname_attribute VARCHAR(50) DEFAULT 'sn',
-- Options
auto_create_users BOOLEAN DEFAULT TRUE COMMENT 'Créer auto user à la 1ère connexion',
auto_sync BOOLEAN DEFAULT TRUE COMMENT 'Sync auto à chaque connexion',
sync_interval INT(11) DEFAULT 3600 COMMENT 'Intervalle sync (secondes)',
fallback_local BOOLEAN DEFAULT TRUE COMMENT 'Autoriser fallback auth locale',
-- Statut
actif BOOLEAN DEFAULT TRUE,
last_test TIMESTAMP NULL COMMENT 'Dernier test de connexion',
last_test_success BOOLEAN NULL,
last_error TEXT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (mairie_id) REFERENCES mairies(id) ON DELETE CASCADE,
UNIQUE KEY (mairie_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Table ldap_group_mapping - Mapping Groupes AD¶
CREATE TABLE ldap_group_mapping (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
mairie_id INT(11) NOT NULL,
-- Groupe AD
ldap_group_dn VARCHAR(500) NOT NULL COMMENT 'CN=IT-Staff,OU=Groups,DC=domain,DC=local',
ldap_group_name VARCHAR(255) NOT NULL COMMENT 'IT-Staff',
-- Mapping application
service_id INT(11) NULL COMMENT 'Service assigné automatiquement',
role ENUM('admin', 'gestionnaire', 'lecteur') NULL COMMENT 'Rôle assigné',
voir_tous_incidents BOOLEAN DEFAULT FALSE,
-- Options
priority INT(11) DEFAULT 0 COMMENT 'Priorité si plusieurs groupes (plus élevé = prioritaire)',
actif BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (mairie_id) REFERENCES mairies(id) ON DELETE CASCADE,
FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE SET NULL,
UNIQUE KEY unique_mapping (mairie_id, ldap_group_dn)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Table ldap_sync_log - Journal de synchronisation¶
CREATE TABLE ldap_sync_log (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
mairie_id INT(11) NOT NULL,
user_id INT(11) NULL,
sync_type ENUM('login', 'manual', 'cron') NOT NULL,
success BOOLEAN NOT NULL,
message TEXT NULL,
users_synced INT(11) DEFAULT 0,
duration_ms INT(11) NULL COMMENT 'Durée en millisecondes',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (mairie_id) REFERENCES mairies(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL,
INDEX idx_created_at (created_at),
INDEX idx_mairie_sync_type (mairie_id, sync_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
📦 Structure des fichiers PHP¶
/public/src/LdapAuth.php - Classe d'authentification LDAP¶
<?php
class LdapAuth {
private $config;
private $connection;
// Connexion au serveur LDAP
public function connect($config);
// Authentification utilisateur
public function authenticate($username, $password);
// Récupération des informations utilisateur
public function getUserInfo($username);
// Récupération des groupes d'un utilisateur
public function getUserGroups($dn);
// Test de connexion
public function testConnection();
// Recherche d'utilisateurs
public function searchUsers($filter = null);
// Mapping groupes → rôles/services
public function mapGroupsToRoles($groups, $mairieId);
// Synchronisation d'un utilisateur
public function syncUser($username, $mairieId);
}
/public/src/Auth.php - Modification pour support hybride¶
<?php
class Auth {
// Méthode modifiée
public function login($email, $password) {
// 1. Récupérer l'utilisateur
$user = $this->getUserByEmail($email);
// 2. Déterminer le type d'authentification
if ($user && $user['auth_type'] === 'ldap') {
return $this->loginLdap($email, $password, $user);
}
// 3. Authentification locale
return $this->loginLocal($email, $password);
}
private function loginLdap($email, $password, $user) {
$ldapConfig = $this->getLdapConfig($user['mairie_id']);
if (!$ldapConfig || !$ldapConfig['actif']) {
// Fallback si configuré
if ($ldapConfig['fallback_local'] && $user['role'] === 'admin') {
return $this->loginLocal($email, $password);
}
return false;
}
try {
$ldap = new LdapAuth();
$ldap->connect($ldapConfig);
// Authentification AD
if ($ldap->authenticate($email, $password)) {
// Synchronisation des infos
if ($ldapConfig['auto_sync']) {
$ldap->syncUser($email, $user['mairie_id']);
}
return $this->createSession($user);
}
} catch (Exception $e) {
$this->logError('LDAP auth failed: ' . $e->getMessage());
// Fallback pour admin
if ($ldapConfig['fallback_local'] && $user['role'] === 'admin') {
return $this->loginLocal($email, $password);
}
}
return false;
}
}
/public/backend/ldap_config.php - Interface de configuration¶
Page d'administration pour : - Configurer la connexion AD/LDAP - Tester la connexion - Importer des utilisateurs - Gérer les mappings groupes → services/rôles
/public/backend/ldap_sync.php - Synchronisation manuelle¶
Interface pour : - Synchroniser manuellement tous les utilisateurs - Voir l'historique des syncs - Logs de connexion LDAP
🚀 Plan d'implémentation¶
Phase 1 : Configuration (2-3h)¶
Tâches :
1. Créer les tables de base de données
2. Créer /public/backend/ldap_config.php
3. Formulaire de configuration AD :
- Serveur, port, SSL/TLS
- Base DN, Bind DN, credentials
- Filtres de recherche
4. Bouton "Tester la connexion"
5. Chiffrement du mot de passe bind
Livrables : - Tables créées - Interface de configuration fonctionnelle - Test de connexion opérationnel
Phase 2 : Authentification (2-3h)¶
Tâches :
1. Créer /public/src/LdapAuth.php
2. Implémenter les méthodes :
- connect() : Connexion LDAP
- authenticate() : Vérification credentials
- getUserInfo() : Récupération infos user
- getUserGroups() : Récupération groupes
3. Modifier /public/src/Auth.php :
- Détection type auth (local/ldap)
- Méthode loginLdap()
- Fallback sur auth locale
4. Tests d'authentification
Livrables : - Classe LdapAuth fonctionnelle - Auth.php modifié avec support hybride - Login LDAP opérationnel
Phase 3 : Mapping & Synchronisation (2-3h)¶
Tâches :
1. Interface de mapping groupes AD :
- Liste des groupes AD disponibles
- Assigner Service + Rôle par groupe
- Priorité si plusieurs groupes
2. Méthode mapGroupsToRoles() :
- Analyse des groupes AD de l'utilisateur
- Application des mappings configurés
- Mise à jour service_id, role, etc.
3. Synchronisation automatique :
- À chaque connexion (optionnel)
- Mise à jour des infos user
- Logs de sync
4. Import en masse (optionnel) :
- Recherche tous les utilisateurs AD
- Création/mise à jour dans DB
Livrables : - Interface de mapping opérationnelle - Synchronisation à la connexion - Import en masse (optionnel)
Phase 4 : Monitoring & Logs (1-2h)¶
Tâches :
1. Page /public/backend/ldap_sync.php :
- Historique des synchronisations
- Statistiques (taux de succès, durée)
- Logs d'erreurs LDAP
2. Dashboard admin :
- Nombre d'utilisateurs LDAP vs local
- Dernière sync
- Statut connexion AD
3. Alertes :
- Email si AD indisponible > X temps
- Notification échecs répétés
Livrables : - Interface de monitoring - Logs détaillés - Alertes configurables
⚙️ Prérequis techniques¶
Extension PHP LDAP¶
Installation sur Debian/Ubuntu :
sudo apt-get update
sudo apt-get install php-ldap
sudo systemctl restart apache2
# ou pour PHP-FPM :
sudo systemctl restart php8.1-fpm
Vérification :
Dans PHP :
Configuration réseau¶
Ports à ouvrir : - 389 : LDAP standard (non chiffré) - 636 : LDAPS (LDAP over SSL) - 3268 : Global Catalog (optionnel) - 3269 : Global Catalog SSL (optionnel)
DNS : - Résolution du nom du contrôleur de domaine - Ou utiliser l'IP directement
Firewall serveur :
Compte service Active Directory¶
Créer un compte dédié :
- Nom : svc_urbafix ou similaire
- Droits : Lecture seule sur l'annuaire
- Pas d'expiration du mot de passe
- Mot de passe complexe
Permissions minimales requises : - Lire les attributs des utilisateurs - Lire les groupes et leurs membres - Pas de droits d'écriture nécessaires
Recommandations de sécurité : - ✅ Utiliser LDAPS (port 636) en production - ✅ Certificat SSL valide sur le DC - ✅ Chiffrer le mot de passe bind dans la DB - ✅ Limiter les tentatives de connexion - ✅ Logger toutes les authentications LDAP
📊 Exemples de configuration¶
Exemple 1 : Active Directory standard¶
Serveur : ldaps://dc01.ville-menton.local
Port : 636 (LDAPS)
Base DN : DC=ville-menton,DC=local
Bind DN : CN=svc_urbafix,OU=ServiceAccounts,DC=ville-menton,DC=local
User Filter : (&(objectClass=user)(objectCategory=person)(sAMAccountName={username}))
User Base : OU=Employees,DC=ville-menton,DC=local
Exemple 2 : Mapping groupes AD¶
| Groupe AD | Service | Rôle | Voir tous incidents |
|---|---|---|---|
| CN=IT-Admins,OU=Groups,DC=ville-menton,DC=local | - | admin | Oui |
| CN=Service-Voirie,OU=Groups,DC=ville-menton,DC=local | Voirie | gestionnaire | Non |
| CN=Service-Proprete,OU=Groups,DC=ville-menton,DC=local | Propreté | gestionnaire | Non |
| CN=Direction,OU=Groups,DC=ville-menton,DC=local | - | lecteur | Oui |
Exemple 3 : Script de test de connexion¶
<?php
// Test rapide de connexion LDAP
$server = "ldaps://dc01.ville-menton.local";
$port = 636;
$bind_dn = "CN=svc_urbafix,OU=ServiceAccounts,DC=ville-menton,DC=local";
$bind_password = "SecurePassword123!";
$conn = ldap_connect($server, $port);
if (!$conn) {
die("Impossible de se connecter au serveur LDAP\n");
}
ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($conn, LDAP_OPT_REFERRALS, 0);
if (@ldap_bind($conn, $bind_dn, $bind_password)) {
echo "✅ Connexion LDAP réussie\n";
ldap_unbind($conn);
} else {
echo "❌ Échec de connexion: " . ldap_error($conn) . "\n";
}
🔒 Sécurité¶
Chiffrement du mot de passe bind¶
Ne JAMAIS stocker en clair :
<?php
// Chiffrement
function encryptPassword($password) {
$key = getenv('APP_SECRET_KEY'); // Depuis .env
$iv = openssl_random_pseudo_bytes(16);
$encrypted = openssl_encrypt($password, 'AES-256-CBC', $key, 0, $iv);
return base64_encode($iv . $encrypted);
}
// Déchiffrement
function decryptPassword($encrypted) {
$key = getenv('APP_SECRET_KEY');
$data = base64_decode($encrypted);
$iv = substr($data, 0, 16);
$encrypted = substr($data, 16);
return openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv);
}
Protection contre les attaques¶
Limitations : - Max 5 tentatives de connexion par utilisateur / 15 min - Délai progressif entre tentatives (backoff) - Blocage temporaire si trop d'échecs
Logging : - Toutes les tentatives d'authentification - Échecs avec raison (mot de passe, utilisateur inconnu, AD indispo) - Changements de permissions via AD
📈 Métriques et monitoring¶
KPIs à suivre¶
- Taux de disponibilité AD : % de connexions réussies
- Temps de réponse LDAP : Latence moyenne
- Nombre d'utilisateurs LDAP : vs utilisateurs locaux
- Fréquence de sync : Dernière synchronisation
- Échecs d'authentification : Tentatives échouées
Dashboard administrateur¶
┌─────────────────────────────────────────┐
│ État Active Directory │
├─────────────────────────────────────────┤
│ Statut : ✅ Connecté │
│ Serveur : dc01.domain.local │
│ Dernière sync : Il y a 5 minutes │
│ Utilisateurs AD : 45 / 52 (86%) │
│ Uptime 30j : 99.8% │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Authentifications (24h) │
├─────────────────────────────────────────┤
│ Succès AD : 234 (94%) │
│ Échecs AD : 12 (5%) │
│ Fallback local : 3 (1%) │
└─────────────────────────────────────────┘
🐛 Troubleshooting¶
Problème : Connexion LDAP échoue¶
Vérifications :
1. Extension PHP LDAP installée : php -m | grep ldap
2. Serveur accessible : telnet dc.domain.local 389
3. DNS résout le serveur : nslookup dc.domain.local
4. Certificat SSL valide (si LDAPS)
5. Credentials bind corrects
6. Firewall autorise le trafic sortant
Logs à consulter :
Problème : Utilisateur AD ne peut pas se connecter¶
Checklist :
1. Utilisateur existe dans l'AD : recherche LDAP
2. Compte activé dans AD
3. Email correspond entre AD et application
4. Groupes AD assignés correctement
5. Mapping groupes configuré
6. auth_type = 'ldap' dans la table users
Problème : Fallback local ne fonctionne pas¶
Vérifications :
1. fallback_local = true dans ldap_config
2. Utilisateur a role = 'admin'
3. Mot de passe local défini dans DB
4. Pas de verrouillage du compte
📚 Ressources¶
Documentation officielle¶
Outils utiles¶
- Apache Directory Studio : Client LDAP graphique
- ldapsearch : Outil CLI pour tester les requêtes
- Wireshark : Analyser le trafic LDAP (debug)
Filtres LDAP courants¶
Rechercher un utilisateur par sAMAccountName :
Rechercher un utilisateur par email :
Récupérer tous les utilisateurs actifs :
Récupérer les groupes d'un utilisateur :
✅ Checklist de déploiement¶
Avant déploiement¶
- Extension PHP LDAP installée et testée
- Compte service AD créé avec droits lecture
- Connexion réseau AD testée (ping, telnet)
- Certificat SSL configuré (si LDAPS)
- Variables d'environnement configurées (.env)
- Sauvegarde de la base de données effectuée
Déploiement¶
- Exécuter les scripts SQL de création de tables
- Déployer les nouveaux fichiers PHP
- Configurer la connexion LDAP via l'interface admin
- Tester la connexion LDAP
- Créer les mappings de groupes
- Tester l'authentification avec un compte test
- Vérifier la synchronisation des données
Post-déploiement¶
- Importer les utilisateurs AD (ou attendre leurs connexions)
- Former les administrateurs à l'interface
- Documenter la configuration spécifique
- Configurer les alertes de monitoring
- Planifier une revue après 1 semaine
💡 Notes et considérations¶
Migration progressive¶
Stratégie recommandée :
1. Phase 1 : Déployer avec auth_type = 'local' pour tous
2. Phase 2 : Activer LDAP pour un groupe test (ex: IT)
3. Phase 3 : Migrer progressivement les services
4. Phase 4 : Tous les utilisateurs en LDAP, local en secours
Gestion des utilisateurs externes¶
Scénario : Prestataires, consultants sans compte AD
Solution : - Créer un compte local (auth_type = 'local') - Définir une date d'expiration - Limiter les droits (lecteur uniquement) - Audit régulier des comptes locaux
Performance¶
Cache :
- Groupes AD en cache (JSON dans ldap_groups)
- Durée de vie : configurable (défaut 1h)
- Refresh à la connexion si expiré
Optimisations :
- Connexion LDAP persistante (pool)
- Pagination pour recherche de nombreux utilisateurs
- Index sur ldap_guid pour rapidité
🎯 Recommandation finale¶
Solution recommandée : Authentification LDAP hybride avec fallback local
Pourquoi : 1. ✅ Sécurité : Gestion centralisée, pas de mots de passe dupliqués 2. ✅ Simplicité : Utilisateurs se connectent avec leurs identifiants habituels 3. ✅ Fiabilité : Fallback si AD indisponible 4. ✅ Flexibilité : Support des utilisateurs externes (comptes locaux) 5. ✅ Auditabilité : Logs centralisés des authentifications
Effort estimé : 8-10 heures de développement + 2-3 heures de tests
📞 Contact et support¶
Pour toute question sur cette implémentation : - Relire ce document - Consulter la documentation PHP LDAP - Tester avec Apache Directory Studio - Contacter l'administrateur AD de la mairie
Document créé le : 10 novembre 2025 Dernière mise à jour : 10 novembre 2025 Version : 1.0