PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | jetpack_vendor/automattic/jetpack-connection/src/class-authorize-json-api.php +281 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,281 @@
1 +<?php
2 +/**
3 + * Authorize_Json_Api handler class.
4 + * Used to handle connections via JSON API.
5 + * Ported from the Jetpack class.
6 + *
7 + * @since 2.7.6 Ported from the Jetpack class.
8 + *
9 + * @package automattic/jetpack-connection
10 + */
11 +
12 +namespace Automattic\Jetpack\Connection;
13 +
14 +use Automattic\Jetpack\Redirect;
15 +use Automattic\Jetpack\Status\Host;
16 +use Jetpack_Options;
17 +
18 +/**
19 + * Authorize_Json_Api handler class.
20 + */
21 +class Authorize_Json_Api {
22 + /**
23 + * Verified data for JSON authorization request
24 + *
25 + * @since 2.7.6
26 + *
27 + * @var array
28 + */
29 + public $json_api_authorization_request = array();
30 +
31 + /**
32 + * Verifies the request by checking the signature
33 + *
34 + * @since jetpack-4.6.0 Method was updated to use `$_REQUEST` instead of `$_GET` and `$_POST`. Method also updated to allow
35 + * passing in an `$environment` argument that overrides `$_REQUEST`. This was useful for integrating with SSO.
36 + * @since 2.7.6 Ported from Jetpack to the Connection package.
37 + *
38 + * @param null|array $environment Value to override $_REQUEST.
39 + *
40 + * @return void
41 + */
42 + public function verify_json_api_authorization_request( $environment = null ) {
43 + $environment = $environment === null
44 + ? $_REQUEST // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce verification handled later in function and request data are 1) used to verify a cryptographic signature of the request data and 2) sanitized later in function.
45 + : $environment;
46 +
47 + if ( ! isset( $environment['token'] ) ) {
48 + wp_die( esc_html__( 'You must connect your Jetpack plugin to WordPress.com to use this feature.', 'jetpack-connection' ) );
49 + }
50 +
51 + list( $env_token,, $env_user_id ) = explode( ':', $environment['token'] );
52 + $token = ( new Tokens() )->get_access_token( (int) $env_user_id, $env_token );
53 + if ( ! $token || empty( $token->secret ) ) {
54 + wp_die( esc_html__( 'You must connect your Jetpack plugin to WordPress.com to use this feature.', 'jetpack-connection' ) );
55 + }
56 +
57 + $die_error = __( 'Someone may be trying to trick you into giving them access to your site. Or it could be you just encountered a bug :). Either way, please close this window.', 'jetpack-connection' );
58 +
59 + // Host has encoded the request URL, probably as a result of a bad http => https redirect.
60 + if (
61 + preg_match( '/https?%3A%2F%2F/i', esc_url_raw( wp_unslash( $_GET['redirect_to'] ) ) ) > 0 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- no site changes, we're erroring out.
62 + ) {
63 + /**
64 + * Jetpack authorisation request Error.
65 + *
66 + * @since jetpack-7.5.0
67 + */
68 + do_action( 'jetpack_verify_api_authorization_request_error_double_encode' );
69 + $die_error = sprintf(
70 + /* translators: %s is a URL */
71 + __( 'Your site is incorrectly double-encoding redirects from http to https. This is preventing Jetpack from authenticating your connection. Please visit our <a href="%s">support page</a> for details about how to resolve this.', 'jetpack-connection' ),
72 + esc_url( Redirect::get_url( 'jetpack-support-double-encoding' ) )
73 + );
74 + }
75 +
76 + $jetpack_signature = new \Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) );
77 +
78 + if ( isset( $environment['jetpack_json_api_original_query'] ) ) {
79 + $signature = $jetpack_signature->sign_request(
80 + $environment['token'],
81 + $environment['timestamp'],
82 + $environment['nonce'],
83 + '',
84 + 'GET',
85 + $environment['jetpack_json_api_original_query'],
86 + null,
87 + true
88 + );
89 + } else {
90 + $signature = $jetpack_signature->sign_current_request(
91 + array(
92 + 'body' => null,
93 + 'method' => 'GET',
94 + )
95 + );
96 + }
97 +
98 + if ( ! $signature ) {
99 + wp_die(
100 + wp_kses(
101 + $die_error,
102 + array(
103 + 'a' => array(
104 + 'href' => array(),
105 + ),
106 + )
107 + )
108 + );
109 + } elseif ( is_wp_error( $signature ) ) {
110 + wp_die(
111 + wp_kses(
112 + $die_error,
113 + array(
114 + 'a' => array(
115 + 'href' => array(),
116 + ),
117 + )
118 + )
119 + );
120 + } elseif ( ! hash_equals( $signature, $environment['signature'] ) ) {
121 + if ( is_ssl() ) {
122 + // If we signed an HTTP request on the Jetpack Servers, but got redirected to HTTPS by the local blog, check the HTTP signature as well.
123 + $signature = $jetpack_signature->sign_current_request(
124 + array(
125 + 'scheme' => 'http',
126 + 'body' => null,
127 + 'method' => 'GET',
128 + )
129 + );
130 + if ( ! $signature || is_wp_error( $signature ) || ! hash_equals( $signature, $environment['signature'] ) ) {
131 + wp_die(
132 + wp_kses(
133 + $die_error,
134 + array(
135 + 'a' => array(
136 + 'href' => array(),
137 + ),
138 + )
139 + )
140 + );
141 + }
142 + } else {
143 + wp_die(
144 + wp_kses(
145 + $die_error,
146 + array(
147 + 'a' => array(
148 + 'href' => array(),
149 + ),
150 + )
151 + )
152 + );
153 + }
154 + }
155 +
156 + $timestamp = (int) $environment['timestamp'];
157 + $nonce = stripslashes( (string) $environment['nonce'] );
158 +
159 + if ( ! ( new Nonce_Handler() )->add( $timestamp, $nonce ) ) {
160 + // De-nonce the nonce, at least for 5 minutes.
161 + // We have to reuse this nonce at least once (used the first time when the initial request is made, used a second time when the login form is POSTed).
162 + $old_nonce_time = get_option( "jetpack_nonce_{$timestamp}_{$nonce}" );
163 + if ( $old_nonce_time < time() - 300 ) {
164 + wp_die( esc_html__( 'The authorization process expired. Please go back and try again.', 'jetpack-connection' ) );
165 + }
166 + }
167 +
168 + $data = json_decode(
169 + base64_decode( stripslashes( $environment['data'] ) ) // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
170 + );
171 + $data_filters = array(
172 + 'state' => 'opaque',
173 + 'client_id' => 'int',
174 + 'client_title' => 'string',
175 + 'client_image' => 'url',
176 + );
177 +
178 + foreach ( $data_filters as $key => $sanitation ) {
179 + if ( ! isset( $data->$key ) ) {
180 + wp_die(
181 + wp_kses(
182 + $die_error,
183 + array(
184 + 'a' => array(
185 + 'href' => array(),
186 + ),
187 + )
188 + )
189 + );
190 + }
191 +
192 + switch ( $sanitation ) {
193 + case 'int':
194 + $this->json_api_authorization_request[ $key ] = (int) $data->$key;
195 + break;
196 + case 'opaque':
197 + $this->json_api_authorization_request[ $key ] = (string) $data->$key;
198 + break;
199 + case 'string':
200 + $this->json_api_authorization_request[ $key ] = wp_kses( (string) $data->$key, array() );
201 + break;
202 + case 'url':
203 + $this->json_api_authorization_request[ $key ] = esc_url_raw( (string) $data->$key );
204 + break;
205 + }
206 + }
207 +
208 + if ( empty( $this->json_api_authorization_request['client_id'] ) ) {
209 + wp_die(
210 + wp_kses(
211 + $die_error,
212 + array(
213 + 'a' => array(
214 + 'href' => array(),
215 + ),
216 + )
217 + )
218 + );
219 + }
220 + }
221 +
222 + /**
223 + * Add the Access Code details to the public-api.wordpress.com redirect.
224 + *
225 + * @since 2.7.6 Ported from Jetpack to the Connection package.
226 + *
227 + * @param string $redirect_to URL.
228 + * @param string $original_redirect_to URL.
229 + * @param \WP_User $user WP_User for the redirect.
230 + *
231 + * @return string
232 + */
233 + public function add_token_to_login_redirect_json_api_authorization( $redirect_to, $original_redirect_to, $user ) {
234 + return add_query_arg(
235 + urlencode_deep(
236 + array(
237 + 'jetpack-code' => get_user_meta(
238 + $user->ID,
239 + 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'],
240 + true
241 + ),
242 + 'jetpack-user-id' => (int) $user->ID,
243 + 'jetpack-state' => $this->json_api_authorization_request['state'],
244 + )
245 + ),
246 + $redirect_to
247 + );
248 + }
249 +
250 + /**
251 + * If someone logs in to approve API access, store the Access Code in usermeta.
252 + *
253 + * @since 2.7.6 Ported from Jetpack to the Connection package.
254 + *
255 + * @param string $user_login Unused.
256 + * @param \WP_User $user User logged in.
257 + *
258 + * @return void
259 + */
260 + public function store_json_api_authorization_token( $user_login, $user ) {
261 + add_filter( 'login_redirect', array( $this, 'add_token_to_login_redirect_json_api_authorization' ), 10, 3 );
262 + add_filter( 'allowed_redirect_hosts', array( Host::class, 'allow_wpcom_public_api_domain' ) );
263 + $token = wp_generate_password( 32, false );
264 + update_user_meta( $user->ID, 'jetpack_json_api_' . $this->json_api_authorization_request['client_id'], $token );
265 + }
266 +
267 + /**
268 + * HTML for the JSON API authorization notice.
269 + *
270 + * @since 2.7.6 Ported from Jetpack to the Connection package.
271 + *
272 + * @return string
273 + */
274 + public function login_message_json_api_authorization() {
275 + return '<p class="message">' . sprintf(
276 + /* translators: Name/image of the client requesting authorization */
277 + esc_html__( '%s wants to access your site’s data. Log in to authorize that access.', 'jetpack-connection' ),
278 + '<strong>' . esc_html( $this->json_api_authorization_request['client_title'] ) . '</strong>'
279 + ) . '<img src="' . esc_url( $this->json_api_authorization_request['client_image'] ) . '" /></p>';
280 + }
281 +}