PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.2
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.2
1.3.5 1.3.4 1.3.3 1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 All 31 releases
← All changes | includes/modules/Mcp/Mcp_OAuth.php +13 -61 1.3.4 → 1.3.2 View file →
@@ -8,17 +8,12 @@
8 8 * authorization-code + PKCE flow themselves. See the security contract and
9 9 * the end-to-end flow notes below.
10 10 *
11 11 * Flow: unauthenticated MCP call -> 401 + WWW-Authenticate (Mcp_Server) ->
12 - * client fetches /.well-known/oauth-protected-resource/xspeed/mcp +
13 - * /.well-known/oauth-authorization-server/xspeed/mcp -> dynamic registration
14 - * (RFC 7591) -> /authorize (admin consent + PKCE) -> /token (code + verifier
15 - * -> access + refresh) -> MCP calls with `Authorization: Bearer <access>`
16 - * validated by validate_token(). Both canonical identifiers -- issuer and
17 - * resource -- are the MCP endpoint URL, which is what puts the documents
18 - * under that path rather than at the contested site root (#266). The root
19 - * URLs are still answered, with the legacy host-only issuer, on sites where
20 - * no other plugin has claimed them -- see legacy_issuer().
12 + * client fetches /.well-known/oauth-protected-resource + oauth-authorization-
13 + * server -> dynamic registration (RFC 7591) -> /authorize (admin consent +
14 + * PKCE) -> /token (code + verifier -> access + refresh) -> MCP calls with
15 + * `Authorization: Bearer <access>` validated by validate_token().
21 16 *
22 17 * Security contract:
23 18 * - PKCE S256 REQUIRED (OAuth 2.1 public clients); codes are single-use,
24 19 * 60 s TTL, bound to client_id + redirect_uri + challenge.
@@ -128,45 +123,13 @@
128 123 private static $writing = false;
129 124
130 125 // -- URLs ------------------------------------------------------------
131 126
132 - /**
133 - * The OAuth issuer identifier -- the MCP endpoint URL, identical to
134 - * resource().
135 - *
136 - * RFC 8414 §2 allows an issuer to carry a path, and §3.1 then moves its
137 - * metadata to /.well-known/oauth-authorization-server/xspeed/mcp, a URL
138 - * only this plugin answers. A bare-host issuer put the document at the
139 - * site root, which every other MCP-serving plugin on the same site also
140 - * wants, and WordPress hands that URL to whichever rewrite rule happens
141 - * to sit first in the table.
142 - *
143 - * Nothing stored carries the issuer -- access and refresh tokens are
144 - * opaque random strings, client records hold redirect_uris/name/created
145 - * -- so changing it invalidates no grant. A client that re-discovers
146 - * simply registers again and asks the admin for consent once more.
147 - * (#266)
148 - */
127 + /** Base site URL used as the OAuth issuer (no trailing slash). */
149 128 public static function issuer(): string {
150 - return self::resource();
129 + return untrailingslashit( home_url() );
151 130 }
152 131
153 - /**
154 - * The host-only issuer earlier builds used, still served at the bare
155 - * /.well-known/oauth-* URLs when no other plugin has claimed them.
156 - *
157 - * RFC 8414 §3.3 makes a client reject a document whose `issuer` is not
158 - * the value it inserted into the URL it fetched, and a client that
159 - * fetched the ROOT document inserted nothing -- it derived that URL from
160 - * `https://site`. Stamping the path issuer there would be the same RFC
161 - * violation this change set out to remove, pointed the other way. So the
162 - * two locations carry two identities, each self-consistent, and a client
163 - * ends up on whichever one it asked for. (#266)
164 - */
165 - public static function legacy_issuer(): string {
166 - return untrailingslashit( home_url( '/' ) );
167 - }
168 -
169 132 /** The protected resource identifier -- the MCP endpoint URL. */
170 133 public static function resource(): string {
171 134 return Mcp_Pairing::site_endpoint();
172 135 }
@@ -177,17 +140,17 @@
177 140 * round-trip — a REST route would see the cookie without a nonce and
178 141 * treat the admin as logged-out, looping back to login.
179 142 */
180 143 public static function authorize_url(): string {
181 - return Mcp_Pairing::absolute( home_url( '/xspeed/authorize' ) );
144 + return home_url( '/xspeed/authorize' );
182 145 }
183 146
184 147 public static function token_url(): string {
185 - return Mcp_Pairing::absolute( rest_url( 'xspeed/v1/mcp/oauth/token' ) );
148 + return rest_url( 'xspeed/v1/mcp/oauth/token' );
186 149 }
187 150
188 151 public static function register_url(): string {
189 - return Mcp_Pairing::absolute( rest_url( 'xspeed/v1/mcp/oauth/register' ) );
152 + return rest_url( 'xspeed/v1/mcp/oauth/register' );
190 153 }
191 154
192 155 // -- Discovery documents (RFC 8414 / RFC 9728) -----------------------
193 156
@@ -194,20 +157,14 @@
194 157 /**
195 158 * RFC 9728 protected-resource metadata -- tells the client which
196 159 * authorization server(s) protect the MCP endpoint (this site).
197 160 *
198 - * @param string|null $issuer The authorization server to name. Defaults
199 - * to the canonical path issuer; the root
200 - * /.well-known/ location passes the legacy
201 - * host-only one, so that the AS document a
202 - * client goes on to fetch is the one served
203 - * at the URL that issuer derives.
204 161 * @return array<string,mixed>
205 162 */
206 - public static function protected_resource_metadata( ?string $issuer = null ): array {
163 + public static function protected_resource_metadata(): array {
207 164 return array(
208 165 'resource' => self::resource(),
209 - 'authorization_servers' => array( $issuer ?? self::issuer() ),
166 + 'authorization_servers' => array( self::issuer() ),
210 167 'scopes_supported' => self::SUPPORTED_SCOPES,
211 168 'bearer_methods_supported' => array( 'header' ),
212 169 );
213 170 }
@@ -216,18 +173,13 @@
216 173 * RFC 8414 authorization-server metadata -- the endpoint map + the
217 174 * capabilities we actually implement (auth-code grant, PKCE S256,
218 175 * dynamic registration, refresh tokens).
219 176 *
220 - * @param string|null $issuer Which identity this copy of the document
221 - * speaks for; see protected_resource_metadata().
222 - * Every endpoint URL below is identical either
223 - * way, which is why a client that switches
224 - * identities keeps its cached endpoints.
225 177 * @return array<string,mixed>
226 178 */
227 - public static function authorization_server_metadata( ?string $issuer = null ): array {
179 + public static function authorization_server_metadata(): array {
228 180 return array(
229 - 'issuer' => $issuer ?? self::issuer(),
181 + 'issuer' => self::issuer(),
230 182 'authorization_endpoint' => self::authorize_url(),
231 183 'token_endpoint' => self::token_url(),
232 184 'registration_endpoint' => self::register_url(),
233 185 'scopes_supported' => self::SUPPORTED_SCOPES,