"""
Scanner de Défaillances d'Identification et Authentification - Web Sentinel
===========================================================================

Détecte les défaillances d'authentification et d'identification.
Couvre OWASP A07 - Identification and Authentication Failures.
"""

from typing import Iterable, List, Dict, Optional, Set
import requests
import re
import json
import time
from urllib.parse import urljoin, urlparse, parse_qs

from web_sentinel.model import Finding, ScanRequest

# Patterns de mots de passe faibles
WEAK_PASSWORD_PATTERNS = [
    r'^.{1,7}$',  # Moins de 8 caractères
    r'^[a-z]+$',  # Que des minuscules
    r'^[A-Z]+$',  # Que des majuscules
    r'^[0-9]+$',  # Que des chiffres
    r'^(password|123456|admin|test|guest)$'  # Mots de passe courants
]

# Endpoints d'authentification courants
AUTH_ENDPOINTS = {
    'login': ['/login', '/signin', '/auth', '/authenticate', '/api/login', '/api/auth'],
    'register': ['/register', '/signup', '/create-account', '/api/register'],
    'password_reset': ['/forgot-password', '/reset-password', '/password-reset', '/api/password-reset'],
    'logout': ['/logout', '/signout', '/api/logout'],
    'profile': ['/profile', '/account', '/user', '/api/user', '/api/profile']
}

# Headers d'authentification à vérifier
AUTH_HEADERS = ['Authorization', 'X-Auth-Token', 'X-API-Key', 'Authentication']

def evaluate_identification_authentication_failures(request: ScanRequest) -> Iterable[Finding]:
    """
    Analyse les défaillances d'identification et d'authentification.
    
    Vérifie :
    - Mécanismes d'authentification faibles
    - Gestion des sessions non sécurisée  
    - Absence de multi-facteur authentication
    - Énumération d'utilisateurs
    - Mots de passe faibles autorisés
    - Tokens d'authentification prévisibles
    - Attaques par force brute non protégées
    """
    findings: List[Finding] = []
    
    findings.extend(_scan_authentication_mechanisms(request))
    findings.extend(_check_session_management(request))
    findings.extend(_test_user_enumeration(request))
    findings.extend(_analyze_password_policies(request))
    findings.extend(_check_brute_force_protection(request))
    
    if request.allow_invasive:
        findings.extend(_test_authentication_bypass(request))
        findings.extend(_probe_token_vulnerabilities(request))
        findings.extend(_test_mfa_implementation(request))
    
    return findings

def _scan_authentication_mechanisms(request: ScanRequest) -> List[Finding]:
    """Scanne les mécanismes d'authentification disponibles."""
    findings = []
    base_url = f"https://{request.domain}"
    
    # Analyser les pages de login
    for endpoint in AUTH_ENDPOINTS['login']:
        try:
            response = requests.get(f"{base_url}{endpoint}", timeout=request.timeout)
            
            if response.status_code == 200:
                findings.extend(_analyze_login_page(endpoint, response))
                
        except requests.exceptions.RequestException:
            continue
    
    return findings

