<?php
/**
 * XML-RPC Security Class
 * 
 * Handles XML-RPC authentication restrictions and 2FA enforcement
 * Phase 4: XML-RPC Security
 * 
 * @package VMPFence
 * @since 2.1.0
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

class VMPFence_XMLRPC_Security {
    
    /**
     * Initialize XML-RPC security hooks
     */
    public static function init() {
        // Check if XML-RPC auth should be completely disabled
        $disable_xmlrpc = VMPFence_Config::get('vmpfence_disable_xmlrpc_auth', false);
        
        if ($disable_xmlrpc) {
            // Disable XML-RPC authentication entirely
            // Priority 20 ensures this runs AFTER existing xmlrpc_enabled filters (lib/class-hooks.php at priority 10)
            add_filter('xmlrpc_enabled', '__return_false', 20);
            add_filter('wp_xmlrpc_server_class', array(__CLASS__, 'disable_xmlrpc_methods'));
        } else {
            // Check if 2FA is required for XML-RPC
            $require_2fa = VMPFence_Config::get('vmpfence_xmlrpc_2fa_required', false);
            
            if ($require_2fa) {
                // Hook into XML-RPC authentication to check 2FA
                add_filter('authenticate', array(__CLASS__, 'check_xmlrpc_2fa'), 40, 3);
            }
        }
    }
    
    /**
     * Disable XML-RPC methods that require authentication
     * 
     * @param string $class
     * @return string
     */
    public static function disable_xmlrpc_methods($class) {
        // Define the disabled server class if it doesn't exist yet
        if (!class_exists('VMPFence_Disabled_XMLRPC_Server')) {
            self::define_disabled_xmlrpc_server();
        }
        return 'VMPFence_Disabled_XMLRPC_Server';
    }
    
    /**
     * Define the disabled XML-RPC server class
     * Only called when needed to avoid fatal errors
     */
    private static function define_disabled_xmlrpc_server() {
        // Ensure wp_xmlrpc_server is loaded
        if (!class_exists('wp_xmlrpc_server')) {
            require_once ABSPATH . WPINC . '/class-wp-xmlrpc-server.php';
        }
        
        // Ensure IXR_Error is loaded
        if (!class_exists('IXR_Error')) {
            require_once ABSPATH . WPINC . '/IXR.php';
        }
        
        // Define the disabled server class
        eval('
        class VMPFence_Disabled_XMLRPC_Server extends wp_xmlrpc_server {
            public function login($username, $password) {
                return new IXR_Error(403, __("XML-RPC authentication has been disabled by the site administrator.", "vmpfence-security"));
            }
            public function login_pass_ok($user_login, $user_pass) {
                return false;
            }
        }
        ');
    }
    
    /**
     * Check 2FA for XML-RPC authentication
     * 
     * @since 2.1.0
     * @since 2.2.3 - Removed license check to allow 2FA for all users
     * @param WP_User|WP_Error|null $user
     * @param string $username
     * @param string $password
     * @return WP_User|WP_Error
     */
    public static function check_xmlrpc_2fa($user, $username, $password) {
        // Only check for XML-RPC requests
        if (!defined('XMLRPC_REQUEST') || !XMLRPC_REQUEST) {
            return $user;
        }
        
        // PHASE 7: Skip XML-RPC 2FA for allowlisted IPs
        $user_ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
        if (!empty($user_ip) && class_exists('VMPFence_IP_Allowlist') && VMPFence_IP_Allowlist::is_ip_allowlisted($user_ip)) {
            return $user; // Allow without 2FA for allowlisted IPs
        }
        
        // Skip if not a valid user or already an error
        if (is_wp_error($user) || !($user instanceof WP_User)) {
            return $user;
        }
        
        // BUG FIX #6: Check if user's role actually requires 2FA before enforcing
        // This prevents forcing 2FA on users whose roles have it disabled/optional
        $user_roles = $user->roles;
        $requires_2fa_for_role = false;
        
        if (!empty($user_roles)) {
            foreach ($user_roles as $role) {
                $role_setting = VMPFence_Config::get('vmpfence_2fa_role_' . $role, 'disabled');
                if ($role_setting === 'required' || $role_setting === 'optional') {
                    $requires_2fa_for_role = true;
                    break;
                }
            }
        }
        
        // If 2FA is disabled for all user's roles, skip enforcement
        if (!$requires_2fa_for_role) {
            return $user;
        }
        
        // Check if user has 2FA enabled
        $api_client = new VMPFence_API_Client();
        $api_key = VMPFence_Config::get('central_api_key');
        
        if (!$api_key) {
            // No API connection, can't check 2FA - allow access
            return $user;
        }
        
        // 2FA is now available for all users (free and premium)
        // No license check needed
        
        $twofa_status = $api_client->get_2fa_status($api_key, $user->ID);
        $has_2fa = (!is_wp_error($twofa_status) && isset($twofa_status['enabled']) && $twofa_status['enabled']);
        
        if (!$has_2fa) {
            // User doesn't have 2FA enabled - block if required
            return new WP_Error(
                'xmlrpc_2fa_required',
                __('Two-Factor Authentication is required for XML-RPC access. Please enable 2FA in your account settings.', 'vmpfence-security')
            );
        }
        
        // User has 2FA - check if they provided a valid code
        // XML-RPC doesn't support interactive 2FA, so code must be appended to password
        // Format: password|2FAcode (e.g., "mypassword|123456")
        
        // BUG FIX: Check for empty password to prevent warnings
        if (empty($password)) {
            return new WP_Error(
                'xmlrpc_2fa_code_missing',
                __('Password is required. For 2FA-enabled users, append your 2FA code with a pipe character (password|code).', 'vmpfence-security')
            );
        }
        
        if (strpos($password, '|') === false) {
            // No 2FA code provided
            return new WP_Error(
                'xmlrpc_2fa_code_missing',
                __('Two-Factor Authentication code required. Please append your 2FA code to your password with a pipe character (password|code).', 'vmpfence-security')
            );
        }
        
        // Extract 2FA code from password
        $parts = explode('|', $password, 2);
        $actual_password = $parts[0];
        $twofa_code = isset($parts[1]) ? $parts[1] : '';
        
        // Validate 2FA code format (6 digits)
        if (!preg_match('/^[0-9]{6}$/', $twofa_code)) {
            return new WP_Error(
                'xmlrpc_2fa_invalid_format',
                __('Invalid Two-Factor Authentication code format. Code must be 6 digits.', 'vmpfence-security')
            );
        }
        
        // BUG FIX: Prevent infinite loop by temporarily removing this filter before re-authenticating
        remove_filter('authenticate', array(__CLASS__, 'check_xmlrpc_2fa'), 40);
        
        // Re-authenticate with actual password (without 2FA code)
        $auth_user = wp_authenticate_username_password(null, $username, $actual_password);
        
        // Re-add the filter after authentication
        add_filter('authenticate', array(__CLASS__, 'check_xmlrpc_2fa'), 40, 3);
        
        if (is_wp_error($auth_user)) {
            return $auth_user; // Password authentication failed
        }
        
        // BUG FIX #10: Use $auth_user (authenticated user) instead of $user for 2FA verification
        // This ensures we're verifying 2FA for the user that just authenticated with password
        $verification = $api_client->verify_2fa_code($api_key, $auth_user->ID, $twofa_code);
        
        if (is_wp_error($verification) || !isset($verification['valid']) || !$verification['valid']) {
            return new WP_Error(
                'xmlrpc_2fa_invalid',
                __('Invalid Two-Factor Authentication code.', 'vmpfence-security')
            );
        }
        
        // 2FA verified - return the authenticated user object
        return $auth_user;
    }
}

// Initialize XML-RPC security on plugins_loaded
add_action('plugins_loaded', array('VMPFence_XMLRPC_Security', 'init'), 30);
