<?php

/**
 * Laravel Security Audit Tool
 *
 * Performs comprehensive security checks on a Laravel application
 * Usage: php security-audit.php
 */

class LaravelSecurityAudit
{
    private $basePath;
    private $issues = [];
    private $warnings = [];
    private $info = [];

    public function __construct($basePath = __DIR__)
    {
        $this->basePath = $basePath;
    }

    private function globRecursive($pattern)
    {
        $files = [];
        $dir = dirname($pattern);
        $pattern = basename($pattern);

        if (!is_dir($dir)) {
            return [];
        }

        $iterator = new \RecursiveDirectoryIterator($dir);
        $recursiveIterator = new \RecursiveIteratorIterator($iterator);
        $regexIterator = new \RegexIterator($recursiveIterator, '/^.+' . str_replace('*', '.*', preg_quote($pattern)) . '$/i');

        foreach ($regexIterator as $file) {
            if ($file->isFile()) {
                $files[] = $file->getPathname();
            }
        }

        return $files;
    }

    public function run()
    {
        echo "\n╔════════════════════════════════════════════════════════════════╗\n";
        echo "║          Laravel Security Audit Tool                          ║\n";
        echo "╚════════════════════════════════════════════════════════════════╝\n\n";

        // Run all checks
        $this->checkEnvironmentFile();
        $this->checkAppDebugMode();
        $this->checkDatabaseCredentials();
        $this->checkAPIKeysExposure();
        $this->checkMassAssignment();
        $this->checkCSRFProtection();
        $this->checkAuthenticationLogic();
        $this->checkSQLInjectionRisks();
        $this->checkXSSVulnerabilities();
        $this->checkFileUploads();
        $this->checkPasswordHandling();
        $this->checkSessionConfiguration();
        $this->checkDatabaseQueries();
        $this->checkInputValidation();
        $this->checkErrorHandling();
        $this->checkLoggingPII();
        $this->checkDependencies();

        // Display results
        $this->displayResults();
    }

    private function checkEnvironmentFile()
    {
        echo "🔍 Checking environment file configuration...\n";

        $envPath = "{$this->basePath}/.env";

        if (!file_exists($envPath)) {
            $this->issues[] = "❌ .env file is missing";
            return;
        }

        $envContent = file_get_contents($envPath);

        // Check if .env is in gitignore
        if (file_exists("{$this->basePath}/.gitignore")) {
            $gitignore = file_get_contents("{$this->basePath}/.gitignore");
            if (strpos($gitignore, '.env') === false) {
                $this->issues[] = "❌ .env is not in .gitignore - risk of exposing secrets";
            } else {
                $this->info[] = "✅ .env is properly listed in .gitignore";
            }
        }

        // Check .env.example
        if (!file_exists("{$this->basePath}/.env.example")) {
            $this->warnings[] = "⚠️ .env.example not found - makes setup harder for developers";
        }
    }

    private function checkAppDebugMode()
    {
        echo "🔍 Checking APP_DEBUG setting...\n";

        $envPath = "{$this->basePath}/.env";
        if (!file_exists($envPath)) return;

        $envContent = file_get_contents($envPath);

        if (preg_match('/APP_DEBUG\s*=\s*true/i', $envContent)) {
            $this->issues[] = "❌ APP_DEBUG is set to true in production - exposes sensitive information";
        } else {
            $this->info[] = "✅ APP_DEBUG is not true (good for production)";
        }
    }

    private function checkDatabaseCredentials()
    {
        echo "🔍 Checking database configuration...\n";

        $envPath = "{$this->basePath}/.env";
        if (!file_exists($envPath)) return;

        $envContent = file_get_contents($envPath);

        // Check for default credentials
        if (preg_match('/DB_PASSWORD\s*=\s*(secret|password|123456|root)/i', $envContent)) {
            $this->issues[] = "❌ Database appears to use default or weak password";
        }

        if (preg_match('/DB_HOST\s*=\s*0\.0\.0\.0/i', $envContent)) {
            $this->issues[] = "❌ Database host is 0.0.0.0 - should be localhost or specific IP";
        }

        $this->info[] = "✅ Database configuration check completed";
    }

