wpdb = $wpdb; } /** * Create a new OAuth2 client */ public function create_client(array $client_data): array { // Validate required fields $required_fields = ['client_name', 'redirect_uri']; foreach ($required_fields as $field) { if (empty($client_data[$field])) { throw new \InvalidArgumentException("Missing required field: {$field}"); } } // Generate client ID and secret $client_id = $this->generate_client_id(); $client_secret = $this->generate_client_secret(); // Sanitize and prepare data $insert_data = [ 'client_id' => $client_id, 'client_secret' => password_hash($client_secret, PASSWORD_ARGON2ID), // 🔐 SECURITY: Use proper password hashing 'client_name' => sanitize_text_field($client_data['client_name']), 'redirect_uri' => esc_url_raw($client_data['redirect_uri']), 'grant_types' => sanitize_text_field($client_data['grant_types'] ?? 'authorization_code'), 'scope' => sanitize_text_field($client_data['scope'] ?? 'basic'), 'user_id' => get_current_user_id(), 'is_public' => !empty($client_data['is_public']) ? 1 : 0, ]; // Insert into database $result = $this->wpdb->insert( $this->wpdb->prefix . 'oauth2_clients', $insert_data, ['%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d'] ); if ($result === false) { throw new \Exception('Failed to create OAuth2 client'); } // Return client information (including plain text secret) return [ 'client_id' => $client_id, 'client_secret' => $client_secret, // Return plain text secret (only time it's available) 'client_name' => $insert_data['client_name'], 'redirect_uri' => $insert_data['redirect_uri'], 'grant_types' => $insert_data['grant_types'], 'scope' => $insert_data['scope'], 'is_public' => (bool) $insert_data['is_public'], ]; } /** * Get all clients for current user */ public function get_user_clients(int $user_id = null): array { if ($user_id === null) { $user_id = get_current_user_id(); } $results = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT client_id, client_name, redirect_uri, grant_types, scope, is_public, created_at FROM {$this->wpdb->prefix}oauth2_clients WHERE user_id = %d ORDER BY created_at DESC", $user_id ), ARRAY_A ); return $results ?: []; } /** * Get client by ID */ public function get_client(string $client_id): ?array { $result = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->prefix}oauth2_clients WHERE client_id = %s", $client_id ), ARRAY_A ); return $result ?: null; } /** * Delete client */ public function delete_client(string $client_id, int $user_id = null): bool { if ($user_id === null) { $user_id = get_current_user_id(); } // Only allow deletion by client owner or admin if (!current_user_can('manage_options')) { $client = $this->get_client($client_id); if (!$client || $client['user_id'] != $user_id) { return false; } } // Delete associated tokens and codes first $this->cleanup_client_data($client_id); // Delete client $result = $this->wpdb->delete( $this->wpdb->prefix . 'oauth2_clients', ['client_id' => $client_id], ['%s'] ); return $result !== false; } /** * Clean up all data associated with a client */ private function cleanup_client_data(string $client_id): void { // Delete access tokens $this->wpdb->delete( $this->wpdb->prefix . 'oauth2_access_tokens', ['client_id' => $client_id], ['%s'] ); // Delete refresh tokens $this->wpdb->delete( $this->wpdb->prefix . 'oauth2_refresh_tokens', ['client_id' => $client_id], ['%s'] ); // Delete authorization codes $this->wpdb->delete( $this->wpdb->prefix . 'oauth2_authorization_codes', ['client_id' => $client_id], ['%s'] ); } /** * Generate client ID */ private function generate_client_id(): string { return 'client_' . bin2hex(random_bytes(16)); } /** * Generate client secret */ private function generate_client_secret(): string { return bin2hex(random_bytes(32)); } /** * Update client information */ public function update_client(string $client_id, array $update_data, int $user_id = null): bool { if ($user_id === null) { $user_id = get_current_user_id(); } // Only allow update by client owner or admin if (!current_user_can('manage_options')) { $client = $this->get_client($client_id); if (!$client || $client['user_id'] != $user_id) { return false; } } // Prepare update data $allowed_fields = ['client_name', 'redirect_uri', 'grant_types', 'scope', 'is_public']; $update_values = []; $update_format = []; foreach ($allowed_fields as $field) { if (isset($update_data[$field])) { switch ($field) { case 'client_name': case 'grant_types': case 'scope': $update_values[$field] = sanitize_text_field($update_data[$field]); $update_format[] = '%s'; break; case 'redirect_uri': $update_values[$field] = esc_url_raw($update_data[$field]); $update_format[] = '%s'; break; case 'is_public': $update_values[$field] = !empty($update_data[$field]) ? 1 : 0; $update_format[] = '%d'; break; } } } if (empty($update_values)) { return false; } $update_values['updated_at'] = current_time('mysql'); $update_format[] = '%s'; $result = $this->wpdb->update( $this->wpdb->prefix . 'oauth2_clients', $update_values, ['client_id' => $client_id], $update_format, ['%s'] ); return $result !== false; } /** * Get statistics for admin dashboard */ public function get_statistics(): array { $stats = []; // Total clients $stats['total_clients'] = $this->wpdb->get_var( "SELECT COUNT(*) FROM {$this->wpdb->prefix}oauth2_clients" ); // Active tokens (non-expired) $stats['active_tokens'] = $this->wpdb->get_var( "SELECT COUNT(*) FROM {$this->wpdb->prefix}oauth2_access_tokens WHERE expires > NOW()" ); // Today's authorizations $stats['todays_authorizations'] = $this->wpdb->get_var( "SELECT COUNT(*) FROM {$this->wpdb->prefix}oauth2_authorization_codes WHERE DATE(created_at) = CURDATE()" ); // Client types $stats['public_clients'] = $this->wpdb->get_var( "SELECT COUNT(*) FROM {$this->wpdb->prefix}oauth2_clients WHERE is_public = 1" ); $stats['confidential_clients'] = $this->wpdb->get_var( "SELECT COUNT(*) FROM {$this->wpdb->prefix}oauth2_clients WHERE is_public = 0" ); return array_map('intval', $stats); } /** * Clean up expired tokens and codes */ public function cleanup_expired_data(): array { $cleanup_stats = []; // Clean expired access tokens $cleanup_stats['access_tokens'] = $this->wpdb->query( "DELETE FROM {$this->wpdb->prefix}oauth2_access_tokens WHERE expires < NOW()" ); // Clean expired refresh tokens $cleanup_stats['refresh_tokens'] = $this->wpdb->query( "DELETE FROM {$this->wpdb->prefix}oauth2_refresh_tokens WHERE expires < NOW()" ); // Clean expired authorization codes $cleanup_stats['authorization_codes'] = $this->wpdb->query( "DELETE FROM {$this->wpdb->prefix}oauth2_authorization_codes WHERE expires < NOW()" ); return $cleanup_stats; } }