#!/usr/bin/env python3
"""API Web Sentinel avec analyseur SAST intégré simple."""

from flask import Flask, request, jsonify
from flask_cors import CORS
import os
import re
from datetime import datetime
import logging

# Configuration
app = Flask(__name__)
CORS(app)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Mock des clés API
mock_api_keys = {
    "ws_sysop_CUsMkeL6jdDHXrA14rot9Vj0Yegg4KiPuYqiHMKhRC0": {
        "email": "aurelien.mossini@example.com",
        "name": "Aurelien Mossini", 
        "tier": "sysop",
        "active": True
    }
}

# Patterns de vulnérabilités pour différents langages
VULNERABILITY_PATTERNS = {
    'python': [
        {
            'pattern': r'eval\s*\(',
            'title': 'Utilisation dangereuse d\'eval()',
            'severity': 'critical',
            'description': 'L\'utilisation d\'eval() avec des données utilisateur peut conduire à l\'injection de code',
            'remediation': 'Éviter eval() ou utiliser ast.literal_eval() pour les données sûres'
        },
        {
            'pattern': r'exec\s*\(',
            'title': 'Utilisation dangereuse d\'exec()',
            'severity': 'critical', 
            'description': 'L\'utilisation d\'exec() peut permettre l\'exécution de code arbitraire',
            'remediation': 'Éviter exec() ou valider strictement l\'entrée'
        },
        {
            'pattern': r'os\.system\s*\(',
            'title': 'Injection de commande potentielle',
            'severity': 'high',
            'description': 'os.system() avec des données utilisateur peut conduire à l\'injection de commandes',
            'remediation': 'Utiliser subprocess avec une liste d\'arguments ou valider l\'entrée'
        },
        {
            'pattern': r'subprocess\.(call|run|Popen)\s*\([^,]*\+',
            'title': 'Concaténation dans subprocess',
            'severity': 'high',
            'description': 'Concaténation de chaînes dans subprocess peut conduire à l\'injection de commandes',
            'remediation': 'Utiliser une liste d\'arguments au lieu de concaténer des chaînes'
        },
        {
            'pattern': r'(password|passwd|pwd|secret|key|token|api_key|private_key)\s*=\s*["\'][^"\']{3,}["\']',
            'title': 'Secrets codés en dur détectés',
            'severity': 'high',
            'description': 'Des secrets potentiels ont été trouvés dans le code source',
            'remediation': 'Utiliser des variables d\'environnement ou un gestionnaire de secrets'
        },
        {
            'pattern': r'hashlib\.md5\s*\(',
            'title': 'Algorithme de hachage faible (MD5)',
            'severity': 'medium',
            'description': 'MD5 est cryptographiquement cassé et ne doit pas être utilisé',
            'remediation': 'Utiliser SHA-256 ou plus récent pour les applications cryptographiques'
        },
        {
            'pattern': r'hashlib\.sha1\s*\(',
            'title': 'Algorithme de hachage faible (SHA1)',
            'severity': 'medium',
            'description': 'SHA1 est faible et ne doit pas être utilisé pour la cryptographie',
            'remediation': 'Utiliser SHA-256 ou plus récent'
        },
        {
            'pattern': r'SELECT\s+.*\s+FROM\s+.*["\'][^"\']*\+',
            'title': 'Injection SQL potentielle',
            'severity': 'high',
            'description': 'Concaténation de chaînes dans une requête SQL',
            'remediation': 'Utiliser des requêtes préparées ou un ORM'
        }
    ],
    'javascript': [
        {
            'pattern': r'eval\s*\(',
            'title': 'Utilisation dangereuse d\'eval()',
            'severity': 'critical',
            'description': 'eval() peut exécuter du code JavaScript arbitraire',
            'remediation': 'Éviter eval() ou utiliser JSON.parse() pour les données JSON'
        },
        {
            'pattern': r'innerHTML\s*=.*\+',
            'title': 'XSS potentiel via innerHTML',
            'severity': 'high',
            'description': 'Insertion de contenu non échappé via innerHTML',
            'remediation': 'Utiliser textContent ou échapper le contenu HTML'
        },
        {
            'pattern': r'document\.write\s*\(',
            'title': 'XSS potentiel via document.write',
            'severity': 'medium',
            'description': 'document.write avec du contenu non validé',
            'remediation': 'Utiliser des méthodes DOM sûres'
        }
    ],
    'php': [
        {
            'pattern': r'eval\s*\(',
            'title': 'Utilisation dangereuse d\'eval()',
            'severity': 'critical',
            'description': 'eval() en PHP peut exécuter du code arbitraire',
            'remediation': 'Éviter eval() complètement'
        },
        {
            'pattern': r'mysql_query\s*\([^,]*\$',
            'title': 'Injection SQL potentielle',
            'severity': 'high',
            'description': 'Utilisation de mysql_query avec des variables non échappées',
            'remediation': 'Utiliser des requêtes préparées'
        }
    ]
}

def validate_api_key_mock(key: str):
    """Validation mock des clés API."""
    if key in mock_api_keys and mock_api_keys[key]["active"]:
        return mock_api_keys[key]
    return None

def can_scan_source(api_key_data):
    """Vérifier si le tier peut scanner du code source."""
    return api_key_data["tier"].lower() == "sysop"

