Aller au contenu

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 :

Cron job (toutes les heures) → Connexion AD → Récupération users/groupes → Sync DB


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 :

php -m | grep ldap

Dans PHP :

if (!extension_loaded('ldap')) {
    die('Extension LDAP non disponible');
}


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 :

# Autoriser LDAP sortant
sudo ufw allow out 389/tcp
sudo ufw allow out 636/tcp


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 :

# Logs PHP
tail -f /var/log/php8.1-fpm.log

# Logs applicatifs
tail -f /srv/urbafix/logs/ldap.log


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 :

(&(objectClass=user)(objectCategory=person)(sAMAccountName=jdupont))

Rechercher un utilisateur par email :

(&(objectClass=user)(mail=jean.dupont@ville.test))

Récupérer tous les utilisateurs actifs :

(&(objectClass=user)(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))

Récupérer les groupes d'un utilisateur :

(&(objectClass=group)(member=CN=Jean Dupont,OU=Users,DC=domain,DC=local))


✅ 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