Completed
Push — update/gardening-action-extrac... ( 967522...8b9cf8 )
by Jeremy
24:34 queued 12:22
created

connection_plugins_permission_check()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 0
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
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
117
	/**
118
	 * Handles verification that a site is registered.
119
	 *
120
	 * @since 5.4.0
121
	 *
122
	 * @param WP_REST_Request $request The request sent to the WP REST API.
123
	 *
124
	 * @return string|WP_Error
125
	 */
126
	public function verify_registration( WP_REST_Request $request ) {
127
		$registration_data = array( $request['secret_1'], $request['state'] );
128
129
		return $this->connection->handle_registration( $registration_data );
130
	}
131
132
	/**
133
	 * Handles verification that a site is registered
134
	 *
135
	 * @since 5.4.0
136
	 *
137
	 * @param WP_REST_Request $request The request sent to the WP REST API.
138
	 *
139
	 * @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...
140
	 */
141
	public static function remote_authorize( $request ) {
142
		$xmlrpc_server = new Jetpack_XMLRPC_Server();
143
		$result        = $xmlrpc_server->remote_authorize( $request );
144
145
		if ( is_a( $result, 'IXR_Error' ) ) {
146
			$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...
147
		}
148
149
		return $result;
150
	}
151
152
	/**
153
	 * Get connection status for this Jetpack site.
154
	 *
155
	 * @since 4.3.0
156
	 *
157
	 * @param bool $rest_response Should we return a rest response or a simple array. Default to rest response.
158
	 *
159
	 * @return WP_REST_Response|array Connection information.
160
	 */
161
	public static function connection_status( $rest_response = true ) {
162
		$status     = new Status();
163
		$connection = new Manager();
164
165
		$connection_status = array(
166
			'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...
167
			'isStaging'         => $status->is_staging_site(),
168
			'isRegistered'      => $connection->is_connected(),
169
			'hasConnectedOwner' => $connection->has_connected_owner(),
170
			'offlineMode'       => array(
171
				'isActive'        => $status->is_offline_mode(),
172
				'constant'        => defined( 'JETPACK_DEV_DEBUG' ) && JETPACK_DEV_DEBUG,
173
				'url'             => $status->is_local_site(),
174
				/** This filter is documented in packages/status/src/class-status.php */
175
				'filter'          => ( apply_filters( 'jetpack_development_mode', false ) || apply_filters( 'jetpack_offline_mode', false ) ), // jetpack_development_mode is deprecated.
176
				'wpLocalConstant' => defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV,
177
			),
178
			'isPublic'          => '1' == get_option( 'blog_public' ), // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
179
		);
180
181
		/**
182
		 * Filters the connection status data.
183
		 *
184
		 * @since 9.6.0
185
		 *
186
		 * @param array An array containing the connection status data.
187
		 */
188
		$connection_status = apply_filters( 'jetpack_connection_status', $connection_status );
189
190
		if ( $rest_response ) {
191
			return rest_ensure_response(
192
				$connection_status
193
			);
194
		} else {
195
			return $connection_status;
196
		}
197
	}
198
199
	/**
200
	 * Get plugins connected to the Jetpack.
201
	 *
202
	 * @since 8.6.0
203
	 *
204
	 * @return WP_REST_Response|WP_Error Response or error object, depending on the request result.
205
	 */
206
	public function get_connection_plugins() {
207
		$plugins = $this->connection->get_connected_plugins();
208
209
		if ( is_wp_error( $plugins ) ) {
210
			return $plugins;
211
		}
212
213
		array_walk(
214
			$plugins,
215
			function ( &$data, $slug ) {
216
				$data['slug'] = $slug;
217
			}
218
		);
219
220
		return rest_ensure_response( array_values( $plugins ) );
221
	}
222
223
	/**
224
	 * Verify that user can view Jetpack admin page and can activate plugins.
225
	 *
226
	 * @since 8.8.0
227
	 *
228
	 * @return bool|WP_Error Whether user has the capability 'activate_plugins'.
229
	 */
