load_settings(); $this->init_oauth2_server(); $this->register_default_authenticators(); $this->init_wordpress_hooks(); } /** * Load plugin settings with secure defaults */ private function load_settings(): void { $defaults = [ 'enabled' => true, 'oauth2_enabled' => true, 'api_key_enabled' => false, 'jwt_enabled' => false, 'block_unauthenticated_rest' => false, 'allowed_authentication_types' => ['oauth2'], 'require_https' => true, 'token_expiry' => 3600, // 1 hour ]; $stored_settings = get_option('wp_oauth2_poc_settings', []); $this->settings = array_merge($defaults, $stored_settings); } /** * Initialize OAuth2 Server */ private function init_oauth2_server(): void { $this->oauth2_server = new OAuth2Server($this->settings); } /** * Register default authenticators */ private function register_default_authenticators(): void { // OAuth2 Bearer Token Authenticator if ($this->settings['oauth2_enabled']) { $this->register_authenticator(new OAuth2BearerAuthenticator()); } // API Key Authenticator (for future extension) if ($this->settings['api_key_enabled']) { $this->register_authenticator(new ApiKeyAuthenticator()); } // JWT Token Authenticator (for future extension) if ($this->settings['jwt_enabled']) { $this->register_authenticator(new JwtAuthenticator()); } } /** * Initialize WordPress hooks */ private function init_wordpress_hooks(): void { // Core authentication hook - THE PATTERN FROM ORIGINAL PLUGIN add_filter('determine_current_user', [$this, 'authenticate_user'], 20); // REST API protection add_filter('rest_authentication_errors', [$this, 'protect_rest_api']); // URL rewrite rules for OAuth endpoints add_action('init', [$this, 'register_rewrite_rules']); // Handle OAuth endpoints add_action('template_redirect', [$this, 'handle_oauth_requests']); // Admin interface hooks if (is_admin()) { add_action('admin_menu', [$this, 'add_admin_menu']); add_action('admin_init', [$this, 'register_settings']); } } /** * Register an authenticator * This allows extending the system with new authentication types */ public function register_authenticator(AuthenticatorInterface $authenticator): void { $this->authenticators[$authenticator->get_name()] = $authenticator; } /** * Main authentication method - WordPress determine_current_user filter * * This is the CORE PATTERN from the original plugin, implemented securely */ public function authenticate_user(?int $user_id): ?int { // Respect existing authentication FIRST (non-destructive pattern) if ($user_id && $user_id > 0) { return $user_id; } // Check if authentication is enabled if (!$this->settings['enabled']) { return $user_id; } // HTTPS enforcement for production if ($this->settings['require_https'] && !is_ssl() && !wp_get_environment_type() === 'development') { return $user_id; } // Try each registered authenticator foreach ($this->authenticators as $authenticator) { if (!$authenticator->can_authenticate()) { continue; } try { $authenticated_user_id = $authenticator->authenticate(); if ($authenticated_user_id && $authenticated_user_id > 0) { // Log successful authentication for security monitoring $this->log_authentication_event($authenticator->get_name(), $authenticated_user_id, 'success'); return (int) $authenticated_user_id; } } catch (\Exception $e) { // Log authentication failure $this->log_authentication_event($authenticator->get_name(), null, 'failure', $e->getMessage()); // Continue to next authenticator continue; } } // CRITICAL: Return false (not null) for WordPress compatibility return false; } /** * Protect REST API endpoints based on settings */ public function protect_rest_api($result) { if (!$this->settings['block_unauthenticated_rest']) { return $result; } // Allow if user is already authenticated if (is_user_logged_in()) { return $result; } // Block unauthenticated requests return new \WP_Error( 'rest_not_authorized', __('Authentication required to access this endpoint.', 'wp-oauth2-poc'), ['status' => 401] ); } /** * Register OAuth2 endpoint rewrite rules */ public function register_rewrite_rules(): void { // Standard OAuth2 endpoints add_rewrite_rule('^oauth/token/?$', 'index.php?oauth_endpoint=token', 'top'); add_rewrite_rule('^oauth/authorize/?$', 'index.php?oauth_endpoint=authorize', 'top'); add_rewrite_rule('^oauth/revoke/?$', 'index.php?oauth_endpoint=revoke', 'top'); // OpenID Connect discovery add_rewrite_rule('^\.well-known/oauth-authorization-server/?$', 'index.php?oauth_endpoint=metadata', 'top'); // Register query variables global $wp; $wp->add_query_var('oauth_endpoint'); } /** * Handle OAuth endpoint requests */ public function handle_oauth_requests(): void { global $wp_query; $endpoint = $wp_query->get('oauth_endpoint'); if (!$endpoint) { return; } // Define constant for OAuth context if (!defined('DOING_OAUTH')) { define('DOING_OAUTH', true); } // Route to appropriate endpoint handler switch ($endpoint) { case 'token': $this->handle_token_endpoint(); break; case 'authorize': $this->handle_authorize_endpoint(); break; case 'revoke': $this->handle_revoke_endpoint(); break; case 'metadata': $this->handle_metadata_endpoint(); break; default: $this->send_error_response(404, 'invalid_endpoint', 'Unknown OAuth endpoint'); } exit; } /** * Handle token endpoint */ private function handle_token_endpoint(): void { $this->oauth2_server->handle_token_request(); } /** * Generate cryptographically secure token */ private function generate_secure_token(): string { // Use cryptographically secure random bytes (fixes original plugin's weakness) return bin2hex(random_bytes(32)); } /** * Handle authorize endpoint */ private function handle_authorize_endpoint(): void { // Check if this is a form submission if ($_SERVER['REQUEST_METHOD'] === 'POST') { $this->oauth2_server->process_authorization_form(); } else { $this->oauth2_server->handle_authorization_request(); } } /** * Handle revoke endpoint placeholder */ private function handle_revoke_endpoint(): void { $this->send_error_response(501, 'not_implemented', 'Revoke endpoint not yet implemented in PoC'); } /** * Handle OAuth metadata endpoint */ private function handle_metadata_endpoint(): void { $metadata = [ 'issuer' => home_url(), 'token_endpoint' => home_url('oauth/token'), 'authorization_endpoint' => home_url('oauth/authorize'), 'revocation_endpoint' => home_url('oauth/revoke'), 'response_types_supported' => ['code'], 'grant_types_supported' => ['authorization_code'], 'token_endpoint_auth_methods_supported' => ['client_secret_basic'], 'code_challenge_methods_supported' => ['S256'], // Only secure PKCE method ]; $this->send_json_response($metadata); } /** * Send JSON response with proper headers */ private function send_json_response(array $data, int $status_code = 200): void { status_header($status_code); header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-cache, no-store, must-revalidate'); header('Pragma: no-cache'); header('Expires: 0'); echo json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } /** * Send OAuth2 error response */ private function send_error_response(int $status_code, string $error, string $description): void { $error_data = [ 'error' => $error, 'error_description' => $description, ]; $this->send_json_response($error_data, $status_code); } /** * Log authentication events for security monitoring */ private function log_authentication_event(string $auth_type, ?int $user_id, string $status, string $details = ''): void { $log_entry = [ 'timestamp' => current_time('mysql'), 'auth_type' => $auth_type, 'user_id' => $user_id, 'status' => $status, 'details' => $details, 'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown', ]; // Store in WordPress options for PoC (in production, use proper logging) $existing_logs = get_option('wp_oauth2_poc_auth_logs', []); $existing_logs[] = $log_entry; // Keep only last 100 entries for PoC if (count($existing_logs) > 100) { $existing_logs = array_slice($existing_logs, -100); } update_option('wp_oauth2_poc_auth_logs', $existing_logs); } /** * Add admin menu */ public function add_admin_menu(): void { add_options_page( __('OAuth2 PoC Settings', 'wp-oauth2-poc'), __('OAuth2 PoC', 'wp-oauth2-poc'), 'manage_options', 'wp-oauth2-poc', [$this, 'render_admin_page'] ); } /** * Register settings */ public function register_settings(): void { register_setting('wp_oauth2_poc_settings', 'wp_oauth2_poc_settings'); } /** * Render admin page */ public function render_admin_page(): void { if (!current_user_can('manage_options')) { wp_die(__('You do not have sufficient permissions to access this page.')); } // Handle client creation if ($_POST && wp_verify_nonce($_POST['oauth2_nonce'] ?? '', 'create_client')) { $this->handle_client_creation(); } // Handle client deletion if (isset($_GET['delete_client']) && wp_verify_nonce($_GET['_wpnonce'] ?? '', 'delete_client')) { $this->handle_client_deletion($_GET['delete_client']); } $client_manager = new OAuth2ClientManager(); $user_clients = $client_manager->get_user_clients(); $stats = $client_manager->get_statistics(); echo '
'; echo '

' . __('OAuth2 Server Management', 'wp-oauth2-poc') . '

'; // Statistics overview echo '
'; echo '

' . __('Server Statistics', 'wp-oauth2-poc') . '

'; echo '
'; echo '
' . __('Total Clients:', 'wp-oauth2-poc') . ' ' . esc_html($stats['total_clients']) . '
'; echo '
' . __('Active Tokens:', 'wp-oauth2-poc') . ' ' . esc_html($stats['active_tokens']) . '
'; echo '
' . __('Today\'s Authorizations:', 'wp-oauth2-poc') . ' ' . esc_html($stats['todays_authorizations']) . '
'; echo '
' . __('Public Clients:', 'wp-oauth2-poc') . ' ' . esc_html($stats['public_clients']) . '
'; echo '
'; echo '
'; // OAuth2 endpoints information echo '
'; echo '

' . __('OAuth2 Endpoints', 'wp-oauth2-poc') . '

'; echo ''; echo ''; echo ''; echo ''; echo ''; echo '
EndpointURLMethod
Authorization' . esc_html(home_url('oauth/authorize')) . 'GET
Token' . esc_html(home_url('oauth/token')) . 'POST
Metadata' . esc_html(home_url('.well-known/oauth-authorization-server')) . 'GET
'; echo '
'; // Client management echo '
'; echo '

' . __('Create New Client', 'wp-oauth2-poc') . '

'; echo '
'; wp_nonce_field('create_client', 'oauth2_nonce'); echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo '
'; echo '

'; echo '
'; echo '
'; // Existing clients if (!empty($user_clients)) { echo '
'; echo '

' . __('Your OAuth2 Clients', 'wp-oauth2-poc') . '

'; echo ''; echo ''; echo ''; foreach ($user_clients as $client) { echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
NameClient IDRedirect URITypeCreatedActions
' . esc_html($client['client_name']) . '' . esc_html($client['client_id']) . '' . esc_html($client['redirect_uri']) . '' . esc_html($client['is_public'] ? 'Public' : 'Confidential') . '' . esc_html($client['created_at']) . ''; $delete_url = wp_nonce_url( add_query_arg(['delete_client' => $client['client_id']]), 'delete_client' ); echo 'Delete'; echo '
'; echo '
'; } // Testing section echo '
'; echo '

' . __('Testing & Development', 'wp-oauth2-poc') . '

'; echo '

Run OAuth2 Flow Test

'; // Show test token for backward compatibility $test_token = get_option('wp_oauth2_poc_test_token'); if ($test_token) { echo '

' . __('Legacy Test Token:', 'wp-oauth2-poc') . ' ' . esc_html($test_token) . '

'; echo '

Use this token for testing: Authorization: Bearer ' . esc_html($test_token) . '

'; } echo '
'; // Authentication logs $logs = get_option('wp_oauth2_poc_auth_logs', []); if (!empty($logs)) { echo '
'; echo '

' . __('Recent Authentication Events', 'wp-oauth2-poc') . '

'; echo ''; echo ''; echo ''; foreach (array_reverse(array_slice($logs, -10)) as $log) { echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
TimestampTypeUser IDStatusIP
' . esc_html($log['timestamp']) . '' . esc_html($log['auth_type']) . '' . esc_html($log['user_id'] ?? 'N/A') . '' . esc_html($log['status']) . '' . esc_html($log['ip_address']) . '
'; echo '
'; } echo '
'; } /** * Handle client creation */ private function handle_client_creation(): void { try { $client_manager = new OAuth2ClientManager(); $client = $client_manager->create_client([ 'client_name' => sanitize_text_field($_POST['client_name']), 'redirect_uri' => esc_url_raw($_POST['redirect_uri']), 'scope' => sanitize_text_field($_POST['scope'] ?? 'basic'), 'is_public' => !empty($_POST['is_public']), ]); echo '

'; echo 'Client created successfully!
'; echo 'Client ID: ' . esc_html($client['client_id']) . '
'; echo 'Client Secret: ' . esc_html($client['client_secret']) . '
'; echo 'Save the client secret - it won\'t be shown again!'; echo '

'; } catch (Exception $e) { echo '

Error creating client: ' . esc_html($e->getMessage()) . '

'; } } /** * Handle client deletion */ private function handle_client_deletion(string $client_id): void { try { $client_manager = new OAuth2ClientManager(); if ($client_manager->delete_client($client_id)) { echo '

Client deleted successfully!

'; } else { echo '

Failed to delete client.

'; } } catch (Exception $e) { echo '

Error deleting client: ' . esc_html($e->getMessage()) . '

'; } } /** * Get plugin settings */ public function get_setting(string $key, $default = null) { return $this->settings[$key] ?? $default; } }