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
jetpack / jetpack_vendor / automattic / jetpack-connection / src / identity-crisis / class-rest-endpoints.php

class-rest-endpoints.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3, at jetpack_vendor/automattic/jetpack-connection/src/identity-crisis/class-rest-endpoints.php

377 lines 11.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Identity_Crisis REST endpoints of the Connection package.
4 *
5 * @package automattic/jetpack-connection
6 */
7
8 namespace Automattic\Jetpack\IdentityCrisis;
9
10 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
11 use Automattic\Jetpack\Connection\Rest_Authentication;
12 use Jetpack_Options;
13 use Jetpack_XMLRPC_Server;
14 use WP_Error;
15 use WP_REST_Server;
16
17 /**
18 * This class will handle Identity Crisis Endpoints
19 *
20 * @since automattic/jetpack-identity-crisis:0.2.0
21 * @since 2.9.0
22 */
23 class REST_Endpoints {
24
25 /**
26 * Initialize REST routes.
27 */
28 public static function initialize_rest_api() {
29
30 // Confirm that a site in identity crisis should be in staging mode.
31 register_rest_route(
32 'jetpack/v4',
33 '/identity-crisis/confirm-safe-mode',
34 array(
35 'methods' => WP_REST_Server::EDITABLE,
36 'callback' => __CLASS__ . '::confirm_safe_mode',
37 'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
38 )
39 );
40
41 // Handles the request to migrate stats and subscribers during an identity crisis.
42 register_rest_route(
43 'jetpack/v4',
44 'identity-crisis/migrate',
45 array(
46 'methods' => WP_REST_Server::EDITABLE,
47 'callback' => __CLASS__ . '::migrate_stats_and_subscribers',
48 'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
49 )
50 );
51
52 // IDC resolve: create an entirely new shadow site for this URL.
53 register_rest_route(
54 'jetpack/v4',
55 '/identity-crisis/start-fresh',
56 array(
57 'methods' => WP_REST_Server::EDITABLE,
58 'callback' => __CLASS__ . '::start_fresh_connection',
59 'permission_callback' => __CLASS__ . '::identity_crisis_mitigation_permission_check',
60 'args' => array(
61 'redirect_uri' => array(
62 'description' => __( 'URI of the admin page where the user should be redirected after connection flow', 'jetpack-connection' ),
63 'type' => 'string',
64 ),
65 ),
66 )
67 );
68
69 // Fetch URL and secret for IDC check.
70 register_rest_route(
71 'jetpack/v4',
72 '/identity-crisis/idc-url-validation',
73 array(
74 'methods' => WP_REST_Server::READABLE,
75 'callback' => array( static::class, 'validate_urls_and_set_secret' ),
76 'permission_callback' => array( static::class, 'url_secret_permission_check' ),
77 )
78 );
79
80 // Fetch URL verification secret.
81 register_rest_route(
82 'jetpack/v4',
83 '/identity-crisis/url-secret',
84 array(
85 'methods' => WP_REST_Server::READABLE,
86 'callback' => array( static::class, 'fetch_url_secret' ),
87 'permission_callback' => array( static::class, 'url_secret_permission_check' ),
88 )
89 );
90
91 // Fetch URL verification secret.
92 register_rest_route(
93 'jetpack/v4',
94 '/identity-crisis/compare-url-secret',
95 array(
96 'methods' => WP_REST_Server::EDITABLE,
97 'callback' => array( static::class, 'compare_url_secret' ),
98 'permission_callback' => array( static::class, 'compare_url_secret_permission_check' ),
99 'args' => array(
100 'secret' => array(
101 'description' => __( 'URL secret to compare to the ones stored in the database.', 'jetpack-connection' ),
102 'type' => 'string',
103 'required' => true,
104 ),
105 ),
106 )
107 );
108 }
109
110 /**
111 * Handles identity crisis mitigation, confirming safe mode for this site.
112 *
113 * @since 0.2.0
114 * @since-jetpack 4.4.0
115 *
116 * @return bool | WP_Error True if option is properly set.
117 */
118 public static function confirm_safe_mode() {
119 // Read the option's true DB state, not a stale cached copy: on sites with
120 // a persistent object cache, a prior un-confirm (the admin-bar "clear
121 // confirmation" action deletes jetpack_safe_mode_confirmed) can leave a
122 // lingering cached value that would make update_option() below see the new
123 // value as unchanged and report a false failure.
124 self::flush_jetpack_option_cache( 'jetpack_safe_mode_confirmed' );
125
126 // update_option() returns false both when the write fails and when the
127 // value is already set, so treat an already-confirmed option as success.
128 if (
129 Jetpack_Options::get_option( 'safe_mode_confirmed' )
130 || Jetpack_Options::update_option( 'safe_mode_confirmed', true )
131 ) {
132 return rest_ensure_response(
133 array(
134 'code' => 'success',
135 )
136 );
137 }
138
139 return new WP_Error(
140 'error_setting_jetpack_safe_mode',
141 esc_html__( 'Could not confirm safe mode.', 'jetpack-connection' ),
142 array( 'status' => 500 )
143 );
144 }
145
146 /**
147 * Flush any cached copy of a Jetpack option so a subsequent read reflects the
148 * option's true state in the database.
149 *
150 * Jetpack options are stored as autoloaded `jetpack_*` WordPress options, so
151 * bust both the individual key and the autoloaded-options cache to keep
152 * persistent object caches from returning a stale value across requests.
153 *
154 * @since 8.7.0
155 *
156 * @param string $option The prefixed option name (e.g. `jetpack_safe_mode_confirmed`).
157 */
158 private static function flush_jetpack_option_cache( $option ) {
159 wp_cache_delete( $option, 'options' );
160 wp_cache_delete( 'alloptions', 'options' );
161 }
162
163 /**
164 * Flush any cached copy of the sync_error_idc option so a subsequent read
165 * reflects the option's true state in the database.
166 *
167 * @since 8.7.0
168 */
169 private static function flush_sync_error_idc_cache() {
170 self::flush_jetpack_option_cache( 'jetpack_sync_error_idc' );
171 }
172
173 /**
174 * Handles identity crisis mitigation, migrating stats and subscribers from old url to this, new url.
175 *
176 * @since 0.2.0
177 * @since-jetpack 4.4.0
178 *
179 * @return bool | WP_Error True if option is properly set.
180 */
181 public static function migrate_stats_and_subscribers() {
182 // Read the option's true DB state, not a stale cached copy: on sites with
183 // a persistent object cache, a prior successful migrate (or an IDC
184 // revalidation) may have already removed the option while a cached value
185 // lingers, which would otherwise make the delete below report a false
186 // failure.
187 self::flush_sync_error_idc_cache();
188
189 if ( Jetpack_Options::get_option( 'sync_error_idc' ) ) {
190 Jetpack_Options::delete_option( 'sync_error_idc' );
191
192 // delete_option() returns false both when the write fails and when
193 // there is nothing to delete, so re-read (cache-busted) and only treat
194 // a still-present option as a real failure.
195 self::flush_sync_error_idc_cache();
196
197 if ( Jetpack_Options::get_option( 'sync_error_idc' ) ) {
198 return new WP_Error(
199 'error_deleting_sync_error_idc',
200 esc_html__( 'Could not delete sync error option.', 'jetpack-connection' ),
201 array( 'status' => 500 )
202 );
203 }
204 }
205
206 if ( Jetpack_Options::get_option( 'migrate_for_idc' ) || Jetpack_Options::update_option( 'migrate_for_idc', true ) ) {
207 return rest_ensure_response(
208 array(
209 'code' => 'success',
210 )
211 );
212 }
213 return new WP_Error(
214 'error_setting_jetpack_migrate',
215 esc_html__( 'Could not confirm migration.', 'jetpack-connection' ),
216 array( 'status' => 500 )
217 );
218 }
219
220 /**
221 * This IDC resolution will disconnect the site and re-connect to a completely new
222 * and separate shadow site than the original.
223 *
224 * It will first will disconnect the site without phoning home as to not disturb the production site.
225 * It then builds a fresh connection URL and sends it back along with the response.
226 *
227 * @since 0.2.0
228 * @since-jetpack 4.4.0
229 *
230 * @param \WP_REST_Request $request The request sent to the WP REST API.
231 *
232 * @return \WP_REST_Response|WP_Error
233 */
234 public static function start_fresh_connection( $request ) {
235 /**
236 * Fires when Users have requested through Identity Crisis for the connection to be reset.
237 * Should be used to disconnect any connections and reset options.
238 *
239 * @since 0.2.0
240 */
241 do_action( 'jetpack_idc_disconnect' );
242
243 $connection = new Connection_Manager();
244 $result = $connection->try_registration( true );
245
246 // early return if site registration fails.
247 if ( ! $result || is_wp_error( $result ) ) {
248 return rest_ensure_response( $result );
249 }
250
251 $redirect_uri = $request->get_param( 'redirect_uri' ) ? admin_url( $request->get_param( 'redirect_uri' ) ) : null;
252
253 /**
254 * Filters the connection url that users should be redirected to for re-establishing their connection.
255 *
256 * @since 0.2.0
257 *
258 * @param \WP_REST_Response|WP_Error $connection_url Connection URL user should be redirected to.
259 */
260 return apply_filters( 'jetpack_idc_authorization_url', rest_ensure_response( $connection->get_authorization_url( null, $redirect_uri ) ) );
261 }
262
263 /**
264 * Verify that user can mitigate an identity crisis.
265 *
266 * @since 0.2.0
267 * @since-jetpack 4.4.0
268 *
269 * @return true|WP_Error True if the user has capability 'jetpack_disconnect', an error object otherwise.
270 */
271 public static function identity_crisis_mitigation_permission_check() {
272 if ( current_user_can( 'jetpack_disconnect' ) ) {
273 return true;
274 }
275 $error_msg = esc_html__(
276 'You do not have the correct user permissions to perform this action.
277 Please contact your site admin if you think this is a mistake.',
278 'jetpack-connection'
279 );
280
281 return new WP_Error( 'invalid_user_permission_identity_crisis', $error_msg, array( 'status' => rest_authorization_required_code() ) );
282 }
283
284 /**
285 * Endpoint for URL validation and creating a secret.
286 *
287 * @since 0.18.0
288 *
289 * @return array
290 */
291 public static function validate_urls_and_set_secret() {
292 $xmlrpc_server = new Jetpack_XMLRPC_Server();
293 $result = $xmlrpc_server->validate_urls_for_idc_mitigation();
294
295 return $result;
296 }
297
298 /**
299 * Endpoint for fetching the existing secret.
300 *
301 * @return WP_Error|\WP_REST_Response
302 */
303 public static function fetch_url_secret() {
304 $secret = new URL_Secret();
305
306 if ( ! $secret->exists() ) {
307 return new WP_Error( 'missing_url_secret', esc_html__( 'URL secret does not exist.', 'jetpack-connection' ) );
308 }
309
310 return rest_ensure_response(
311 array(
312 'code' => 'success',
313 'data' => array(
314 'secret' => $secret->get_secret(),
315 'expires_at' => $secret->get_expires_at(),
316 ),
317 )
318 );
319 }
320
321 /**
322 * Endpoint for comparing the existing secret.
323 *
324 * @param \WP_REST_Request $request The request sent to the WP REST API.
325 *
326 * @return WP_Error|\WP_REST_Response
327 */
328 public static function compare_url_secret( $request ) {
329 $match = false;
330
331 $storage = new URL_Secret();
332
333 if ( $storage->exists() ) {
334 $remote_secret = $request->get_param( 'secret' );
335 $match = $remote_secret && hash_equals( $storage->get_secret(), $remote_secret );
336 }
337
338 return rest_ensure_response(
339 array(
340 'code' => 'success',
341 'match' => $match,
342 )
343 );
344 }
345
346 /**
347 * Verify url_secret create/fetch permissions (valid blog token authentication).
348 *
349 * @return true|WP_Error
350 */
351 public static function url_secret_permission_check() {
352 return Rest_Authentication::is_signed_with_blog_token()
353 ? true
354 : new WP_Error(
355 'invalid_user_permission_identity_crisis',
356 esc_html__( 'You do not have the correct user permissions to perform this action.', 'jetpack-connection' ),
357 array( 'status' => rest_authorization_required_code() )
358 );
359 }
360
361 /**
362 * The endpoint is only available on non-connected sites.
363 * use `/identity-crisis/url-secret` for connected sites.
364 *
365 * @return true|WP_Error
366 */
367 public static function compare_url_secret_permission_check() {
368 return ( new Connection_Manager() )->is_connected()
369 ? new WP_Error(
370 'invalid_connection_status',
371 esc_html__( 'The endpoint is not available on connected sites.', 'jetpack-connection' ),
372 array( 'status' => 403 )
373 )
374 : true;
375 }
376 }
377