Completed
Push — add/php-test-for-business-hour... ( 87d7ff...7c58d5 )
by
unknown
60:32 queued 48:54
created

REST_Connector   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 441
Duplicated Lines 0.68 %

Coupling/Cohesion

Components 2
Dependencies 5

Importance

Changes 0
Metric Value
dl 3
loc 441
rs 8.96
c 0
b 0
f 0
wmc 43
lcom 2
cbo 5

13 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 96 2
A verify_registration() 0 5 1
A remote_authorize() 0 10 2
A connection_status() 0 38 5
A get_connection_plugins() 0 16 2
A activate_plugins_permission_check() 0 7 2
A connection_plugins_permission_check() 0 12 3
B is_request_signed_by_jetpack_debugger() 0 36 6
A jetpack_reconnect_permission_check() 0 7 2
A get_user_permissions_error_msg() 0 3 1
B connection_reconnect() 0 36 7
A jetpack_register_permission_check() 0 7 2
B connection_register() 3 36 8

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like REST_Connector often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use REST_Connector, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Sets up the Connection REST API endpoints.
4
 *
5
 * @package automattic/jetpack-connection
6
 */
7
8
namespace Automattic\Jetpack\Connection;
9
10
use Automattic\Jetpack\Status;
11
use Jetpack_XMLRPC_Server;
12
use WP_Error;
13
use WP_REST_Request;
14
use WP_REST_Response;
15
use WP_REST_Server;
16
17
/**
18
 * Registers the REST routes for Connections.
19
 */
