PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.3.1
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.3.1
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / vendor / wp-media / mcp-oauth / inc / Auth / RevokeEndpoint.php

RevokeEndpoint.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF 2.3.1, at vendor/wp-media/mcp-oauth/inc/Auth/RevokeEndpoint.php

145 lines 4.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Token Revocation Endpoint (RFC 7009).
4 *
5 * Handles POST /oauth/revoke. Accepts an access or refresh JWT, verifies its
6 * signature (ignoring expiry per spec), then deletes the WordPress Application
7 * Password that anchors the session. Because OAuthHttpTransport checks the
8 * Application Password on every request, deletion immediately invalidates all
9 * outstanding tokens for that session — no token store is needed.
10 *
11 * RFC 7009 §2.2 requires HTTP 200 even for unrecognisable or already-revoked
12 * tokens; the response body is an empty JSON object `{}`.
13 */
14
15 declare(strict_types=1);
16
17 namespace WPMedia\MCP\OAuth\Auth;
18
19 use WPMedia\MCP\OAuth\Logging\McpLogger;
20
21 /**
22 * Revoke Endpoint.
23 */
24 class RevokeEndpoint {
25 use ParseBodyTrait;
26
27 /**
28 * Handle the revocation request.
29 *
30 * @return void
31 */
32 public function handle_request(): void {
33 $request_method = isset( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '';
34
35 McpLogger::log(
36 'REVOKE',
37 'revocation request received',
38 [
39 'method' => $request_method,
40 'remote_addr' => isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '',
41 'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '',
42 ]
43 );
44
45 if ( 'POST' !== $request_method ) {
46 McpLogger::log( 'REVOKE', 'rejected: wrong method', [ 'method' => $request_method ] );
47 $this->send_error( 405, 'invalid_request', 'Method not allowed.' );
48 return;
49 }
50
51 $body = $this->parse_body();
52 $token = sanitize_text_field( $body['token'] ?? '' );
53 $client_id_param = esc_url_raw( $body['client_id'] ?? '' );
54
55 if ( '' === $token ) {
56 McpLogger::log( 'REVOKE', 'rejected: missing token parameter' );
57 $this->send_error( 400, 'invalid_request', 'token is required.' );
58 return;
59 }
60
61 $secret = SecretManager::get_secret();
62
63 // Decode without expiry check — RFC 7009 requires revoking even expired tokens.
64 $claims = JWT::decode( $token, $secret, false );
65
66 if ( null === $claims ) {
67 // Invalid signature or malformed token — return success per RFC 7009 §2.2.
68 McpLogger::log( 'REVOKE', 'no-op: token not recognised (invalid signature or format)' );
69 $this->send_success();
70 return;
71 }
72
73 $user_id = (int) ( $claims['sub'] ?? 0 );
74 $app_pass_uuid = (string) ( $claims['app_pass_id'] ?? '' );
75
76 if ( 0 === $user_id || '' === $app_pass_uuid ) {
77 McpLogger::log( 'REVOKE', 'no-op: token missing sub or app_pass_id claims' );
78 $this->send_success();
79 return;
80 }
81
82 // Client binding check (RFC 7009 §2.1): if the caller supplied a client_id and
83 // the token carries one, they must match. A mismatch silently succeeds — no
84 // Application Password is deleted and no information about token ownership is
85 // leaked to the caller.
86 $token_client_id = (string) ( $claims['client_id'] ?? '' );
87 if ( '' !== $client_id_param && '' !== $token_client_id && $client_id_param !== $token_client_id ) {
88 McpLogger::log(
89 'REVOKE',
90 'no-op: client_id mismatch',
91 [
92 'param_client_id' => $client_id_param,
93 'token_client_id' => $token_client_id,
94 'user_id' => $user_id,
95 ]
96 );
97 $this->send_success();
98 return;
99 }
100
101 \WP_Application_Passwords::delete_application_password( $user_id, $app_pass_uuid );
102
103 McpLogger::log(
104 'REVOKE',
105 'session revoked',
106 [
107 'user_id' => $user_id,
108 'app_pass_uuid' => $app_pass_uuid,
109 'client_id' => $token_client_id,
110 'token_type' => isset( $claims['type'] ) && 'refresh' === $claims['type'] ? 'refresh' : 'access',
111 ]
112 );
113
114 $this->send_success();
115 }
116
117 /**
118 * Send a successful revocation response (HTTP 200, empty JSON object).
119 *
120 * @return void
121 */
122 private function send_success(): void {
123 nocache_headers();
124 wp_send_json( new \stdClass() );
125 }
126
127 /**
128 * Send a JSON error response and exit.
129 *
130 * @param int $status HTTP status code.
131 * @param string $error OAuth error code.
132 * @param string $description Optional human-readable description.
133 * @return void
134 */
135 private function send_error( int $status, string $error, string $description = '' ): void {
136 status_header( $status );
137 nocache_headers();
138 $body = [ 'error' => $error ];
139 if ( '' !== $description ) {
140 $body['error_description'] = $description;
141 }
142 wp_send_json( $body );
143 }
144 }
145