PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.1.7
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.1.7
3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / labs / oauth.php
ai-engine / labs Last commit date
mcp-core.php 8 months ago mcp-rest.php 1 year ago mcp.conf 1 year ago mcp.js 8 months ago mcp.md 8 months ago mcp.php 8 months ago oauth.php 9 months ago realtime.php 1 year ago
oauth.php
411 lines
1 <?php
2 // NOTE: This OAuth implementation is currently disabled in MCP due to
3 // security issues (unvalidated redirect URIs). The class remains for
4 // reference but is not loaded anywhere. Do not rely on this code until
5 // proper client registration and redirect URI validation is implemented.
6
7 class Meow_MWAI_Labs_OAuth {
8 private $core = null;
9 private $namespace = 'mcp/oauth';
10 private $codes_option = 'mwai_oauth_codes';
11 private $tokens_option = 'mwai_oauth_tokens';
12 private $code_lifetime = 600; // 10 minutes
13 private $token_lifetime = 3600; // 1 hour
14 private $logging = false;
15
16 public function __construct( $core, $logging = false ) {
17 $this->core = $core;
18 $this->logging = $logging;
19
20 add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
21 add_action( 'init', [ $this, 'handle_well_known' ] );
22
23 // Cleanup expired authorization codes and access tokens on a schedule.
24 // When OAuth is enabled, this ensures tokens don't accumulate forever.
25 add_action( 'mwai_cleanup_oauth', [ $this, 'cleanup_expired' ] );
26 if ( !wp_next_scheduled( 'mwai_cleanup_oauth' ) ) {
27 wp_schedule_event( time(), 'hourly', 'mwai_cleanup_oauth' );
28 }
29 }
30
31 public function rest_api_init() {
32 // Authorization endpoint
33 register_rest_route( $this->namespace, '/authorize', [
34 'methods' => 'GET',
35 'callback' => [ $this, 'handle_authorize' ],
36 'permission_callback' => '__return_true',
37 ] );
38
39 // Token endpoint
40 register_rest_route( $this->namespace, '/token', [
41 'methods' => 'POST',
42 'callback' => [ $this, 'handle_token' ],
43 'permission_callback' => '__return_true',
44 ] );
45 }
46
47 // Handle .well-known/oauth-authorization-server
48 public function handle_well_known() {
49 if ( $_SERVER['REQUEST_URI'] === '/.well-known/oauth-authorization-server' ) {
50 if ( $this->logging ) {
51 error_log( '[OAuth] 🌐 Discovery endpoint requested.' );
52 }
53 header( 'Content-Type: application/json' );
54 $base_url = get_site_url();
55 $discovery = [
56 'issuer' => $base_url,
57 'authorization_endpoint' => $base_url . '/wp-json/mcp/oauth/authorize',
58 'token_endpoint' => $base_url . '/wp-json/mcp/oauth/token',
59 'scopes_supported' => [ 'mcp' ],
60 'response_types_supported' => [ 'code' ],
61 'grant_types_supported' => [ 'authorization_code' ],
62 'token_endpoint_auth_methods_supported' => [ 'none' ],
63 'code_challenge_methods_supported' => [ 'S256' ]
64 ];
65 echo json_encode( $discovery, JSON_PRETTY_PRINT );
66 exit;
67 }
68 }
69
70 // Authorization endpoint
71 public function handle_authorize( $request ) {
72 if ( $this->logging ) {
73 error_log( '[OAuth] 🔐 Authorize: ' . $request->get_param( 'client_id' ) );
74 }
75
76 $response_type = $request->get_param( 'response_type' );
77 $client_id = $request->get_param( 'client_id' );
78 $redirect_uri = $request->get_param( 'redirect_uri' );
79 $state = $request->get_param( 'state' );
80 $code_challenge = $request->get_param( 'code_challenge' );
81 $code_challenge_method = $request->get_param( 'code_challenge_method' );
82 $scope = $request->get_param( 'scope' );
83
84 // Validate request
85 if ( $response_type !== 'code' ) {
86 return new WP_Error( 'invalid_request', 'Invalid response_type' );
87 }
88
89 if ( empty( $client_id ) || empty( $redirect_uri ) || empty( $code_challenge ) ) {
90 return new WP_Error( 'invalid_request', 'Missing required parameters' );
91 }
92
93 if ( $code_challenge_method && $code_challenge_method !== 'S256' ) {
94 return new WP_Error( 'invalid_request', 'Only S256 code challenge method is supported' );
95 }
96
97 // Check if user is logged in
98 if ( !is_user_logged_in() ) {
99 // Store OAuth params in session/transient
100 $session_key = 'oauth_' . wp_generate_password( 16, false );
101 set_transient( $session_key, [
102 'client_id' => $client_id,
103 'redirect_uri' => $redirect_uri,
104 'state' => $state,
105 'code_challenge' => $code_challenge,
106 'scope' => $scope ?: 'mcp'
107 ], 600 );
108
109 // Show login form
110 $this->show_login_form( $session_key );
111 exit;
112 }
113
114 // User is logged in, generate authorization code
115 $code = $this->generate_authorization_code(
116 get_current_user_id(),
117 $client_id,
118 $redirect_uri,
119 $code_challenge,
120 $scope ?: 'mcp'
121 );
122
123 // Redirect back with code
124 $redirect_params = [
125 'code' => $code,
126 'state' => $state
127 ];
128
129 $redirect_url = add_query_arg( $redirect_params, $redirect_uri );
130 wp_redirect( $redirect_url );
131 exit;
132 }
133
134 // Token endpoint
135 public function handle_token( $request ) {
136 if ( $this->logging ) {
137 $params = $request->get_params();
138 // Don't log sensitive data like code_verifier
139 $safe_params = $params;
140 if ( isset( $safe_params['code_verifier'] ) ) {
141 $safe_params['code_verifier'] = '[REDACTED]';
142 }
143 error_log( '[OAuth] 🎫 Token exchange for client: ' . $request->get_param( 'client_id' ) );
144 }
145
146 $grant_type = $request->get_param( 'grant_type' );
147 $code = $request->get_param( 'code' );
148 $client_id = $request->get_param( 'client_id' );
149 $redirect_uri = $request->get_param( 'redirect_uri' );
150 $code_verifier = $request->get_param( 'code_verifier' );
151
152 // Validate grant type
153 if ( $grant_type !== 'authorization_code' ) {
154 return new WP_Error( 'unsupported_grant_type', 'Only authorization_code grant type is supported', [ 'status' => 400 ] );
155 }
156
157 // Validate required parameters
158 if ( empty( $code ) || empty( $client_id ) || empty( $redirect_uri ) || empty( $code_verifier ) ) {
159 return new WP_Error( 'invalid_request', 'Missing required parameters', [ 'status' => 400 ] );
160 }
161
162 // Validate authorization code
163 $codes = get_option( $this->codes_option, [] );
164 if ( !isset( $codes[ $code ] ) ) {
165 return new WP_Error( 'invalid_grant', 'Invalid authorization code', [ 'status' => 400 ] );
166 }
167
168 $code_data = $codes[ $code ];
169
170 // Check if code is expired
171 if ( time() > $code_data['expires'] ) {
172 unset( $codes[ $code ] );
173 update_option( $this->codes_option, $codes );
174 return new WP_Error( 'invalid_grant', 'Authorization code has expired', [ 'status' => 400 ] );
175 }
176
177 // Validate client_id and redirect_uri
178 if ( $code_data['client_id'] !== $client_id || $code_data['redirect_uri'] !== $redirect_uri ) {
179 return new WP_Error( 'invalid_grant', 'Invalid client_id or redirect_uri', [ 'status' => 400 ] );
180 }
181
182 // Verify PKCE
183 $verifier_hash = base64_encode( hash( 'sha256', $code_verifier, true ) );
184 $verifier_hash = rtrim( strtr( $verifier_hash, '+/', '-_' ), '=' ); // Base64 URL encoding
185
186 if ( $verifier_hash !== $code_data['code_challenge'] ) {
187 return new WP_Error( 'invalid_grant', 'Invalid code_verifier', [ 'status' => 400 ] );
188 }
189
190 // Code is valid, remove it (one-time use)
191 unset( $codes[ $code ] );
192 update_option( $this->codes_option, $codes );
193
194 // Generate access token
195 $access_token = $this->generate_access_token( $code_data['user_id'], $code_data['scope'] );
196
197 // Return token response
198 return [
199 'access_token' => $access_token,
200 'token_type' => 'Bearer',
201 'expires_in' => $this->token_lifetime,
202 'scope' => $code_data['scope']
203 ];
204 }
205
206 // Generate authorization code
207 private function generate_authorization_code( $user_id, $client_id, $redirect_uri, $code_challenge, $scope ) {
208 if ( $this->logging ) {
209 error_log( '[OAuth] �
210 Auth code generated for user ' . $user_id . '.' );
211 }
212
213 $code = wp_generate_password( 32, false );
214
215 $codes = get_option( $this->codes_option, [] );
216 $codes[ $code ] = [
217 'user_id' => $user_id,
218 'client_id' => $client_id,
219 'redirect_uri' => $redirect_uri,
220 'code_challenge' => $code_challenge,
221 'scope' => $scope,
222 'expires' => time() + $this->code_lifetime
223 ];
224
225 update_option( $this->codes_option, $codes );
226
227 return $code;
228 }
229
230 // Generate access token
231 private function generate_access_token( $user_id, $scope ) {
232 if ( $this->logging ) {
233 error_log( '[OAuth] �
234 Access token generated for user ' . $user_id . '.' );
235 }
236
237 $token = wp_generate_password( 40, false );
238
239 $tokens = get_option( $this->tokens_option, [] );
240 $tokens[ $token ] = [
241 'user_id' => $user_id,
242 'scope' => $scope,
243 'expires' => time() + $this->token_lifetime
244 ];
245
246 update_option( $this->tokens_option, $tokens );
247
248 return $token;
249 }
250
251 // Validate access token
252 public function validate_token( $token ) {
253 $tokens = get_option( $this->tokens_option, [] );
254
255 if ( !isset( $tokens[ $token ] ) ) {
256 return false;
257 }
258
259 $token_data = $tokens[ $token ];
260
261 // Check if expired
262 if ( time() > $token_data['expires'] ) {
263 if ( $this->logging ) {
264 error_log( '[OAuth] ❌ Token validation failed: expired.' );
265 }
266 unset( $tokens[ $token ] );
267 update_option( $this->tokens_option, $tokens );
268 return false;
269 }
270
271 if ( $this->logging ) {
272 error_log( '[OAuth] �
273 Token valid for user ' . $token_data['user_id'] . '.' );
274 }
275
276 return $token_data;
277 }
278
279 // Show login form
280 private function show_login_form( $session_key ) {
281 $login_url = wp_login_url( add_query_arg( 'oauth_session', $session_key, $_SERVER['REQUEST_URI'] ) );
282 ?>
283 <!DOCTYPE html>
284 <html>
285 <head>
286 <title>Login Required</title>
287 <style>
288 body {
289 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
290 display: flex;
291 justify-content: center;
292 align-items: center;
293 height: 100vh;
294 margin: 0;
295 background: #f5f5f5;
296 }
297 .login-container {
298 background: white;
299 padding: 40px;
300 border-radius: 8px;
301 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
302 text-align: center;
303 max-width: 400px;
304 }
305 h2 {
306 margin-top: 0;
307 color: #333;
308 }
309 p {
310 color: #666;
311 margin-bottom: 30px;
312 }
313 .login-button {
314 display: inline-block;
315 background: #0073aa;
316 color: white;
317 padding: 12px 24px;
318 text-decoration: none;
319 border-radius: 4px;
320 font-weight: 500;
321 }
322 .login-button:hover {
323 background: #005a87;
324 }
325 </style>
326 </head>
327 <body>
328 <div class="login-container">
329 <h2>Authorization Required</h2>
330 <p>Please log in to authorize access to your MCP connector.</p>
331 <a href="<?php echo esc_url( $login_url ); ?>" class="login-button">Log In</a>
332 </div>
333 </body>
334 </html>
335 <?php
336 }
337
338 // Handle OAuth callback after login
339 public function handle_oauth_callback() {
340 if ( isset( $_GET['oauth_session'] ) && is_user_logged_in() ) {
341 if ( $this->logging ) {
342 error_log( '[OAuth] 🔄 Callback handling for session.' );
343 }
344 $session_key = sanitize_text_field( $_GET['oauth_session'] );
345 $oauth_params = get_transient( $session_key );
346
347 if ( $oauth_params ) {
348 delete_transient( $session_key );
349
350 // Generate authorization code
351 $code = $this->generate_authorization_code(
352 get_current_user_id(),
353 $oauth_params['client_id'],
354 $oauth_params['redirect_uri'],
355 $oauth_params['code_challenge'],
356 $oauth_params['scope']
357 );
358
359 // Redirect back with code
360 $redirect_params = [
361 'code' => $code,
362 'state' => $oauth_params['state']
363 ];
364
365 $redirect_url = add_query_arg( $redirect_params, $oauth_params['redirect_uri'] );
366 wp_redirect( $redirect_url );
367 exit;
368 }
369 }
370 }
371
372 // Clean up expired tokens and codes
373 public function cleanup_expired() {
374 // Track that this cron started
375 $this->core->track_cron_start( 'mwai_cleanup_oauth' );
376
377 try {
378 if ( $this->logging ) {
379 error_log( '[OAuth] 🧹 Cleaning expired tokens.' );
380 }
381
382 $now = time();
383
384 // Clean codes
385 $codes = get_option( $this->codes_option, [] );
386 foreach ( $codes as $code => $data ) {
387 if ( $now > $data['expires'] ) {
388 unset( $codes[ $code ] );
389 }
390 }
391 update_option( $this->codes_option, $codes );
392
393 // Clean tokens
394 $tokens = get_option( $this->tokens_option, [] );
395 foreach ( $tokens as $token => $data ) {
396 if ( $now > $data['expires'] ) {
397 unset( $tokens[ $token ] );
398 }
399 }
400 update_option( $this->tokens_option, $tokens );
401
402 // Track successful completion
403 $this->core->track_cron_end( 'mwai_cleanup_oauth', 'success' );
404 } catch ( Exception $e ) {
405 // Track failure
406 $this->core->track_cron_end( 'mwai_cleanup_oauth', 'error' );
407 throw $e; // Re-throw to maintain original behavior
408 }
409 }
410 }
411