230
	public static function activate_plugins_permission_check() {
231
		if ( current_user_can( 'activate_plugins' ) ) {
232
			return true;
233
		}
234
235
		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...
236
	}
237
238
	/**
239
	 * Permission check for the connection_plugins endpoint
240
	 *
241
	 * @return bool|WP_Error
242
	 */
243
	public static function connection_plugins_permission_check() {
244
		if ( true === static::activate_plugins_permission_check() ) {
245
			return true;
246
		}
247
248
		if ( true === static::is_request_signed_by_jetpack_debugger() ) {
249
			return true;
250
		}
251
252
		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...
253
254
	}
255
256
	/**
257
	 * Verifies if the request was signed with the Jetpack Debugger key
258
	 *
259
	 * @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.
260
	 *
261
	 * @return bool
262
	 */
263
	public static function is_request_signed_by_jetpack_debugger( $pub_key = null ) {
264
		 // phpcs:disable WordPress.Security.NonceVerification.Recommended
265
		if ( ! isset( $_GET['signature'], $_GET['timestamp'], $_GET['url'], $_GET['rest_route'] ) ) {
266
			return false;
267
		}
268
269
		// signature timestamp must be within 5min of current time.
270
		if ( abs( time() - (int) $_GET['timestamp'] ) > 300 ) {
271
			return false;
272
		}
273
274
		$signature = base64_decode( $_GET['signature'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
275
276
		$signature_data = wp_json_encode(
277
			array(
278
				'rest_route' => $_GET['rest_route'],
279
				'timestamp'  => (int) $_GET['timestamp'],
280
				'url'        => wp_unslash( $_GET['url'] ),
281
			)
282
		);
283
284
		if (
285
			! function_exists( 'openssl_verify' )
286
			|| 1 !== openssl_verify(
287
				$signature_data,
288
				$signature,
289
				$pub_key ? $pub_key : static::JETPACK__DEBUGGER_PUBLIC_KEY
290
			)
291
		) {
292
			return false;
293
		}
294
295
		// phpcs:enable WordPress.Security.NonceVerification.Recommended
296
297
		return true;
298
	}
299
300
	/**
301
	 * Verify that user is allowed to disconnect Jetpack.
302
	 *
303
	 * @since 8.8.0
304
	 *
305
	 * @return bool|WP_Error Whether user has the capability 'jetpack_disconnect'.
306
	 */
307
	public static function jetpack_reconnect_permission_check() {
308
		if ( current_user_can( 'jetpack_reconnect' ) ) {
309
			return true;
310
		}
311
312
		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...
313
	}
314
315
	/**
316
	 * Returns generic error message when user is not allowed to perform an action.
317
	 *
318
	 * @return string The error message.
319
	 */
320
	public static function get_user_permissions_error_msg() {
321
		return self::$user_permissions_error_msg;
322
	}
323
324
	/**
325
	 * The endpoint tried to partially or fully reconnect the website to WP.com.
326
	 *
327
	 * @since 8.8.0
328
	 *
329
	 * @return \WP_REST_Response|WP_Error
330
	 */
331
	public function connection_reconnect() {
332
		$response = array();
333
334
		$next = null;
335
336
		$result = $this->connection->restore();
337
338
		if ( is_wp_error( $result ) ) {
339
			$response = $result;
340
		} elseif ( is_string( $result ) ) {
341
			$next = $result;
342
		} else {
343
			$next = true === $result ? 'completed' : 'failed';
344
		}
345
346
		switch ( $next ) {
347
			case 'authorize':
348
				$response['status']       = 'in_progress';
349
				$response['authorizeUrl'] = $this->connection->get_authorization_url();
350
				break;
351
			case 'completed':
352
				$response['status'] = 'completed';
353
				/**
354
				 * Action fired when reconnection has completed successfully.
355
				 *
356
				 * @since 9.0.0
357
				 */
358
				do_action( 'jetpack_reconnection_completed' );
359
				break;
360
			case 'failed':
361
				$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...
362
				break;
363
		}
364
365
		return rest_ensure_response( $response );
366
	}
367
368
}
369