def _analyze_login_page(endpoint: str, response: requests.Response) -> List[Finding]:
    """Analyse une page de login pour les vulnérabilités d'authentification."""
    findings = []
    content = response.text.lower()
    
    # Vérifier les mécanismes d'authentification faibles
    weak_auth_indicators = [
        (r'type=["\']password["\'][^>]*autocomplete=["\']on["\']', 
         "Autocomplétion activée pour les mots de passe"),
        (r'remember.*me.*forever|permanent.*login', 
         "Option 'Se souvenir de moi' permanente"),
        (r'guest.*login|anonymous.*access', 
         "Accès invité sans authentification"),
        (r'social.*login.*only|oauth.*only', 
         "Authentification uniquement via réseaux sociaux"),
        (r'sms.*only|phone.*only.*auth', 
         "Authentification uniquement par SMS (vulnérable)")
    ]
    
    for pattern, description in weak_auth_indicators:
        if re.search(pattern, content):
            findings.append(Finding(
                check="identification_authentication_failures",
                title=f"Mécanisme d'authentification faible : {endpoint}",
                severity="medium",
                description=f"{description} détecté sur la page de login.",
                remediation="Implémenter des mécanismes d'authentification robustes et sécurisés.",
                evidence=f"Pattern: {description}"
            ))
    
    # Vérifier l'absence de protection CSRF
    csrf_indicators = ['csrf', '_token', 'authenticity_token', 'anti-forgery']
    if not any(indicator in content for indicator in csrf_indicators):
        findings.append(Finding(
            check="identification_authentication_failures",
            title=f"Protection CSRF manquante : {endpoint}",
            severity="high",
            description="La page de login ne possède pas de protection CSRF visible.",
            remediation="Implémenter des tokens CSRF pour tous les formulaires d'authentification.",
            evidence=f"Aucun token CSRF détecté sur {endpoint}"
        ))
    
    # Vérifier l'absence de limitations sur les tentatives
    rate_limit_indicators = ['rate.*limit', 'captcha', 'throttle', 'attempt.*limit']
    if not any(re.search(indicator, content) for indicator in rate_limit_indicators):
        findings.append(Finding(
            check="identification_authentication_failures",
            title=f"Protection anti-brute force absente : {endpoint}",
            severity="high",
            description="Aucune protection contre les attaques par force brute n'est visible.",
            remediation="Implémenter un rate limiting, CAPTCHA ou verrouillage de compte.",
            evidence=f"Aucune protection détectée sur {endpoint}"
        ))
    
    return findings

def _check_session_management(request: ScanRequest) -> List[Finding]:
    """Vérifie la gestion sécurisée des sessions."""
    findings = []
    base_url = f"https://{request.domain}"
    
    try:
        response = requests.get(base_url, timeout=request.timeout)
        
        # Analyser les cookies de session
        for cookie in response.cookies:
            findings.extend(_analyze_session_cookie(cookie))
        
        # Vérifier la régénération d'ID de session
        findings.extend(_check_session_regeneration(request, base_url))
        
    except requests.exceptions.RequestException:
        pass
    
    return findings

def _analyze_session_cookie(cookie) -> List[Finding]:
    """Analyse un cookie de session pour les vulnérabilités."""
    findings = []
    
    # Identifier les cookies de session
    session_names = ['sessionid', 'session', 'sess', 'jsessionid', 'phpsessid', 'asp.net_sessionid']
    
    if any(name in cookie.name.lower() for name in session_names):
        
        # Vérifier les attributs de sécurité
        if not getattr(cookie, 'secure', False):
            findings.append(Finding(
                check="identification_authentication_failures",
                title=f"Cookie de session non sécurisé : {cookie.name}",
                severity="high",
                description="Le cookie de session n'a pas l'attribut Secure.",
                remediation="Configurer l'attribut Secure pour tous les cookies de session.",
                evidence=f"Cookie: {cookie.name}"
            ))
        
        if not getattr(cookie, 'httponly', False):
            findings.append(Finding(
                check="identification_authentication_failures",
                title=f"Cookie de session accessible en JavaScript : {cookie.name}",
                severity="medium",
                description="Le cookie de session n'a pas l'attribut HttpOnly.",
                remediation="Configurer l'attribut HttpOnly pour empêcher l'accès JavaScript.",
                evidence=f"Cookie: {cookie.name}"
            ))
        
        # Vérifier la robustesse de l'ID de session
        session_value = cookie.value
        findings.extend(_analyze_session_id_strength(cookie.name, session_value))
    
    return findings

