PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.3.2
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.3.2
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 / AuthorizeCallback.php

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

165 lines 5.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Post-login Authorization Callback — Consent Screen.
4 *
5 * Handles GET /oauth/authorize-callback — called by WordPress after the user
6 * authenticates. Looks up the state transient (which already carries the
7 * validated client display data captured at authorize time), refreshes the
8 * transient TTL to give the user time to read the screen, then renders a
9 * standalone HTML consent page.
10 *
11 * The user's Allow/Deny choice is handled by ConsentEndpoint (POST /oauth/consent).
12 */
13
14 declare(strict_types=1);
15
16 namespace WPMedia\MCP\OAuth\Auth;
17
18 use WPMedia\MCP\OAuth\Logging\McpLogger;
19 use WPMedia\MCP\OAuth\Views\Render;
20
21 /**
22 * Authorize Callback — renders the consent screen.
23 */
24 class AuthorizeCallback {
25 /**
26 * How long (seconds) the state transient lives once the consent screen is shown.
27 * Replaces the original 60 s authorize-window TTL so the user has time to decide.
28 */
29 const CONSENT_TTL = 300;
30
31 /**
32 * View renderer.
33 *
34 * @var Render
35 */
36 private Render $render;
37
38 /**
39 * Constructor.
40 *
41 * @param Render $render View renderer.
42 */
43 public function __construct( Render $render ) {
44 $this->render = $render;
45 }
46
47 /**
48 * Handle the post-login callback — show the consent screen.
49 *
50 * @return void
51 */
52 public function handle_request(): void {
53 $state = sanitize_text_field( wp_unslash( $_GET['state'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- OAuth state token echoed back by the login redirect, not a WP nonce-protected form.
54
55 McpLogger::log(
56 'CALLBACK',
57 'authorize-callback received',
58 [
59 'is_logged_in' => is_user_logged_in() ? 'yes' : 'no',
60 'user_id' => is_user_logged_in() ? get_current_user_id() : 0,
61 'has_state' => '' !== $state ? 'yes' : 'no',
62 'remote_addr' => isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '',
63 ]
64 );
65
66 if ( ! is_user_logged_in() ) {
67 McpLogger::log( 'CALLBACK', 'rejected: user not logged in' );
68 wp_die( esc_html__( 'You must be logged in to authorise an MCP session.', 'mcp-oauth' ), esc_html__( 'OAuth Error', 'mcp-oauth' ), [ 'response' => 401 ] );
69 }
70
71 if ( '' === $state ) {
72 McpLogger::log( 'CALLBACK', 'rejected: missing state' );
73 wp_die( esc_html__( 'Missing state parameter.', 'mcp-oauth' ), esc_html__( 'OAuth Error', 'mcp-oauth' ), [ 'response' => 400 ] );
74 }
75
76 $state_key = 'mcp_oauth_state_' . $state;
77 $state_data = get_transient( $state_key );
78
79 if ( false === $state_data || ! is_array( $state_data ) ) {
80 McpLogger::log( 'CALLBACK', 'rejected: state transient not found or expired', [ 'state' => $state ] );
81 wp_die( esc_html__( 'Invalid or expired state. Please restart the authorization flow.', 'mcp-oauth' ), esc_html__( 'OAuth Error', 'mcp-oauth' ), [ 'response' => 400 ] );
82 }
83
84 // Client display data was captured and validated by AuthorizeEndpoint and
85 // stored in the state transient. Reusing it avoids a second CIMD lookup —
86 // no network round-trip, and no spurious failure if the publisher is
87 // briefly unreachable between login and consent.
88 $client_id = (string) ( $state_data['client_id'] ?? '' );
89 $client = [
90 'client_id' => $client_id,
91 'client_name' => (string) ( $state_data['client_name'] ?? '' ),
92 'client_uri' => (string) ( $state_data['client_uri'] ?? '' ),
93 'verified' => ! empty( $state_data['verified'] ),
94 'publisher' => (string) ( $state_data['publisher'] ?? '' ),
95 ];
96
97 // Refresh state TTL — the user now has CONSENT_TTL seconds to decide.
98 set_transient( $state_key, $state_data, self::CONSENT_TTL );
99
100 McpLogger::log(
101 'CALLBACK',
102 'showing consent screen',
103 [
104 'user_id' => get_current_user_id(),
105 'client_id' => $client_id,
106 'client_name' => $client['client_name'],
107 'verified' => $client['verified'] ? 'yes' : 'no',
108 ]
109 );
110
111 $this->render_consent_screen( $state, $client );
112 }
113
114 /**
115 * Assemble the consent-screen view model and render it.
116 *
117 * @param string $state OAuth state token.
118 * @param array<string, mixed> $client Resolved CIMD client record.
119 * @return void
120 */
121 private function output_consent_screen( string $state, array $client ): void {
122 nocache_headers();
123
124 // Escaped at each output site inside the template. URLs are esc_url'd
125 // here since they're only ever emitted into href/action attributes.
126 $client_name = (string) ( $client['client_name'] ?? '' );
127 $client_id = esc_url( (string) ( $client['client_id'] ?? '' ) );
128 $client_uri = esc_url( (string) ( $client['client_uri'] ?? '' ) );
129 $verified = ! empty( $client['verified'] );
130 $publisher = (string) ( $client['publisher'] ?? '' );
131 $site_name = (string) get_bloginfo( 'name' );
132 $consent_url = esc_url( home_url( '/oauth/consent' ) ); // Rewrite endpoint: home_url(), not get_site_url().
133
134 // The display name links to client_uri if available, otherwise client_id.
135 $display_href = '' !== $client_uri ? $client_uri : $client_id;
136
137 $this->render->view(
138 'consent-screen',
139 [
140 'state' => $state,
141 'client_name' => $client_name,
142 'client_id' => $client_id,
143 'client_uri' => $client_uri,
144 'verified' => $verified,
145 'publisher' => $publisher,
146 'site_name' => $site_name,
147 'consent_url' => $consent_url,
148 'display_href' => $display_href,
149 ]
150 );
151 }
152
153 /**
154 * Render the consent screen, then terminate the request.
155 *
156 * @param string $state OAuth state token.
157 * @param array<string, mixed> $client Resolved CIMD client record.
158 * @return void
159 */
160 private function render_consent_screen( string $state, array $client ): void {
161 $this->output_consent_screen( $state, $client );
162 exit;
163 }
164 }
165