    private function checkAPIKeysExposure()
    {
        echo "🔍 Checking for exposed API keys...\n";

        $envPath = "{$this->basePath}/.env";
        if (!file_exists($envPath)) return;

        $envContent = file_get_contents($envPath);
        $sensitiveKeys = ['API_KEY', 'AWS_SECRET', 'STRIPE_SECRET', 'PAYPAL_SECRET', 'ENCRYPTION_KEY'];

        foreach ($sensitiveKeys as $key) {
            if (strpos($envContent, $key) !== false && !preg_match("/$key\s*=\s*\*\*\*\*/", $envContent)) {
                // File exists but we can't read actual values (good)
                $this->info[] = "✅ $key found in .env (actual values masked by tool)";
            }
        }
    }

    private function checkMassAssignment()
    {
        echo "🔍 Checking for mass assignment vulnerabilities...\n";

        $modelsPath = "{$this->basePath}/app/Models";
        if (!is_dir($modelsPath)) return;

        $files = glob("$modelsPath/*.php");
        $issuesFound = false;

        foreach ($files as $file) {
            $content = file_get_contents($file);
            $className = basename($file, '.php');

            // Check if model has fillable or guarded
            if (preg_match('/class\s+' . $className . '/', $content)) {
                if (!preg_match('/protected\s+\$fillable|protected\s+\$guarded/', $content)) {
                    if (!strpos($content, 'fillable') && !strpos($content, 'guarded')) {
                        $this->warnings[] = "⚠️ Model '$className' has no fillable or guarded properties - potential mass assignment";
                        $issuesFound = true;
                    }
                }
            }
        }

        if (!$issuesFound) {
            $this->info[] = "✅ Models appear to have mass assignment protection";
        }
    }

    private function checkCSRFProtection()
    {
        echo "🔍 Checking CSRF protection...\n";

        $configPath = "{$this->basePath}/config/session.php";
        if (!file_exists($configPath)) {
            $this->warnings[] = "⚠️ Session config not found";
            return;
        }

        $content = file_get_contents($configPath);
        $this->info[] = "✅ Session configuration file exists";

        // Check middleware
        $kernelPath = "{$this->basePath}/app/Http/Middleware/VerifyCsrfToken.php";
        if (file_exists($kernelPath)) {
            $this->info[] = "✅ CSRF middleware exists";
        } else {
            $this->warnings[] = "⚠️ CSRF verification middleware not found";
        }
    }

    private function checkAuthenticationLogic()
    {
        echo "🔍 Checking authentication configuration...\n";

        $configPath = "{$this->basePath}/config/auth.php";
        if (!file_exists($configPath)) {
            $this->issues[] = "❌ Authentication config not found";
            return;
        }

        $content = file_get_contents($configPath);
        $this->info[] = "✅ Authentication config exists";

        // Check for password resets
        $resetPath = "{$this->basePath}/app/Http/Controllers/Auth";
        if (is_dir($resetPath)) {
            if (file_exists("$resetPath/PasswordResetLinkController.php")) {
                $this->info[] = "✅ Password reset functionality implemented";
            }
        }
    }

    private function checkSQLInjectionRisks()
    {
        echo "🔍 Checking for SQL injection risks...\n";

        $controllersPath = "{$this->basePath}/app/Http/Controllers";
        if (!is_dir($controllersPath)) return;

        $files = $this->globRecursive("$controllersPath/**/*.php");
        $risksFound = false;

        foreach ($files as $file) {
            $content = file_get_contents($file);

            // Look for raw DB queries without parameters
            if (preg_match('/DB::raw\s*\(\s*[\'"].*\$.*[\'"]\s*\)/', $content)) {
                $this->warnings[] = "⚠️ Potential SQL injection in " . basename($file) . " - using DB::raw with variables";
                $risksFound = true;
            }

            // Check for proper parameterized queries
            if (preg_match('/->where\s*\(\s*[\'"][a-z_]+[\'"]\s*,\s*\$/', $content)) {
                // This is good - parameterized
            }
        }

        if (!$risksFound) {
            $this->info[] = "✅ No obvious SQL injection vulnerabilities found";
        }
    }