def _analyze_session_id_strength(cookie_name: str, session_id: str) -> List[Finding]:
    """Analyse la robustesse d'un ID de session."""
    findings = []
    
    # Vérifier la longueur
    if len(session_id) < 16:
        findings.append(Finding(
            check="identification_authentication_failures",
            title=f"ID de session trop court : {cookie_name}",
            severity="medium",
            description=f"L'ID de session ({len(session_id)} caractères) est trop court.",
            remediation="Utiliser des IDs de session d'au moins 128 bits (16+ caractères).",
            evidence=f"Longueur: {len(session_id)} caractères"
        ))
    
    # Vérifier la complexité
    if re.match(r'^[0-9]+$', session_id):
        findings.append(Finding(
            check="identification_authentication_failures",
            title=f"ID de session prévisible : {cookie_name}",
            severity="high",
            description="L'ID de session ne contient que des chiffres (prévisible).",
            remediation="Utiliser un générateur cryptographiquement sécurisé pour les IDs de session.",
            evidence=f"Pattern détecté: chiffres uniquement"
        ))
    
    # Vérifier les patterns séquentiels
    if _is_sequential_pattern(session_id):
        findings.append(Finding(
            check="identification_authentication_failures", 
            title=f"ID de session séquentiel : {cookie_name}",
            severity="critical",
            description="L'ID de session semble suivre un pattern séquentiel.",
            remediation="Implémenter un générateur d'ID de session aléatoire et cryptographiquement sécurisé.",
            evidence=f"Pattern séquentiel détecté"
        ))
    
    return findings

def _check_session_regeneration(request: ScanRequest, base_url: str) -> List[Finding]:
    """Vérifie la régénération des IDs de session après authentification."""
    findings = []
    
    # Cette vérification nécessiterait une authentification réelle
    # Pour l'instant, on se contente de vérifier les endpoints de login
    
    login_endpoints = AUTH_ENDPOINTS['login']
    
    for endpoint in login_endpoints:
        try:
            # Vérifier si l'endpoint accepte les POST
            response = requests.options(f"{base_url}{endpoint}", timeout=request.timeout)
            
            if 'POST' in response.headers.get('Allow', ''):
                findings.append(Finding(
                    check="identification_authentication_failures",
                    title=f"Endpoint de login détecté : {endpoint}",
                    severity="info",
                    description=f"Un endpoint de login est disponible à {endpoint}.",
                    remediation="S'assurer que l'ID de session est régénéré après une authentification réussie.",
                    evidence=f"Méthode POST autorisée sur {endpoint}"
                ))
                
        except requests.exceptions.RequestException:
            continue
    
    return findings

def _test_user_enumeration(request: ScanRequest) -> List[Finding]:
    """Teste la possibilité d'énumération d'utilisateurs."""
    findings = []
    base_url = f"https://{request.domain}"
    
    # Tester sur les pages de login et de récupération de mot de passe
    test_endpoints = AUTH_ENDPOINTS['login'] + AUTH_ENDPOINTS['password_reset']
    
    for endpoint in test_endpoints:
        try:
            response = requests.get(f"{base_url}{endpoint}", timeout=request.timeout)
            
            if response.status_code == 200 and 'form' in response.text.lower():
                findings.extend(_check_enumeration_vulnerability(base_url, endpoint))
                
        except requests.exceptions.RequestException:
            continue
    
    return findings

def _check_enumeration_vulnerability(base_url: str, endpoint: str) -> List[Finding]:
    """Vérifie la vulnérabilité d'énumération sur un endpoint."""
    findings = []
    
    # Cette vérification est passive - on analyse juste les réponses d'erreur
    try:
        # Simuler des requêtes avec des utilisateurs inexistants
        test_data = {'username': 'nonexistent_user_12345', 'email': 'nonexistent@example.com'}
        
        response = requests.post(f"{base_url}{endpoint}", 
                               data=test_data, timeout=5, allow_redirects=False)
        
        # Analyser la réponse pour des indices d'énumération
        if response.status_code in [200, 400, 401]:
            response_text = response.text.lower()
            
            enumeration_patterns = [
                (r'user.*not.*found|username.*not.*exist', 
                 "Message d'erreur révélant l'inexistence d'un utilisateur"),
                (r'invalid.*username.*or.*password.*different.*format', 
                 "Messages d'erreur différents selon le cas"),
                (r'account.*not.*activated|user.*disabled', 
                 "Information sur l'état du compte"),
                (r'email.*not.*registered|email.*address.*unknown',
                 "Validation d'existence d'email")
            ]
            
            for pattern, description in enumeration_patterns:
                if re.search(pattern, response_text):
                    findings.append(Finding(
                        check="identification_authentication_failures",
                        title=f"Vulnérabilité d'énumération d'utilisateurs : {endpoint}",
                        severity="medium",
                        description=f"{description} permet l'énumération d'utilisateurs.",
                        remediation="Utiliser des messages d'erreur génériques identiques pour tous les cas.",
                        evidence=f"Pattern détecté: {description}"
                    ))
                    
    except requests.exceptions.RequestException:
        pass
    
    return findings