def analyze_source_code(content: str, language: str) -> list:
    """Analyser le code source pour détecter les vulnérabilités."""
    findings = []
    
    # Obtenir les patterns pour le langage
    patterns = VULNERABILITY_PATTERNS.get(language.lower(), [])
    
    lines = content.split('\n')
    
    for pattern_def in patterns:
        pattern = re.compile(pattern_def['pattern'], re.IGNORECASE | re.MULTILINE)
        
        for line_num, line in enumerate(lines, 1):
            matches = pattern.finditer(line)
            for match in matches:
                # Extraire le code autour de la ligne
                start_line = max(0, line_num - 2)
                end_line = min(len(lines), line_num + 1)
                context_lines = lines[start_line:end_line]
                
                evidence = f"Ligne: {line_num}\nCode: {line.strip()}"
                if len(context_lines) > 1:
                    evidence += f"\nContexte:\n" + "\n".join(f"{start_line + i + 1}: {l}" for i, l in enumerate(context_lines))
                
                findings.append({
                    'check': 'sast-analyzer',
                    'title': pattern_def['title'],
                    'severity': pattern_def['severity'],
                    'description': pattern_def['description'],
                    'remediation': pattern_def['remediation'],
                    'evidence': evidence,
                    'line': line_num,
                    'column': match.start() + 1
                })
    
    return findings

@app.route('/api/v1/health', methods=['GET'])
def health():
    """Health check endpoint."""
    return jsonify({
        'status': 'healthy',
        'version': '2.0.0-sast',
        'timestamp': datetime.utcnow().isoformat(),
        'server': 'taaazzz-prog.fr'
    })

@app.route('/api/v1/scan-source', methods=['POST'])
def scan_source_code():
    """Scanner du code source avec analyseur intégré."""
    start_time = datetime.utcnow()
    
    try:
        data = request.get_json()
        if not data:
            return jsonify({'error': 'JSON payload required'}), 400
        
        # Validation de l'API key
        api_key_str = data.get('api_key')
        api_key_data = validate_api_key_mock(api_key_str)
        
        if not api_key_data:
            return jsonify({'error': 'Invalid or missing API key'}), 401
        
        # Vérifier les permissions pour le scan de source
        if not can_scan_source(api_key_data):
            return jsonify({
                'error': 'Source code scanning requires SYSOP tier',
                'current_tier': api_key_data['tier'],
                'required_tier': 'sysop'
            }), 403
        
        # Paramètres obligatoires
        source_content = data.get('source_content')
        if not source_content:
            return jsonify({'error': 'source_content required'}), 400
        
        file_path = data.get('file_path', 'unknown.txt')
        language = data.get('language', 'python')
        
        logger.info(f"Début scan SAST pour {api_key_data['email']} - Language: {language}")
        
        # Analyser le code source
        findings = analyze_source_code(source_content, language)
        
        processing_time = (datetime.utcnow() - start_time).total_seconds()
        logger.info(f"Scan SAST terminé: {len(findings)} vulnérabilités trouvées en {processing_time:.2f}s")
        
        return jsonify({
            'status': 'success',
            'scan_id': f"sast_{int(datetime.utcnow().timestamp())}",
            'timestamp': datetime.utcnow().isoformat(),
            'file_path': file_path,
            'language': language,
            'findings': findings,
            'summary': {
                'total': len(findings),
                'processing_time': processing_time,
                'by_severity': {
                    'critical': len([f for f in findings if f['severity'] == 'critical']),
                    'high': len([f for f in findings if f['severity'] == 'high']),
                    'medium': len([f for f in findings if f['severity'] == 'medium']),
                    'low': len([f for f in findings if f['severity'] == 'low'])
                }
            }
        })
        
    except Exception as e:
        logger.error(f"Erreur dans scan source: {e}")
        import traceback
        traceback.print_exc()
        return jsonify({
            'error': 'Internal server error', 
            'details': str(e),
            'type': type(e).__name__
        }), 500

@app.route('/api/v1/admin/create-sysop-key', methods=['POST'])
def create_sysop_key():
    """Créer une clé API SYSOP (endpoint administratif)."""
    try:
        data = request.get_json()
        
        # Vérification du secret admin
        admin_secret = data.get('admin_secret')
        expected_secret = os.environ.get('WEB_SENTINEL_ADMIN_SECRET')
        
        if not admin_secret or not expected_secret or admin_secret != expected_secret:
            return jsonify({'error': 'Invalid admin secret'}), 403
        
        name = data.get('name')
        email = data.get('email')
        
        if not name or not email:
            return jsonify({'error': 'name and email required'}), 400
        
        # Générer une clé API SYSOP
        import secrets
        api_key_str = f"ws_sysop_{secrets.token_urlsafe(32)}"
        
        # Stocker dans le mock
        mock_api_keys[api_key_str] = {
            'name': name,
            'email': email,
            'tier': 'sysop',
            'active': True,
            'created_at': datetime.utcnow().isoformat()
        }
        
        logger.info(f"Clé SYSOP créée pour {email}: {api_key_str}")
        
        return jsonify({
            'api_key': api_key_str,
            'tier': 'sysop',
            'message': 'SYSOP API key generated successfully',
            'permissions': ['source_code_scanning']
        })
        
    except Exception as e:
        logger.error(f"Erreur création clé SYSOP: {e}")
        return jsonify({'error': 'Internal server error'}), 500

if __name__ == '__main__':
    print("=== Web Sentinel API SAST v2.0 ===")
    print(f"Secret admin: {os.environ.get('WEB_SENTINEL_ADMIN_SECRET', 'NOT SET')}")
    print("Patterns de vulnérabilités chargés pour:", ', '.join(VULNERABILITY_PATTERNS.keys()))
    print("Clés API disponibles:")
    for key, data in mock_api_keys.items():
        print(f"  - {key[:20]}... ({data['email']}, tier: {data['tier']})")
    
    app.run(host='0.0.0.0', port=5002, debug=False)  # Port 5002 pour tester