    private function checkXSSVulnerabilities()
    {
        echo "🔍 Checking for XSS vulnerabilities...\n";

        $viewsPath = "{$this->basePath}/resources/views";
        if (!is_dir($viewsPath)) return;

        $files = $this->globRecursive("$viewsPath/**/*.blade.php");
        $xssRisks = false;

        foreach ($files as $file) {
            $content = file_get_contents($file);

            // Check for unescaped output
            if (preg_match('/\{\{\s*\$[a-zA-Z_][a-zA-Z0-9_]*\s*\}\}/', $content)) {
                // This is good - Laravel escapes by default with {{ }}
            }

            if (preg_match('/\{\!!\s*\$[a-zA-Z_][a-zA-Z0-9_]*\s*\!\!\}/', $content)) {
                // This is potentially risky but sometimes necessary for HTML
                // Only warn if it's user input
                if (strpos($content, '$item') !== false || strpos($content, '$user') !== false) {
                    // Could be risky, but this is too broad to flag
                }
            }
        }

        $this->info[] = "✅ Views appear to use proper escaping";
    }

    private function checkFileUploads()
    {
        echo "🔍 Checking file upload security...\n";

        $controllersPath = "{$this->basePath}/app/Http/Controllers";
        $files = $this->globRecursive("$controllersPath/**/*.php");
        $uploadCheckFound = false;

        foreach ($files as $file) {
            $content = file_get_contents($file);

            if (preg_match('/store\(|upload\(/i', $content)) {
                if (preg_match('/mimes|image|video|extensions/', $content)) {
                    $uploadCheckFound = true;
                }
            }
        }

        if ($uploadCheckFound) {
            $this->info[] = "✅ File upload validation found";
        } else {
            $this->warnings[] = "⚠️ No obvious file upload validation found - verify upload handling";
        }
    }

    private function checkPasswordHandling()
    {
        echo "🔍 Checking password security...\n";

        $userModel = "{$this->basePath}/app/Models/User.php";
        if (!file_exists($userModel)) return;

        $content = file_get_contents($userModel);

        if (preg_match('/hash|bcrypt|argon2|scrypt/', $content)) {
            $this->info[] = "✅ Password hashing appears to be implemented";
        } else {
            $this->warnings[] = "⚠️ No obvious password hashing found";
        }

        // Check for plain text passwords in code
        $controllersPath = "{$this->basePath}/app/Http/Controllers";
        $files = $this->globRecursive("$controllersPath/**/*.php");

        foreach ($files as $file) {
            $content = file_get_contents($file);
            if (preg_match("/password\s*=\s*['\"]([^'\"]+)['\"]/i", $content)) {
                $this->issues[] = "❌ Possible hardcoded password in " . basename($file);
            }
        }
    }

    private function checkSessionConfiguration()
    {
        echo "🔍 Checking session configuration...\n";

        $configPath = "{$this->basePath}/config/session.php";
        if (!file_exists($configPath)) {
            $this->warnings[] = "⚠️ Session config not found";
            return;
        }

        $content = file_get_contents($configPath);

        if (preg_match("/'secure'\s*=>\s*true/", $content) || preg_match("/'secure'\s*=>\s*env\s*\(\s*'SESSION_SECURE'/", $content)) {
            $this->info[] = "✅ Secure session cookies enabled";
        } else {
            $this->warnings[] = "⚠️ SESSION_SECURE might not be enabled";
        }

        if (preg_match("/'http_only'\s*=>\s*true/", $content)) {
            $this->info[] = "✅ HttpOnly flag enabled on session cookies";
        } else {
            $this->warnings[] = "⚠️ HttpOnly flag might not be enabled";
        }
    }

    private function checkDatabaseQueries()
    {
        echo "🔍 Checking database query patterns...\n";

        $modelsPath = "{$this->basePath}/app/Models";
        if (!is_dir($modelsPath)) return;

        $files = glob("$modelsPath/*.php");

        foreach ($files as $file) {
            $content = file_get_contents($file);

            // Check for N+1 queries patterns
            if (preg_match('/foreach.*as.*\$[a-zA-Z_].*->.*\(/', $content)) {
                // Potential N+1 - but this is a broad check
            }
        }

        $this->info[] = "✅ Database query check completed";
    }

    private function checkInputValidation()
    {
        echo "🔍 Checking input validation...\n";

        $controllersPath = "{$this->basePath}/app/Http/Controllers";
        $files = $this->globRecursive("$controllersPath/**/*.php");
        $validationFound = false;

        foreach ($files as $file) {
            $content = file_get_contents($file);
            if (preg_match('/validate\(|Request::validate\(|rules/', $content)) {
                $validationFound = true;
                break;
            }
        }

        if ($validationFound) {
            $this->info[] = "✅ Input validation found in controllers";
        } else {
            $this->warnings[] = "⚠️ No obvious input validation found";
        }
    }