def _analyze_password_policies(request: ScanRequest) -> List[Finding]:
    """Analyse les politiques de mots de passe."""
    findings = []
    base_url = f"https://{request.domain}"
    
    # Chercher des informations sur les politiques de mot de passe
    policy_endpoints = ['/register', '/signup', '/password-policy', '/security-policy']
    
    for endpoint in policy_endpoints:
        try:
            response = requests.get(f"{base_url}{endpoint}", timeout=request.timeout)
            
            if response.status_code == 200:
                findings.extend(_analyze_password_requirements(endpoint, response.text))
                
        except requests.exceptions.RequestException:
            continue
    
    return findings

def _analyze_password_requirements(endpoint: str, content: str) -> List[Finding]:
    """Analyse les exigences de mot de passe dans le contenu."""
    findings = []
    content_lower = content.lower()
    
    # Chercher des indicateurs de politique de mot de passe faible
    weak_policy_indicators = [
        (r'minimum.*4.*character|at.*least.*4.*character', 
         "Longueur minimale trop faible (4 caractères)"),
        (r'minimum.*5.*character|at.*least.*5.*character',
         "Longueur minimale trop faible (5 caractères)"),  
        (r'minimum.*6.*character|at.*least.*6.*character',
         "Longueur minimale insuffisante (6 caractères)"),
        (r'no.*special.*character.*required',
         "Aucun caractère spécial requis"),
        (r'numbers.*not.*required|digits.*optional', 
         "Chiffres non obligatoires"),
        (r'case.*insensitive|no.*uppercase.*required',
         "Pas d'exigence de casse mixte")
    ]
    
    for pattern, description in weak_policy_indicators:
        if re.search(pattern, content_lower):
            findings.append(Finding(
                check="identification_authentication_failures",
                title=f"Politique de mot de passe faible : {endpoint}",
                severity="medium",
                description=f"{description} détecté dans la politique de mot de passe.",
                remediation="Implémenter une politique robuste : 8+ caractères, casse mixte, chiffres, caractères spéciaux.",
                evidence=f"Exigence faible: {description}"
            ))
    
    # Vérifier l'absence complète de politique visible
    policy_keywords = [
        'password.*requirement', 'password.*policy', 'password.*rule',
        'minimum.*length', 'character.*requirement', 'complexity'
    ]
    
    if not any(re.search(keyword, content_lower) for keyword in policy_keywords):
        findings.append(Finding(
            check="identification_authentication_failures",
            title=f"Politique de mot de passe non visible : {endpoint}",
            severity="low",
            description="Aucune politique de mot de passe n'est visible sur la page d'inscription.",
            remediation="Afficher clairement les exigences de mot de passe aux utilisateurs.",
            evidence=f"Aucune politique visible sur {endpoint}"
        ))
    
    return findings

