wpdb = $wpdb; $this->settings = $settings; } /** * Handle scent authentication requests (OAuth2 endpoints) */ public function handle_scent_request(): void { $endpoint = get_query_var('oauth_endpoint'); // 🔐 SECURITY: Enforce HTTPS for all OAuth2 endpoints $this->enforce_https(); // 🔐 SECURITY: Add comprehensive security headers $this->add_security_headers(); // 🔐 SECURITY: Check for blocked IPs and emergency blocks if ($this->is_client_blocked()) { $this->send_blocked_response(); return; } // 🔐 SECURITY: Validate request integrity if (!$this->validate_request_integrity()) { $this->log_security_violation('Request integrity validation failed'); $this->send_error_response(400, 'invalid_request', 'Request validation failed'); return; } // 🔐 SECURITY: Check rate limits before processing request if (!TigerStyleScent_RateLimiter::check_rate_limit($endpoint)) { TigerStyleScent_RateLimiter::send_rate_limit_response($endpoint); return; } switch ($endpoint) { case 'authorize': $this->handle_territory_authorization(); break; case 'token': $this->handle_scent_token_exchange(); break; case 'introspect': $this->handle_scent_analysis(); break; case 'revoke': $this->handle_scent_revocation(); break; default: $this->send_error_response(404, 'unknown_territory', 'Unknown territory endpoint'); } } /** * Handle territory authorization (OAuth2 authorize endpoint) */ private function handle_territory_authorization(): void { // 🔐 SECURITY: Comprehensive OAuth2 parameter validation $validation_rules = TigerStyleScent_InputValidator::get_oauth2_validation_rules(); $auth_rules = [ 'response_type' => $validation_rules['response_type'], 'client_id' => $validation_rules['client_id'], 'redirect_uri' => $validation_rules['redirect_uri'], 'scope' => $validation_rules['scope'], 'state' => $validation_rules['state'] ]; $validation_result = TigerStyleScent_InputValidator::validate_oauth2_request($_GET, $auth_rules); if (!$validation_result['valid']) { // Log validation failure with detailed context TigerStyleScent_SecurityLogger::log_security_event( TigerStyleScent_SecurityLogger::EVENT_VALIDATION_FAILURE, TigerStyleScent_SecurityLogger::SEVERITY_MEDIUM, 'Authorization endpoint validation failed', [ 'errors' => $validation_result['errors'], 'warnings' => $validation_result['warnings'], 'raw_input' => $_GET ] ); $this->send_error_response(400, 'invalid_request', 'Invalid request parameters'); return; } // Extract validated parameters $response_type = $validation_result['sanitized']['response_type']; $client_id = $validation_result['sanitized']['client_id']; $redirect_uri = $validation_result['sanitized']['redirect_uri']; $scope = $validation_result['sanitized']['scope'] ?? 'basic'; $state = $validation_result['sanitized']['state'] ?? ''; // Validate required parameters for territory access if (empty($response_type) || empty($client_id)) { $this->send_error_response(400, 'invalid_request', 'Missing required territory parameters'); return; } // Only support authorization code (territory code) flow if ($response_type !== 'code') { $this->send_error_response(400, 'unsupported_response_type', 'Only territory code flow is supported'); return; } // Validate client scent credentials $client = $this->recognize_client_scent($client_id); if (!$client) { $this->send_error_response(400, 'invalid_client', 'Unknown client scent'); return; } // Validate redirect URI for safe territory return if (!empty($redirect_uri) && !$this->validate_territory_return_uri($client, $redirect_uri)) { $this->send_error_response(400, 'invalid_request', 'Invalid territory return URI'); return; } // Check if user is authenticated (has proper scent) if (!is_user_logged_in()) { // Redirect to WordPress login with return URL $login_url = wp_login_url(add_query_arg($_GET, admin_url('admin.php'))); wp_redirect($login_url); exit; } // Handle POST request (user authorization decision) if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (isset($_POST['authorize']) && wp_verify_nonce($_POST['_wpnonce'], 'tigerstyle_scent_authorize')) { $this->grant_territory_access($client_id, $redirect_uri, $scope, $state); } elseif (isset($_POST['deny'])) { $this->deny_territory_access($redirect_uri, $state); } return; } // Show territory authorization form $this->show_territory_authorization_form([ 'client' => $client, 'scope' => $scope, 'state' => $state, 'redirect_uri' => $redirect_uri, 'client_id' => $client_id ]); } /** * Handle scent token exchange (OAuth2 token endpoint) */ private function handle_scent_token_exchange(): void { // Only accept POST requests for scent token exchange if ($_SERVER['REQUEST_METHOD'] !== 'POST') { $this->send_error_response(405, 'method_not_allowed', 'Scent exchange requires POST'); return; } $grant_type = sanitize_text_field($_POST['grant_type'] ?? ''); switch ($grant_type) { case 'authorization_code': $this->handle_territory_code_grant(); break; case 'refresh_token': $this->handle_scent_refresh_grant(); break; case 'client_credentials': $this->handle_client_scent_credentials_grant(); break; default: $this->send_error_response(400, 'unsupported_grant_type', 'Unsupported scent grant type'); } } /** * Handle territory code grant (authorization code flow) */ private function handle_territory_code_grant(): void { // Extract scent parameters $code = sanitize_text_field($_POST['code'] ?? ''); $client_id = sanitize_text_field($_POST['client_id'] ?? ''); $redirect_uri = esc_url_raw($_POST['redirect_uri'] ?? ''); $code_verifier = sanitize_text_field($_POST['code_verifier'] ?? ''); // Get client scent credentials from Authorization header or POST $client_credentials = $this->extract_client_scent_credentials(); if ($client_credentials) { $client_id = $client_credentials['client_id']; $client_secret = $client_credentials['client_secret']; } else { $client_secret = sanitize_text_field($_POST['client_secret'] ?? ''); } // Validate required scent parameters if (empty($code) || empty($client_id)) { $this->send_error_response(400, 'invalid_request', 'Missing required scent parameters'); return; } // Get and validate territory code $territory_code = $this->get_territory_code($code); if (!$territory_code) { $this->send_error_response(400, 'invalid_grant', 'Invalid territory code'); return; } // Check if territory code has expired if (strtotime($territory_code['expires']) < time()) { $this->delete_territory_code($code); $this->send_error_response(400, 'invalid_grant', 'Territory code expired'); return; } // Validate client scent match if ($territory_code['client_id'] !== $client_id) { $this->send_error_response(400, 'invalid_client', 'Client scent mismatch'); return; } // Get client details for scent recognition $client = $this->recognize_client_scent($client_id); if (!$client) { // 🔐 SECURITY: Log client authentication failure TigerStyleScent_SecurityLogger::log_security_event( TigerStyleScent_SecurityLogger::EVENT_AUTH_FAILURE, TigerStyleScent_SecurityLogger::SEVERITY_MEDIUM, 'Unknown client attempted authentication', ['client_id' => $client_id, 'endpoint' => 'token'], $client_id ); $this->send_error_response(400, 'invalid_client', 'Invalid client scent'); return; } // Validate client secret for confidential clients (strong scent verification) if (!$client['is_public']) { if (empty($client_secret) || !password_verify($client_secret, $client['client_secret'])) { // 🔐 SECURITY: Log client secret failure TigerStyleScent_SecurityLogger::log_security_event( TigerStyleScent_SecurityLogger::EVENT_AUTH_FAILURE, TigerStyleScent_SecurityLogger::SEVERITY_HIGH, 'Invalid client secret provided', ['client_id' => $client_id, 'endpoint' => 'token', 'is_public' => false], $client_id ); $this->send_error_response(401, 'invalid_client', 'Invalid scent credentials'); return; } } // Validate territory return URI if (!empty($redirect_uri) && $territory_code['redirect_uri'] !== $redirect_uri) { $this->send_error_response(400, 'invalid_grant', 'Territory return URI mismatch'); return; } // Generate scent tokens $scent_token = $this->generate_scent_token($client_id, $territory_code['user_id'], $territory_code['scope']); $refresh_scent = $this->generate_refresh_scent($client_id, $territory_code['user_id'], $territory_code['scope']); // 🔐 SECURITY: Log successful token issuance TigerStyleScent_SecurityLogger::log_security_event( TigerStyleScent_SecurityLogger::EVENT_TOKEN_ISSUED, TigerStyleScent_SecurityLogger::SEVERITY_LOW, 'OAuth2 tokens successfully issued', [ 'grant_type' => 'authorization_code', 'scope' => $territory_code['scope'], 'token_length' => strlen($scent_token), 'refresh_token_issued' => !empty($refresh_scent) ], $client_id, $territory_code['user_id'] ); // Delete territory code (one-time use like a scent trail) $this->delete_territory_code($code); // Send scent token response $this->send_scent_token_response($scent_token, $refresh_scent, $territory_code['scope']); } /** * Handle scent analysis (OAuth2 introspect endpoint) */ private function handle_scent_analysis(): void { // 🔐 CSRF Protection for POST requests if ($_SERVER['REQUEST_METHOD'] === 'POST') { $nonce = sanitize_text_field($_POST['_wpnonce'] ?? ''); if (!wp_verify_nonce($nonce, 'tigerstyle_scent_introspect')) { $this->send_error_response(403, 'invalid_request', 'CSRF token required'); return; } } $token = sanitize_text_field($_POST['token'] ?? ''); if (empty($token)) { $this->send_error_response(400, 'invalid_request', 'Missing scent token for analysis'); return; } $scent_data = $this->analyze_scent_token($token); header('Content-Type: application/json'); echo json_encode($scent_data); exit; } /** * Recognize client by their unique scent (get client data) */ private function recognize_client_scent(string $client_id): ?array { // Query WordPress posts to find OAuth2 client by scent signature $posts = get_posts([ 'post_type' => 'oauth2_client', 'meta_key' => 'client_id', 'meta_value' => $client_id, 'posts_per_page' => 1, 'post_status' => 'publish' ]); if (empty($posts)) { return null; } $post = $posts[0]; $client_scent_data = [ 'client_id' => get_post_meta($post->ID, 'client_id', true), 'client_secret' => get_post_meta($post->ID, 'client_secret', true), 'redirect_uris' => get_post_meta($post->ID, 'redirect_uris', true), 'grant_types' => get_post_meta($post->ID, 'grant_types', true), 'scope' => get_post_meta($post->ID, 'scope', true), 'is_public' => (bool)get_post_meta($post->ID, 'is_public', true), 'name' => $post->post_title, 'client_name' => $post->post_title // Add client_name for scent recognition display ]; return $client_scent_data; } /** * Validate territory return URI for safe scent trail */ private function validate_territory_return_uri(array $client, string $redirect_uri): bool { $redirect_uris = $client['redirect_uris'] ?? ''; if (empty($redirect_uris)) { return false; } $allowed_uris = explode(',', $redirect_uris); return in_array($redirect_uri, array_map('trim', $allowed_uris)); } /** * Generate unique scent token (access token) */ private function generate_scent_token(string $client_id, int $user_id, string $scope): string { // 🔐 SECURITY: Maximum entropy token generation (48 bytes = 384 bits) $scent_token = $this->generate_secure_token(48); $expires = date('Y-m-d H:i:s', time() + 3600); // 1 hour scent trail // Store scent token in territory database $this->wpdb->insert( $this->wpdb->prefix . 'oauth_access_tokens', [ 'access_token' => $scent_token, 'client_id' => $client_id, 'user_id' => $user_id, 'expires' => $expires, 'scope' => $scope ] ); return $scent_token; } /** * Generate refresh scent for long-term authentication */ private function generate_refresh_scent(string $client_id, int $user_id, string $scope): string { // 🔐 SECURITY: Higher entropy for refresh tokens (64 bytes = 512 bits) $refresh_scent = $this->generate_secure_token(64); $expires = date('Y-m-d H:i:s', time() + (30 * 24 * 3600)); // 30 day scent memory // Store refresh scent in territory database $this->wpdb->insert( $this->wpdb->prefix . 'oauth_refresh_tokens', [ 'refresh_token' => $refresh_scent, 'client_id' => $client_id, 'user_id' => $user_id, 'expires' => $expires, 'scope' => $scope ] ); return $refresh_scent; } /** * Analyze scent token validity and return data */ public function analyze_scent_token(string $token): ?array { $result = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->prefix}oauth_access_tokens WHERE access_token = %s", $token ), ARRAY_A ); if (!$result) { return ['active' => false]; } // Check if scent has expired if (strtotime($result['expires']) < time()) { // Clean up expired scent $this->wpdb->delete( $this->wpdb->prefix . 'oauth_access_tokens', ['access_token' => $token] ); return ['active' => false]; } return [ 'active' => true, 'client_id' => $result['client_id'], 'user_id' => (int)$result['user_id'], 'scope' => $result['scope'], 'exp' => strtotime($result['expires']) ]; } /** * Show territory authorization form with cat-themed UI */ private function show_territory_authorization_form(array $params): void { // Set content type header('Content-Type: text/html; charset=utf-8'); // TigerStyle Scent authorization form with cat theming ?>
The application "" wants to recognize your scent.