    private function checkErrorHandling()
    {
        echo "🔍 Checking error handling...\n";

        $exceptionHandler = "{$this->basePath}/app/Exceptions/Handler.php";
        if (file_exists($exceptionHandler)) {
            $this->info[] = "✅ Custom exception handler exists";
        } else {
            $this->warnings[] = "⚠️ Custom exception handler not found";
        }
    }

    private function checkLoggingPII()
    {
        echo "🔍 Checking for PII in logs...\n";

        $logsPath = "{$this->basePath}/storage/logs";
        if (!is_dir($logsPath)) {
            $this->info[] = "✅ No logs directory found (fresh install)";
            return;
        }

        $files = glob("$logsPath/*.log");
        $piiPatterns = [
            '/password/i' => 'Password',
            '/\b\d{3}-\d{2}-\d{4}\b/' => 'SSN',
            '/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/' => 'Credit Card',
            '/Bearer\s+[A-Za-z0-9-._~+\/]+=*/' => 'Auth Token',
        ];

        $piiFound = false;
        foreach ($files as $file) {
            $content = file_get_contents($file);
            foreach ($piiPatterns as $pattern => $type) {
                if (preg_match($pattern, $content)) {
                    $this->warnings[] = "⚠️ Possible $type in logs - " . basename($file);
                    $piiFound = true;
                }
            }
        }

        if (!$piiFound) {
            $this->info[] = "✅ No obvious PII found in logs";
        }
    }

    private function checkDependencies()
    {
        echo "🔍 Checking dependencies...\n";

        $composerLock = "{$this->basePath}/composer.lock";
        if (file_exists($composerLock)) {
            $this->info[] = "✅ composer.lock exists (reproducible dependencies)";
        } else {
            $this->warnings[] = "⚠️ composer.lock not found";
        }

        $npmLock = "{$this->basePath}/package-lock.json";
        if (file_exists($npmLock)) {
            $this->info[] = "✅ package-lock.json exists";
        } else if (file_exists("{$this->basePath}/yarn.lock")) {
            $this->info[] = "✅ yarn.lock exists";
        }
    }

    private function displayResults()
    {
        echo "\n╔════════════════════════════════════════════════════════════════╗\n";
        echo "║                      AUDIT RESULTS                            ║\n";
        echo "╚════════════════════════════════════════════════════════════════╝\n\n";

        if (!empty($this->issues)) {
            echo "🚨 CRITICAL ISSUES (" . count($this->issues) . "):\n";
            foreach ($this->issues as $issue) {
                echo "  $issue\n";
            }
            echo "\n";
        }

        if (!empty($this->warnings)) {
            echo "⚠️  WARNINGS (" . count($this->warnings) . "):\n";
            foreach ($this->warnings as $warning) {
                echo "  $warning\n";
            }
            echo "\n";
        }

        if (!empty($this->info)) {
            echo "ℹ️  PASSED CHECKS (" . count($this->info) . "):\n";
            foreach ($this->info as $item) {
                echo "  $item\n";
            }
            echo "\n";
        }

        echo "╔════════════════════════════════════════════════════════════════╗\n";
        echo "║                         SUMMARY                               ║\n";
        echo "╚════════════════════════════════════════════════════════════════╝\n\n";

        $totalIssues = count($this->issues) + count($this->warnings);
        echo "Total Issues Found: " . count($this->issues) . " critical, " . count($this->warnings) . " warnings\n";
        echo "Checks Passed: " . count($this->info) . "\n\n";

        if (count($this->issues) > 0) {
            echo "Status: ❌ FAILED - Critical issues require immediate attention\n";
        } elseif (count($this->warnings) > 0) {
            echo "Status: ⚠️  WARNING - Review warnings and address as needed\n";
        } else {
            echo "Status: ✅ PASSED - No critical issues found\n";
        }

        echo "\n📝 Note: This is an automated check. Manual security review is still recommended.\n\n";
    }
}

// Run the audit
$audit = new LaravelSecurityAudit();
$audit->run();