def _check_brute_force_protection(request: ScanRequest) -> List[Finding]:
    """Vérifie les protections contre les attaques par force brute."""
    findings = []
    base_url = f"https://{request.domain}"
    
    # Tester les endpoints de login pour la protection rate limiting
    for endpoint in AUTH_ENDPOINTS['login']:
        try:
            # Effectuer plusieurs requêtes pour tester le rate limiting
            protection_detected = False
            
            for attempt in range(3):  # Test bénin avec seulement 3 tentatives
                test_data = {
                    'username': f'testuser{attempt}',
                    'password': 'wrongpassword'
                }
                
                response = requests.post(f"{base_url}{endpoint}",
                                       data=test_data, 
                                       timeout=request.timeout,
                                       allow_redirects=False)
                
                # Vérifier les signes de protection
                if (response.status_code == 429 or  # Too Many Requests
                    'rate limit' in response.text.lower() or
                    'too many attempts' in response.text.lower() or
                    'captcha' in response.text.lower()):
                    protection_detected = True
                    break
                
                time.sleep(0.5)  # Pause entre les tentatives
            
            if not protection_detected:
                findings.append(Finding(
                    check="identification_authentication_failures",
                    title=f"Protection anti-force brute insuffisante : {endpoint}",
                    severity="high",
                    description=f"L'endpoint {endpoint} ne semble pas protégé contre les attaques par force brute.",
                    remediation="Implémenter un rate limiting, CAPTCHA ou verrouillage temporaire de compte.",
                    evidence=f"Aucune protection détectée après 3 tentatives"
                ))
                
        except requests.exceptions.RequestException:
            continue
    
    return findings

def _test_authentication_bypass(request: ScanRequest) -> List[Finding]:
    """Teste les techniques de contournement d'authentification (mode invasif)."""
    findings = []
    base_url = f"https://{request.domain}"
    
    # Tester les bypasses courants
    bypass_tests = [
        ('/admin', {'X-Forwarded-User': 'admin'}, "Header X-Forwarded-User"),
        ('/admin', {'X-Remote-User': 'administrator'}, "Header X-Remote-User"),
        ('/admin', {'X-User': 'root'}, "Header X-User"),
        ('/api/admin', {'Authorization': 'Bearer invalid'}, "Token Bearer invalide")
    ]
    
    for endpoint, headers, test_description in bypass_tests:
        try:
            # Test sans headers spéciaux d'abord
            normal_response = requests.get(f"{base_url}{endpoint}", timeout=request.timeout)
            
            # Test avec headers de bypass
            bypass_response = requests.get(f"{base_url}{endpoint}", 
                                         headers=headers, timeout=request.timeout)
            
            # Comparer les réponses
            if (normal_response.status_code != bypass_response.status_code or
                len(normal_response.text) != len(bypass_response.text)):
                findings.append(Finding(
                    check="identification_authentication_failures",
                    title=f"Possible contournement d'authentification : {endpoint}",
                    severity="critical",
                    description=f"{test_description} modifie le comportement de l'authentification.",
                    remediation="Valider et nettoyer tous les headers d'authentification proxy.",
                    evidence=f"Test: {test_description}, Codes: {normal_response.status_code} → {bypass_response.status_code}"
                ))
                
        except requests.exceptions.RequestException:
            continue
    
    return findings

def _probe_token_vulnerabilities(request: ScanRequest) -> List[Finding]:
    """Sonde les vulnérabilités des tokens d'authentification (mode invasif)."""
    findings = []
    base_url = f"https://{request.domain}"
    
    # Endpoints susceptibles d'utiliser des tokens
    token_endpoints = [
        '/api/token', '/oauth/token', '/auth/token',
        '/api/refresh', '/refresh-token', '/renew-token'
    ]
    
    for endpoint in token_endpoints:
        try:
            response = requests.post(f"{base_url}{endpoint}",
                                   json={'grant_type': 'client_credentials'}, 
                                   timeout=request.timeout)
            
            if response.status_code == 200:
                findings.extend(_analyze_token_response(endpoint, response))
            elif response.status_code == 400:
                # Analyser les erreurs pour des fuites d'information
                findings.extend(_analyze_token_errors(endpoint, response))
                
        except requests.exceptions.RequestException:
            continue
    
    return findings