20
class REST_Connector {
21
	/**
22
	 * The Connection Manager.
23
	 *
24
	 * @var Manager
25
	 */
26
	private $connection;
27
28
	/**
29
	 * This property stores the localized "Insufficient Permissions" error message.
30
	 *
31
	 * @var string Generic error message when user is not allowed to perform an action.
32
	 */
33
	private static $user_permissions_error_msg;
34
35
	const JETPACK__DEBUGGER_PUBLIC_KEY = "\r\n" . '-----BEGIN PUBLIC KEY-----' . "\r\n"
36
	. 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm+uLLVoxGCY71LS6KFc6' . "\r\n"
37
	. '1UnF6QGBAsi5XF8ty9kR3/voqfOkpW+gRerM2Kyjy6DPCOmzhZj7BFGtxSV2ZoMX' . "\r\n"
38
	. '9ZwWxzXhl/Q/6k8jg8BoY1QL6L2K76icXJu80b+RDIqvOfJruaAeBg1Q9NyeYqLY' . "\r\n"
39
	. 'lEVzN2vIwcFYl+MrP/g6Bc2co7Jcbli+tpNIxg4Z+Hnhbs7OJ3STQLmEryLpAxQO' . "\r\n"
40
	. 'q8cbhQkMx+FyQhxzSwtXYI/ClCUmTnzcKk7SgGvEjoKGAmngILiVuEJ4bm7Q1yok' . "\r\n"
41
	. 'xl9+wcfW6JAituNhml9dlHCWnn9D3+j8pxStHihKy2gVMwiFRjLEeD8K/7JVGkb/' . "\r\n"
42
	. 'EwIDAQAB' . "\r\n"
43
	. '-----END PUBLIC KEY-----' . "\r\n";
44
45
	/**
46
	 * Constructor.
47
	 *
48
	 * @param Manager $connection The Connection Manager.
49
	 */
50
	public function __construct( Manager $connection ) {
51
		$this->connection = $connection;
52
53
		self::$user_permissions_error_msg = esc_html__(
54
			'You do not have the correct user permissions to perform this action.
55
			Please contact your site admin if you think this is a mistake.',
56
			'jetpack'
57
		);
58
59
		if ( ! $this->connection->has_connected_owner() ) {
60
			// Register a site.
61
			register_rest_route(
62
				'jetpack/v4',
63
				'/verify_registration',
64
				array(
65
					'methods'             => WP_REST_Server::EDITABLE,
66
					'callback'            => array( $this, 'verify_registration' ),
67
					'permission_callback' => '__return_true',
68
				)
69
			);
70
		}
71
72
		// Authorize a remote user.
73
		register_rest_route(
74
			'jetpack/v4',
75
			'/remote_authorize',
76
			array(
77
				'methods'             => WP_REST_Server::EDITABLE,
78
				'callback'            => __CLASS__ . '::remote_authorize',
79
				'permission_callback' => '__return_true',
80
			)
81
		);
82
83
		// Get current connection status of Jetpack.
84
		register_rest_route(
85
			'jetpack/v4',
86
			'/connection',
87
			array(
88
				'methods'             => WP_REST_Server::READABLE,
89
				'callback'            => __CLASS__ . '::connection_status',
90
				'permission_callback' => '__return_true',
91
			)
92
		);
93
94
		// Get list of plugins that use the Jetpack connection.
95
		register_rest_route(
96
			'jetpack/v4',
97
			'/connection/plugins',
98
			array(
99
				'methods'             => WP_REST_Server::READABLE,
100
				'callback'            => array( $this, 'get_connection_plugins' ),
101
				'permission_callback' => __CLASS__ . '::connection_plugins_permission_check',
102
			)
103
		);
104
105
		// Full or partial reconnect in case of connection issues.
106
		register_rest_route(
107
			'jetpack/v4',
108
			'/connection/reconnect',
109
			array(
110
				'methods'             => WP_REST_Server::EDITABLE,
111
				'callback'            => array( $this, 'connection_reconnect' ),
112
				'permission_callback' => __CLASS__ . '::jetpack_reconnect_permission_check',
113
			)
114
		);
115
116
		// Register the site (get `blog_token`).
117
		register_rest_route(
118
			'jetpack/v4',
119
			'/connection/register',
120
			array(
121
				'methods'             => WP_REST_Server::EDITABLE,
122
				'callback'            => array( $this, 'connection_register' ),
123
				'permission_callback' => __CLASS__ . '::jetpack_register_permission_check',
124
				'args'                => array(
125
					'from'               => array(
126
						'description' => __( 'Indicates where the registration action was triggered for tracking/segmentation purposes', 'jetpack' ),
127
						'type'        => 'string',
128
					),
129
					'registration_nonce' => array(
130
						'description' => __( 'The registration nonce', 'jetpack' ),
131
						'type'        => 'string',
132
						'required'    => true,
133
					),
134
					'no_iframe'          => array(
135
						'description' => __( 'Disable In-Place connection flow and go straight to Calypso', 'jetpack' ),
136
						'type'        => 'boolean',
137
					),
138
					'redirect_uri'       => array(
139
						'description' => __( 'URI of the admin page where the user should be redirected after connection flow', 'jetpack' ),
140
						'type'        => 'string',
141
					),
142
				),
143
			)
144
		);
145
	}
146
147
	/**
148
	 * Handles verification that a site is registered.
149
	 *
150
	 * @since 5.4.0
151
	 *
152
	 * @param WP_REST_Request $request The request sent to the WP REST API.
153
	 *
154
	 * @return string|WP_Error
155
	 */
156
	public function verify_registration( WP_REST_Request $request ) {
157
		$registration_data = array( $request['secret_1'], $request['state'] );
158
159
		return $this->connection->handle_registration( $registration_data );
160
	}
161
162
	/**
163
	 * Handles verification that a site is registered
164
	 *
165
	 * @since 5.4.0
166
	 *
167
	 * @param WP_REST_Request $request The request sent to the WP REST API.
168
	 *
169
	 * @return array|wp-error
0 ignored issues
show
Documentation introduced by
The doc-type array|wp-error could not be parsed: Unknown type name "wp-error" at position 6. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
170
	 */
171
	public static function remote_authorize( $request ) {
172
		$xmlrpc_server = new Jetpack_XMLRPC_Server();
173
		$result        = $xmlrpc_server->remote_authorize( $request );
174
175
		if ( is_a( $result, 'IXR_Error' ) ) {
176
			$result = new WP_Error( $result->code, $result->message );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with $result->code.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
177
		}
178
179
		return $result;
180
	}
181
182
	/**
183
	 * Get connection status for this Jetpack site.
184
	 *
185
	 * @since 4.3.0
186
	 *
187
	 * @param bool $rest_response Should we return a rest response or a simple array. Default to rest response.
188
	 *
189
	 * @return WP_REST_Response|array Connection information.
190
	 */
191
	public static function connection_status( $rest_response = true ) {
192
		$status     = new Status();
193
		$connection = new Manager();
194
195
		$connection_status = array(
196
			'isActive'          => $connection->is_active(), // TODO deprecate this.
0 ignored issues
show
Deprecated Code introduced by
The method Automattic\Jetpack\Connection\Manager::is_active() has been deprecated with message: 9.6.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
197
			'isStaging'         => $status->is_staging_site(),
198
			'isRegistered'      => $connection->is_connected(),
199
			'isUserConnected'   => $connection->is_user_connected(),
200
			'hasConnectedOwner' => $connection->has_connected_owner(),
201
			'offlineMode'       => array(
202
				'isActive'        => $status->is_offline_mode(),
203
				'constant'        => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
204
				'url'             => $status->is_local_site(),
205
				/** This filter is documented in packages/status/src/class-status.php */
206
				'filter'          => ( apply_filters( 'jetpack_development_mode', false ) || apply_filters( 'jetpack_offline_mode', false ) ), // jetpack_development_mode is deprecated.
207
				'wpLocalConstant' => defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV,
208
			),
209
			'isPublic'          => '1' == get_option( 'blog_public' ), // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
210
		);
211
212
		/**
213
		 * Filters the connection status data.
214
		 *
215
		 * @since 9.6.0
216
		 *
217
		 * @param array An array containing the connection status data.
218
		 */
219
		$connection_status = apply_filters( 'jetpack_connection_status', $connection_status );
220
221
		if ( $rest_response ) {
222
			return rest_ensure_response(
223
				$connection_status
224
			);
225
		} else {
226
			return $connection_status;
227
		}
228
	}
229
230
	/**
231
	 * Get plugins connected to the Jetpack.
232
	 *
233
	 * @since 8.6.0
234
	 *
235
	 * @return WP_REST_Response|WP_Error Response or error object, depending on the request result.
236
	 */
237
	public function get_connection_plugins() {
238
		$plugins = $this->connection->get_connected_plugins();
239
240
		if ( is_wp_error( $plugins ) ) {
241
			return $plugins;
242
		}
243
244
		array_walk(
245
			$plugins,
246
			function ( &$data, $slug ) {
247
				$data['slug'] = $slug;
248
			}
249
		);
250
251
		return rest_ensure_response( array_values( $plugins ) );
252
	}
253
254
	/**
255
	 * Verify that user can view Jetpack admin page and can activate plugins.
256
	 *
257
	 * @since 8.8.0
258
	 *
259
	 * @return bool|WP_Error Whether user has the capability 'activate_plugins'.
260
	 */
261
	public static function activate_plugins_permission_check() {
262
		if ( current_user_can( 'activate_plugins' ) ) {
263
			return true;
264
		}
265
266
		return new WP_Error( 'invalid_user_permission_activate_plugins', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_user_permission_activate_plugins'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
267
	}
268
269
	/**
270
	 * Permission check for the connection_plugins endpoint
271
	 *
272
	 * @return bool|WP_Error
273
	 */
274
	public static function connection_plugins_permission_check() {
275
		if ( true === static::activate_plugins_permission_check() ) {
276
			return true;
277
		}
278
279
		if ( true === static::is_request_signed_by_jetpack_debugger() ) {
280
			return true;
281
		}
282
283
		return new WP_Error( 'invalid_user_permission_activate_plugins', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_user_permission_activate_plugins'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
284
285
	}
286
287
	/**
288
	 * Verifies if the request was signed with the Jetpack Debugger key
289
	 *
290
	 * @param string|null $pub_key The public key used to verify the signature. Default is the Jetpack Debugger key. This is used for testing purposes.
291
	 *
292
	 * @return bool
293
	 */
294
	public static function is_request_signed_by_jetpack_debugger( $pub_key = null ) {
295
		 // phpcs:disable WordPress.Security.NonceVerification.Recommended
296
		if ( ! isset( $_GET['signature'], $_GET['timestamp'], $_GET['url'], $_GET['rest_route'] ) ) {
297
			return false;
298
		}
299
300
		// signature timestamp must be within 5min of current time.
301
		if ( abs( time() - (int) $_GET['timestamp'] ) > 300 ) {
302
			return false;
303
		}
304
305
		$signature = base64_decode( $_GET['signature'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
306
307
		$signature_data = wp_json_encode(
308
			array(
309
				'rest_route' => $_GET['rest_route'],
310
				'timestamp'  => (int) $_GET['timestamp'],
311
				'url'        => wp_unslash( $_GET['url'] ),
312
			)
313
		);
314
315
		if (
316
			! function_exists( 'openssl_verify' )
317
			|| 1 !== openssl_verify(
318
				$signature_data,
319
				$signature,
320
				$pub_key ? $pub_key : static::JETPACK__DEBUGGER_PUBLIC_KEY
321
			)
322
		) {
323
			return false;
324
		}
325
326
		// phpcs:enable WordPress.Security.NonceVerification.Recommended
327
328
		return true;
329
	}
330
331
	/**
332
	 * Verify that user is allowed to disconnect Jetpack.
333
	 *
334
	 * @since 8.8.0
335
	 *
336
	 * @return bool|WP_Error Whether user has the capability 'jetpack_disconnect'.
337
	 */
338
	public static function jetpack_reconnect_permission_check() {
339
		if ( current_user_can( 'jetpack_reconnect' ) ) {
340
			return true;
341
		}
342
343
		return new WP_Error( 'invalid_user_permission_jetpack_disconnect', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_user_permission_jetpack_disconnect'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
344
	}
345
346
	/**
347
	 * Returns generic error message when user is not allowed to perform an action.
348
	 *
349
	 * @return string The error message.
350
	 */
351
	public static function get_user_permissions_error_msg() {
352
		return self::$user_permissions_error_msg;
353
	}
354
355
	/**
356
	 * The endpoint tried to partially or fully reconnect the website to WP.com.
357
	 *
358
	 * @since 8.8.0
359
	 *
360
	 * @return \WP_REST_Response|WP_Error
361
	 */
362
	public function connection_reconnect() {
363
		$response = array();
364
365
		$next = null;
366
367
		$result = $this->connection->restore();
368
369
		if ( is_wp_error( $result ) ) {
370
			$response = $result;
371
		} elseif ( is_string( $result ) ) {
372
			$next = $result;
373
		} else {
374
			$next = true === $result ? 'completed' : 'failed';
375
		}
376
377
		switch ( $next ) {
378
			case 'authorize':
379
				$response['status']       = 'in_progress';
380
				$response['authorizeUrl'] = $this->connection->get_authorization_url();
381
				break;
382
			case 'completed':
383
				$response['status'] = 'completed';
384
				/**
385
				 * Action fired when reconnection has completed successfully.
386
				 *
387
				 * @since 9.0.0
388
				 */
389
				do_action( 'jetpack_reconnection_completed' );
390
				break;
391
			case 'failed':
392
				$response = new WP_Error( 'Reconnect failed' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'Reconnect failed'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
393
				break;
394
		}
395
396
		return rest_ensure_response( $response );
397
	}
398
399
	/**
400
	 * Verify that user is allowed to connect Jetpack.
401
	 *
402
	 * @since 9.7.0
403
	 *
404
	 * @return bool|WP_Error Whether user has the capability 'jetpack_connect'.
405
	 */
406
	public static function jetpack_register_permission_check() {
407
		if ( current_user_can( 'jetpack_connect' ) ) {
408
			return true;
409
		}
410
411
		return new WP_Error( 'invalid_user_permission_jetpack_connect', self::get_user_permissions_error_msg(), array( 'status' => rest_authorization_required_code() ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_user_permission_jetpack_connect'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
412
	}
413
414
	/**
415
	 * The endpoint tried to partially or fully reconnect the website to WP.com.
416
	 *
417
	 * @since 7.7.0
418
	 *
419
	 * @param \WP_REST_Request $request The request sent to the WP REST API.
420
	 *
421
	 * @return \WP_REST_Response|WP_Error
422
	 */
423
	public function connection_register( $request ) {
424 View Code Duplication
		if ( ! wp_verify_nonce( $request->get_param( 'registration_nonce' ), 'jetpack-registration-nonce' ) ) {
425
			return new WP_Error( 'invalid_nonce', __( 'Unable to verify your request.', 'jetpack' ), array( 'status' => 403 ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_nonce'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
426
		}
427
428
		if ( isset( $request['from'] ) ) {
429
			$this->connection->add_register_request_param( 'from', (string) $request['from'] );
430
		}
431
		$result = $this->connection->try_registration();
432
433
		if ( is_wp_error( $result ) ) {
434
			return $result;
435
		}
436
437
		$redirect_uri = $request->get_param( 'redirect_uri' ) ? admin_url( $request->get_param( 'redirect_uri' ) ) : null;
438
439
		if ( class_exists( 'Jetpack' ) ) {
440
			$authorize_url = \Jetpack::build_authorize_url( $redirect_uri, ! $request->get_param( 'no_iframe' ) );
441
		} else {
442
			if ( ! $request->get_param( 'no_iframe' ) ) {
443
				add_filter( 'jetpack_use_iframe_authorization_flow', '__return_true' );
444
			}
445
446
			$authorize_url = $this->connection->get_authorization_url( null, $redirect_uri );
447
448
			if ( ! $request->get_param( 'no_iframe' ) ) {
449
				remove_filter( 'jetpack_use_iframe_authorization_flow', '__return_true' );
450
			}
451
		}
452
453
		return rest_ensure_response(
454
			array(
455
				'authorizeUrl' => $authorize_url,
456
			)
457
		);
458
	}
459
460
}
461