def _analyze_token_response(endpoint: str, response: requests.Response) -> List[Finding]:
    """Analyse une réponse de token pour les vulnérabilités."""
    findings = []
    
    try:
        token_data = response.json()
        
        # Vérifier les tokens faibles
        if 'access_token' in token_data:
            token = token_data['access_token']
            findings.extend(_analyze_token_strength(endpoint, token))
        
        # Vérifier les durées de vie excessives
        if 'expires_in' in token_data:
            expires_in = token_data['expires_in']
            if expires_in > 86400:  # Plus de 24 heures
                findings.append(Finding(
                    check="identification_authentication_failures",
                    title=f"Durée de vie de token excessive : {endpoint}",
                    severity="medium",
                    description=f"Le token a une durée de vie de {expires_in} secondes (>{expires_in//3600}h).",
                    remediation="Limiter la durée de vie des tokens à quelques heures maximum.",
                    evidence=f"expires_in: {expires_in}s"
                ))
        
        # Vérifier l'absence de refresh token
        if 'refresh_token' not in token_data and 'expires_in' in token_data:
            findings.append(Finding(
                check="identification_authentication_failures",
                title=f"Pas de refresh token : {endpoint}",
                severity="low",
                description="Aucun refresh token n'est fourni avec l'access token.",
                remediation="Implémenter des refresh tokens pour une gestion sécurisée des sessions longues.",
                evidence="Champ refresh_token absent"
            ))
            
    except json.JSONDecodeError:
        pass
    
    return findings

def _analyze_token_strength(endpoint: str, token: str) -> List[Finding]:
    """Analyse la robustesse d'un token d'authentification."""
    findings = []
    
    # Vérifier la longueur
    if len(token) < 32:
        findings.append(Finding(
            check="identification_authentication_failures",
            title=f"Token d'authentification trop court : {endpoint}",
            severity="high",
            description=f"Le token ({len(token)} caractères) est trop court pour être sécurisé.",
            remediation="Utiliser des tokens d'au moins 256 bits (32+ caractères).",
            evidence=f"Longueur du token: {len(token)} caractères"
        ))
    
    # Vérifier si c'est un JWT décodable
    if '.' in token and token.count('.') == 2:
        findings.extend(_analyze_jwt_token(endpoint, token))
    
    # Vérifier les patterns prévisibles
    if re.match(r'^[0-9]+$', token):
        findings.append(Finding(
            check="identification_authentication_failures",
            title=f"Token prévisible : {endpoint}",
            severity="critical",
            description="Le token ne contient que des chiffres et pourrait être prévisible.",
            remediation="Utiliser un générateur cryptographiquement sécurisé pour les tokens.",
            evidence="Token composé uniquement de chiffres"
        ))
    
    return findings

def _analyze_jwt_token(endpoint: str, jwt_token: str) -> List[Finding]:
    """Analyse un token JWT pour les vulnérabilités."""
    findings = []
    
    try:
        import base64
        
        # Décoder le header JWT
        header_b64 = jwt_token.split('.')[0]
        # Ajouter le padding si nécessaire
        header_b64 += '=' * (4 - len(header_b64) % 4)
        header = json.loads(base64.b64decode(header_b64).decode('utf-8'))
        
        # Vérifier l'algorithme
        alg = header.get('alg', '').lower()
        
        if alg == 'none':
            findings.append(Finding(
                check="identification_authentication_failures",
                title=f"JWT non signé : {endpoint}",
                severity="critical",
                description="Le JWT utilise l'algorithme 'none' (pas de signature).",
                remediation="Utiliser un algorithme de signature sécurisé (RS256, HS256).",
                evidence="Algorithme: none"
            ))
        elif alg in ['hs256', 'hs384', 'hs512']:
            findings.append(Finding(
                check="identification_authentication_failures",
                title=f"JWT avec HMAC : {endpoint}",
                severity="medium", 
                description=f"Le JWT utilise un algorithme HMAC ({alg.upper()}) qui partage le secret.",
                remediation="Considérer l'utilisation de RSA/ECDSA (RS256, ES256) pour plus de sécurité.",
                evidence=f"Algorithme: {alg.upper()}"
            ))
            
    except Exception:
        # Erreur de décodage - pas un JWT valide
        pass
    
    return findings

def _analyze_token_errors(endpoint: str, response: requests.Response) -> List[Finding]:
    """Analyse les erreurs de token pour les fuites d'information."""
    findings = []
    
    error_text = response.text.lower()
    
    # Patterns révélant des informations sensibles
    sensitive_patterns = [
        (r'invalid.*client.*secret', "Secret client révélé dans l'erreur"),
        (r'token.*expired.*at.*\d{4}-\d{2}-\d{2}', "Horodatage précis d'expiration"),
        (r'user.*not.*found.*id.*\d+', "ID utilisateur dans les erreurs"),
        (r'database.*error|sql.*error', "Erreur de base de données exposée")
    ]
    
    for pattern, description in sensitive_patterns:
        if re.search(pattern, error_text):
            findings.append(Finding(
                check="identification_authentication_failures",
                title=f"Fuite d'information dans les erreurs de token : {endpoint}",
                severity="medium",
                description=f"{description} dans les messages d'erreur.",
                remediation="Utiliser des messages d'erreur génériques sans détails sensibles.",
                evidence=f"Information exposée: {description}"
            ))
    
    return findings

def _test_mfa_implementation(request: ScanRequest) -> List[Finding]:
    """Teste l'implémentation de l'authentification multi-facteurs (mode invasif)."""
    findings = []
    base_url = f"https://{request.domain}"
    
    # Endpoints liés au MFA
    mfa_endpoints = [
        '/mfa', '/2fa', '/totp', '/multi-factor',
        '/api/mfa', '/api/2fa', '/verify-code',
        '/sms-verify', '/email-verify'
    ]
    
    mfa_detected = False
    
    for endpoint in mfa_endpoints:
        try:
            response = requests.get(f"{base_url}{endpoint}", timeout=request.timeout)
            
            if response.status_code == 200:
                mfa_detected = True
                findings.extend(_analyze_mfa_implementation(endpoint, response))
                
        except requests.exceptions.RequestException:
            continue
    
    # Si aucun MFA n'est détecté
    if not mfa_detected:
        findings.append(Finding(
            check="identification_authentication_failures",
            title="Authentification multi-facteurs non détectée",
            severity="medium",
            description="Aucun système d'authentification multi-facteurs n'a été détecté.",
            remediation="Implémenter une authentification à deux facteurs (TOTP, SMS, email) pour les comptes sensibles.",
            evidence="Aucun endpoint MFA trouvé"
        ))
    
    return findings

def _analyze_mfa_implementation(endpoint: str, response: requests.Response) -> List[Finding]:
    """Analyse l'implémentation MFA pour les vulnérabilités."""
    findings = []
    content = response.text.lower()
    
    # Vérifier les implémentations MFA faibles
    weak_mfa_patterns = [
        (r'sms.*only.*verification', "Vérification uniquement par SMS (vulnérable aux SIM swapping)"),
        (r'email.*only.*verification', "Vérification uniquement par email (moins sécurisé)"),
        (r'backup.*code.*unlimited', "Codes de secours illimités"),
        (r'bypass.*mfa|skip.*verification', "Option de contournement MFA"),
        (r'remember.*device.*forever', "Mémorisation permanente d'appareil")
    ]
    
    for pattern, description in weak_mfa_patterns:
        if re.search(pattern, content):
            findings.append(Finding(
                check="identification_authentication_failures",
                title=f"Implémentation MFA faible : {endpoint}",
                severity="medium",
                description=f"{description} détecté dans l'implémentation MFA.",
                remediation="Utiliser TOTP/HOTP comme méthode principale avec SMS/email comme backup limité.",
                evidence=f"Pattern détecté: {description}"
            ))
    
    return findings

# Fonctions utilitaires

def _is_sequential_pattern(value: str) -> bool:
    """Vérifie si une valeur suit un pattern séquentiel."""
    if len(value) < 3:
        return False
    
    # Vérifier les séquences numériques
    if value.isdigit():
        digits = [int(c) for c in value]
        # Séquence croissante
        if all(digits[i] == digits[i-1] + 1 for i in range(1, len(digits))):
            return True
        # Séquence décroissante  
        if all(digits[i] == digits[i-1] - 1 for i in range(1, len(digits))):
            return True
    
    # Vérifier les répétitions
    if len(set(value)) == 1:  # Tous les caractères identiques
        return True
        
    return False