Completed
Push — try/run-package-tests-together... ( 183f95...9b30a1 )
by Marin
794:38 queued 787:11
created

Jetpack_XMLRPC_Server::do_post_authorization()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
use Automattic\Jetpack\Tracking;
4
/**
5
 * Just a sack of functions.  Not actually an IXR_Server
6
 */
7
class Jetpack_XMLRPC_Server {
8
	/**
9
	 * The current error object
10
	 */
11
	public $error = null;
12
13
	/**
14
	 * The current user
15
	 */
16
	public $user = null;
17
18
	/**
19
	 * Whitelist of the XML-RPC methods available to the Jetpack Server. If the
20
	 * user is not authenticated (->login()) then the methods are never added,
21
	 * so they will get a "does not exist" error.
22
	 */
23
	function xmlrpc_methods( $core_methods ) {
24
		$jetpack_methods = array(
25
			'jetpack.jsonAPI'           => array( $this, 'json_api' ),
26
			'jetpack.verifyAction'      => array( $this, 'verify_action' ),
27
			'jetpack.remoteRegister'    => array( $this, 'remote_register' ),
28
			'jetpack.remoteProvision'   => array( $this, 'remote_provision' ),
29
		);
30
31
		$this->user = $this->login();
32
33
		if ( $this->user ) {
34
			$jetpack_methods = array_merge( $jetpack_methods, array(
35
				'jetpack.testConnection'    => array( $this, 'test_connection' ),
36
				'jetpack.testAPIUserCode'   => array( $this, 'test_api_user_code' ),
37
				'jetpack.featuresAvailable' => array( $this, 'features_available' ),
38
				'jetpack.featuresEnabled'   => array( $this, 'features_enabled' ),
39
				'jetpack.disconnectBlog'    => array( $this, 'disconnect_blog' ),
40
				'jetpack.unlinkUser'        => array( $this, 'unlink_user' ),
41
				'jetpack.syncObject'        => array( $this, 'sync_object' ),
42
				'jetpack.idcUrlValidation'  => array( $this, 'validate_urls_for_idc_mitigation' ),
43
			) );
44
45
			if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
46
				$jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject'];
47
				$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
48
			}
49
50
			/**
51
			 * Filters the XML-RPC methods available to Jetpack for authenticated users.
52
			 *
53
			 * @since 1.1.0
54
			 *
55
			 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
56
			 * @param array $core_methods Available core XML-RPC methods.
57
			 * @param WP_User $user Information about a given WordPress user.
58
			 */
59
			$jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $this->user );
60
		}
61
62
		/**
63
		 * Filters the XML-RPC methods available to Jetpack for unauthenticated users.
64
		 *
65
		 * @since 3.0.0
66
		 *
67
		 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
68
		 * @param array $core_methods Available core XML-RPC methods.
69
		 */
70
		return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods );
71
	}
72
73
	/**
74
	 * Whitelist of the bootstrap XML-RPC methods
75
	 */
76
	function bootstrap_xmlrpc_methods() {
77
		return array(
78
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
79
			'jetpack.remoteRegister' => array( $this, 'remote_register' ),
80
		);
81
	}
82
83
	function authorize_xmlrpc_methods() {
84
		return array(
85
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
86
		);
87
	}
88
89
	function provision_xmlrpc_methods() {
90
		return array(
91
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
92
			'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
93
			'jetpack.remoteConnect'   => array( $this, 'remote_connect' ),
94
		);
95
	}
96
97
	function remote_authorize( $request ) {
98
		$user = get_user_by( 'id', $request['state'] );
99
		Tracking::record_user_event( 'jpc_remote_authorize_begin', array(), $user );
100
101
		foreach( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
102
			if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
103
				return $this->error( new Jetpack_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ), 'jpc_remote_authorize_fail' );
104
			}
105
		}
106
107
		if ( ! $user ) {
108
			return $this->error( new Jetpack_Error( 'user_unknown', 'User not found.', 404 ), 'jpc_remote_authorize_fail' );
109
		}
110
111
		if ( Jetpack::is_active() && Jetpack::is_user_connected( $request['state'] ) ) {
112
			return $this->error( new Jetpack_Error( 'already_connected', 'User already connected.', 400 ), 'jpc_remote_authorize_fail' );
113
		}
114
115
		$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
116
117
		if ( is_a( $verified, 'IXR_Error' ) ) {
118
			return $this->error( $verified, 'jpc_remote_authorize_fail' );
119
		}
120
121
		wp_set_current_user( $request['state'] );
122
123
		$client_server = new Jetpack_Client_Server;
124
		$result = $client_server->authorize( $request );
125
126
		if ( is_wp_error( $result ) ) {
127
			return $this->error( $result, 'jpc_remote_authorize_fail' );
128
		}
129
130
		Tracking::record_user_event( 'jpc_remote_authorize_success' );
131
132
		return array(
133
			'result' => $result,
134
		);
135
	}
136
137
	/**
138
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
139
	 * register this site so that a plan can be provisioned.
140
	 *
141
	 * @param array $request An array containing at minimum nonce and local_user keys.
142
	 *
143
	 * @return WP_Error|array
144
	 */
145
	public function remote_register( $request ) {
146
		Tracking::record_user_event( 'jpc_remote_register_begin', array() );
147
148
		$user = $this->fetch_and_verify_local_user( $request );
149
150
		if ( ! $user ) {
151
			return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_register_fail' );
152
		}
153
154
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
155
			return $this->error( $user, 'jpc_remote_register_fail' );
156
		}
157
158
		if ( empty( $request['nonce'] ) ) {
159
			return $this->error(
160
				new Jetpack_Error(
161
					'nonce_missing',
162
					__( 'The required "nonce" parameter is missing.', 'jetpack' ),
163
					400
164
				),
165
				'jpc_remote_register_fail'
166
			);
167
		}
168
169
		$nonce = sanitize_text_field( $request['nonce'] );
170
		unset( $request['nonce'] );
171
172
		$api_url  = Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'partner_provision_nonce_check' ) );
173
		$response = Jetpack_Client::_wp_remote_request(
174
			esc_url_raw( add_query_arg( 'nonce', $nonce, $api_url ) ),
175
			array( 'method' => 'GET' ),
176
			true
177
		);
178
179
		if (
180
			200 !== wp_remote_retrieve_response_code( $response ) ||
181
			'OK' !== trim( wp_remote_retrieve_body( $response ) )
182
		) {
183
			return $this->error(
184
				new Jetpack_Error(
185
					'invalid_nonce',
186
					__( 'There was an issue validating this request.', 'jetpack' ),
187
					400
188
				),
189
				'jpc_remote_register_fail'
190
			);
191
		}
192
193
		if ( ! Jetpack_Options::get_option( 'id' ) || ! Jetpack_Data::get_access_token() || ! empty( $request['force'] ) ) {
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack_Data::get_access_token() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
194
			wp_set_current_user( $user->ID );
195
196
			// This code mostly copied from Jetpack::admin_page_load.
197
			Jetpack::maybe_set_version_option();
198
			$registered = Jetpack::try_registration();
199
			if ( is_wp_error( $registered ) ) {
200
				return $this->error( $registered, 'jpc_remote_register_fail' );
201
			} elseif ( ! $registered ) {
202
				return $this->error(
203
					new Jetpack_Error(
204
						'registration_error',
205
						__( 'There was an unspecified error registering the site', 'jetpack' ),
206
						400
207
					),
208
					'jpc_remote_register_fail'
209
				);
210
			}
211
		}
212
213
		Tracking::record_user_event( 'jpc_remote_register_success' );
214
215
		return array(
216
			'client_id' => Jetpack_Options::get_option( 'id' )
217
		);
218
	}
219
220
	/**
221
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
222
	 * register this site so that a plan can be provisioned.
223
	 *
224
	 * @param array $request An array containing at minimum a nonce key and a local_username key.
225
	 *
226
	 * @return WP_Error|array
227
	 */
228
	public function remote_provision( $request ) {
229
		$user = $this->fetch_and_verify_local_user( $request );
230
231
		if ( ! $user ) {
232
			return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_provision_fail' );
233
		}
234
235
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
236
			return $this->error( $user, 'jpc_remote_provision_fail' );
237
		}
238
239
		$site_icon = get_site_icon_url();
240
241
		$auto_enable_sso = ( ! Jetpack::is_active() || Jetpack::is_module_active( 'sso' ) );
242
243
		/** This filter is documented in class.jetpack-cli.php */
244 View Code Duplication
		if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) {
245
			$redirect_uri = add_query_arg(
246
				array(
247
					'action'      => 'jetpack-sso',
248
					'redirect_to' => rawurlencode( admin_url() ),
249
				),
250
				wp_login_url() // TODO: come back to Jetpack dashboard?
251
			);
252
		} else {
253
			$redirect_uri = admin_url();
254
		}
255
256
		// Generate secrets.
257
		$role    = Jetpack::translate_user_to_role( $user );
258
		$secrets = Jetpack::init()->generate_secrets( 'authorize', $user->ID );
259
260
		$response = array(
261
			'jp_version'   => JETPACK__VERSION,
262
			'redirect_uri' => $redirect_uri,
263
			'user_id'      => $user->ID,
264
			'user_email'   => $user->user_email,
265
			'user_login'   => $user->user_login,
266
			'scope'        => Jetpack::sign_role( $role, $user->ID ),
267
			'secret'       => $secrets['secret_1'],
268
			'is_active'    => Jetpack::is_active(),
269
		);
270
271
		if ( $site_icon ) {
272
			$response['site_icon'] = $site_icon;
273
		}
274
275
		if ( ! empty( $request['onboarding'] ) ) {
276
			Jetpack::create_onboarding_token();
277
			$response['onboarding_token'] = Jetpack_Options::get_option( 'onboarding' );
278
		}
279
280
		return $response;
281
	}
282
283
	/**
284
	 * Given an array containing a local user identifier and a nonce, will attempt to fetch and set
285
	 * an access token for the given user.
286
	 *
287
	 * @param array $request An array containing local_user and nonce keys at minimum.
288
	 * @return mixed
289
	 */
290
	public function remote_connect( $request, $ixr_client = false ) {
291
		if ( Jetpack::is_active() ) {
292
			return $this->error(
293
				new WP_Error(
294
					'already_connected',
295
					__( 'Jetpack is already connected.', 'jetpack' ),
296
					400
297
				),
298
				'jpc_remote_connect_fail'
299
			);
300
		}
301
302
		$user = $this->fetch_and_verify_local_user( $request );
303
304
		if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
305
			return $this->error(
306
				new WP_Error(
307
					'input_error',
308
					__( 'Valid user is required.', 'jetpack' ),
309
					400
310
				),
311
				'jpc_remote_connect_fail'
312
			);
313
		}
314
315
		if ( empty( $request['nonce'] ) ) {
316
			return $this->error(
317
				new WP_Error(
318
					'input_error',
319
					__( 'A non-empty nonce must be supplied.', 'jetpack' ),
320
					400
321
				),
322
				'jpc_remote_connect_fail'
323
			);
324
		}
325
326
		if ( ! $ixr_client ) {
327
			Jetpack::load_xml_rpc_client();
328
			$ixr_client = new Jetpack_IXR_Client();
329
		}
330
		$ixr_client->query( 'jetpack.getUserAccessToken', array(
0 ignored issues
show
Bug introduced by
It seems like $ixr_client is not always an object, but can also be of type boolean. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
331
			'nonce'            => sanitize_text_field( $request['nonce'] ),
332
			'external_user_id' => $user->ID,
333
		) );
334
335
		$token = $ixr_client->isError() ? false : $ixr_client->getResponse();
336
		if ( empty( $token ) ) {
337
			return $this->error(
338
				new WP_Error(
339
					'token_fetch_failed',
340
					__( 'Failed to fetch user token from WordPress.com.', 'jetpack' ),
341
					400
342
				),
343
				'jpc_remote_connect_fail'
344
			);
345
		}
346
		$token = sanitize_text_field( $token );
347
348
		Jetpack::update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true );
349
350
		$this->do_post_authorization();
351
352
		return Jetpack::is_active();
353
	}
354
355
	private function fetch_and_verify_local_user( $request ) {
356
		if ( empty( $request['local_user'] ) ) {
357
			return $this->error(
358
				new Jetpack_Error(
359
					'local_user_missing',
360
					__( 'The required "local_user" parameter is missing.', 'jetpack' ),
361
					400
362
				),
363
				'jpc_remote_provision_fail'
364
			);
365
		}
366
367
		// local user is used to look up by login, email or ID
368
		$local_user_info = $request['local_user'];
369
370
		$user = get_user_by( 'login', $local_user_info );
371
372
		if ( ! $user ) {
373
			$user = get_user_by( 'email', $local_user_info );
374
		}
375
376
		if ( ! $user ) {
377
			$user = get_user_by( 'ID', $local_user_info );
378
		}
379
380
		return $user;
381
	}
382
383
	private function tracks_record_error( $name, $error, $user = null ) {
384
		if ( is_wp_error( $error ) ) {
385
			Tracking::record_user_event( $name, array(
386
				'error_code' => $error->get_error_code(),
387
				'error_message' => $error->get_error_message()
388
			), $user );
389
		} elseif( is_a( $error, 'IXR_Error' ) ) {
390
			Tracking::record_user_event( $name, array(
391
				'error_code' => $error->code,
392
				'error_message' => $error->message
393
			), $user );
394
		}
395
396
		return $error;
397
	}
398
399
	/**
400
	 * @return WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure
401
	 *
402
	 * Possible error_codes:
403
	 *
404
	 * verify_secret_1_missing
405
	 * verify_secret_1_malformed
406
	 * verify_secrets_missing: verification secrets are not found in database
407
	 * verify_secrets_incomplete: verification secrets are only partially found in database
408
	 * verify_secrets_expired: verification secrets have expired
409
	 * verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
410
	 * state_missing: required parameter of state not found
411
	 * state_malformed: state is not a digit
412
	 * invalid_state: state in request does not match the stored state
413
	 *
414
	 * The 'authorize' and 'register' actions have additional error codes
415
	 *
416
	 * Possible values for action are `authorize`, `publicize` and `register`.
417
	 *
418
	 * state_missing: a state ( user id ) was not supplied
419
	 * state_malformed: state is not the correct data type
420
	 * invalid_state: supplied state does not match the stored state
421
	 */
422
	function verify_action( $params ) {
423
		$action = $params[0];
424
		$verify_secret = $params[1];
425
		$state = isset( $params[2] ) ? $params[2] : '';
426
		$user = get_user_by( 'id', $state );
427
		$tracks_failure_event_name = '';
428
429
		if ( 'authorize' === $action ) {
430
			$tracks_failure_event_name = 'jpc_verify_authorize_fail';
431
			Tracking::record_user_event( 'jpc_verify_authorize_begin', array(), $user );
432
		}
433
		if ( 'publicize' === $action ) {
434
			// This action is used on a response from a direct XML-RPC done from WordPress.com
435
			$tracks_failure_event_name = 'jpc_verify_publicize_fail';
436
			Tracking::record_user_event( 'jpc_verify_publicize_begin', array(), $user );
437
		}
438
		if ( 'register' === $action ) {
439
			$tracks_failure_event_name = 'jpc_verify_register_fail';
440
			Tracking::record_user_event( 'jpc_verify_register_begin', array(), $user );
441
		}
442
443
		if ( empty( $verify_secret ) ) {
444
			return $this->error( new Jetpack_Error( 'verify_secret_1_missing', sprintf( 'The required "%s" parameter is missing.', 'secret_1' ), 400 ), $tracks_failure_event_name, $user );
445
		} else if ( ! is_string( $verify_secret ) ) {
446
			return $this->error( new Jetpack_Error( 'verify_secret_1_malformed', sprintf( 'The required "%s" parameter is malformed.', 'secret_1' ), 400 ), $tracks_failure_event_name, $user );
447
		} else if ( empty( $state ) ) {
448
			return $this->error( new Jetpack_Error( 'state_missing', sprintf( 'The required "%s" parameter is missing.', 'state' ), 400 ), $tracks_failure_event_name, $user );
449
		} else if ( ! ctype_digit( $state ) ) {
450
			return $this->error( new Jetpack_Error( 'state_malformed', sprintf( 'The required "%s" parameter is malformed.', 'state' ), 400 ), $tracks_failure_event_name, $user );
451
		}
452
453
		$secrets = Jetpack::get_secrets( $action, $state );
454
455
		if ( ! $secrets ) {
456
			Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
457
			return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification secrets not found', 400 ), $tracks_failure_event_name, $user );
458
		}
459
460
		if ( is_wp_error( $secrets ) ) {
461
			Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
462
			return $this->error( new Jetpack_Error( $secrets->get_error_code(), $secrets->get_error_message(), 400 ), $tracks_failure_event_name, $user );
463
		}
464
465
		if ( empty( $secrets['secret_1'] ) || empty( $secrets['secret_2'] ) || empty( $secrets['exp'] ) ) {
466
			Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
467
			return $this->error( new Jetpack_Error( 'verify_secrets_incomplete', 'Verification secrets are incomplete', 400 ), $tracks_failure_event_name, $user );
468
		}
469
470
		if ( ! hash_equals( $verify_secret, $secrets['secret_1'] ) ) {
471
			Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
472
			return $this->error( new Jetpack_Error( 'verify_secrets_mismatch', 'Secret mismatch', 400 ), $tracks_failure_event_name, $user );
473
		}
474
475
		Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
476
477
		if ( 'authorize' === $action ) {
478
			Tracking::record_user_event( 'jpc_verify_authorize_success', array(), $user );
479
		}
480
		if ( 'publicize' === $action ) {
481
			Tracking::record_user_event( 'jpc_verify_publicize_success', array(), $user );
482
		}
483
		if ( 'register' === $action ) {
484
			Tracking::record_user_event( 'jpc_verify_register_success', array(), $user );
485
		}
486
487
		return $secrets['secret_2'];
488
	}
489
490
	/**
491
	 * Wrapper for wp_authenticate( $username, $password );
492
	 *
493
	 * @return WP_User|bool
494
	 */
495
	function login() {
496
		Jetpack::init()->require_jetpack_authentication();
497
		$user = wp_authenticate( 'username', 'password' );
498
		if ( is_wp_error( $user ) ) {
499
			if ( 'authentication_failed' == $user->get_error_code() ) { // Generic error could mean most anything.
500
				$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
501
			} else {
502
				$this->error = $user;
503
			}
504
			return false;
505
		} else if ( !$user ) { // Shouldn't happen.
506
			$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
507
			return false;
508
		}
509
510
		return $user;
511
	}
512
513
	/**
514
	 * Returns the current error as an IXR_Error
515
	 *
516
	 * @return bool|IXR_Error
517
	 */
518
	function error( $error = null, $tracks_event_name = null, $user = null ) {
519
		// record using Tracks
520
		if ( null !== $tracks_event_name ) {
521
			$this->tracks_record_error( $tracks_event_name, $error, $user );
522
		}
523
524
		if ( !is_null( $error ) ) {
525
			$this->error = $error;
526
		}
527
528
		if ( is_wp_error( $this->error ) ) {
529
			$code = $this->error->get_error_data();
530
			if ( !$code ) {
531
				$code = -10520;
532
			}
533
			$message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() );
534
			return new IXR_Error( $code, $message );
535
		} else if ( is_a( $this->error, 'IXR_Error' ) ) {
536
			return $this->error;
537
		}
538
539
		return false;
540
	}
541
542
/* API Methods */
543
544
	/**
545
	 * Just authenticates with the given Jetpack credentials.
546
	 *
547
	 * @return string The current Jetpack version number
548
	 */
549
	function test_connection() {
550
		return JETPACK__VERSION;
551
	}
552
553
	function test_api_user_code( $args ) {
554
		$client_id = (int) $args[0];
555
		$user_id   = (int) $args[1];
556
		$nonce     = (string) $args[2];
557
		$verify    = (string) $args[3];
558
559
		if ( !$client_id || !$user_id || !strlen( $nonce ) || 32 !== strlen( $verify ) ) {
560
			return false;
561
		}
562
563
		$user = get_user_by( 'id', $user_id );
564
		if ( !$user || is_wp_error( $user ) ) {
565
			return false;
566
		}
567
568
		/* debugging
569
		error_log( "CLIENT: $client_id" );
570
		error_log( "USER:   $user_id" );
571
		error_log( "NONCE:  $nonce" );
572
		error_log( "VERIFY: $verify" );
573
		*/
574
575
		$jetpack_token = Jetpack_Data::get_access_token( $user_id );
0 ignored issues
show
Documentation introduced by
$user_id is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Deprecated Code introduced by
The method Jetpack_Data::get_access_token() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
576
577
		$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
578
		if ( !$api_user_code ) {
579
			return false;
580
		}
581
582
		$hmac = hash_hmac( 'md5', json_encode( (object) array(
583
			'client_id' => (int) $client_id,
584
			'user_id'   => (int) $user_id,
585
			'nonce'     => (string) $nonce,
586
			'code'      => (string) $api_user_code,
587
		) ), $jetpack_token->secret );
588
589
		if ( ! hash_equals( $hmac, $verify ) ) {
590
			return false;
591
		}
592
593
		return $user_id;
594
	}
595
596
	/**
597
	* Disconnect this blog from the connected wordpress.com account
598
	* @return boolean
599
	*/
600
	function disconnect_blog() {
601
602
		// For tracking
603
		if ( ! empty( $this->user->ID ) ) {
604
			wp_set_current_user( $this->user->ID );
605
		}
606
607
		Jetpack::log( 'disconnect' );
608
		Jetpack::disconnect();
609
610
		return true;
611
	}
612
613
	/**
614
	 * Unlink a user from WordPress.com
615
	 *
616
	 * This will fail if called by the Master User.
617
	 */
618
	function unlink_user() {
619
		Jetpack::log( 'unlink' );
620
		return Jetpack::unlink_user();
621
	}
622
623
	/**
624
	 * Returns any object that is able to be synced
625
	 */
626
	function sync_object( $args ) {
627
		// e.g. posts, post, 5
628
		list( $module_name, $object_type, $id ) = $args;
629
630
		$sync_module = Jetpack_Sync_Modules::get_module( $module_name );
631
		$codec = Jetpack_Sync_Sender::get_instance()->get_codec();
632
633
		return $codec->encode( $sync_module->get_object_by_id( $object_type, $id ) );
634
	}
635
636
	/**
637
	 * Returns the home URL and site URL for the current site which can be used on the WPCOM side for
638
	 * IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
639
	 * and the remote Jetpack site.
640
	 *
641
	 * @return array
642
	 */
643
	function validate_urls_for_idc_mitigation() {
644
		return array(
645
			'home'    => Jetpack_Sync_Functions::home_url(),
646
			'siteurl' => Jetpack_Sync_Functions::site_url(),
647
		);
648
	}
649
650
	/**
651
	 * Returns what features are available. Uses the slug of the module files.
652
	 *
653
	 * @return array
654
	 */
655 View Code Duplication
	function features_available() {
656
		$raw_modules = Jetpack::get_available_modules();
657
		$modules = array();
658
		foreach ( $raw_modules as $module ) {
659
			$modules[] = Jetpack::get_module_slug( $module );
660
		}
661
662
		return $modules;
663
	}
664
665
	/**
666
	 * Returns what features are enabled. Uses the slug of the modules files.
667
	 *
668
	 * @return array
669
	 */
670 View Code Duplication
	function features_enabled() {
671
		$raw_modules = Jetpack::get_active_modules();
672
		$modules = array();
673
		foreach ( $raw_modules as $module ) {
674
			$modules[] = Jetpack::get_module_slug( $module );
675
		}
676
677
		return $modules;
678
	}
679
680
	function update_attachment_parent( $args ) {
681
		$attachment_id = (int) $args[0];
682
		$parent_id     = (int) $args[1];
683
684
		return wp_update_post( array(
685
			'ID'          => $attachment_id,
686
			'post_parent' => $parent_id,
687
		) );
688
	}
689
690
	function json_api( $args = array() ) {
691
		$json_api_args = $args[0];
692
		$verify_api_user_args = $args[1];
693
694
		$method       = (string) $json_api_args[0];
695
		$url          = (string) $json_api_args[1];
696
		$post_body    = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2];
697
		$user_details = (array) $json_api_args[4];
698
		$locale       = (string) $json_api_args[5];
699
700
		if ( !$verify_api_user_args ) {
701
			$user_id = 0;
702
		} elseif ( 'internal' === $verify_api_user_args[0] ) {
703
			$user_id = (int) $verify_api_user_args[1];
704
			if ( $user_id ) {
705
				$user = get_user_by( 'id', $user_id );
706
				if ( !$user || is_wp_error( $user ) ) {
707
					return false;
708
				}
709
			}
710
		} else {
711
			$user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args );
712
			if ( !$user_id ) {
713
				return false;
714
			}
715
		}
716
717
		/* debugging
718
		error_log( "-- begin json api via jetpack debugging -- " );
719
		error_log( "METHOD: $method" );
720
		error_log( "URL: $url" );
721
		error_log( "POST BODY: $post_body" );
722
		error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) );
723
		error_log( "VERIFIED USER_ID: " . (int) $user_id );
724
		error_log( "-- end json api via jetpack debugging -- " );
725
		*/
726
727
		if ( 'en' !== $locale ) {
728
			// .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them.
729
			$new_locale = $locale;
730
			if ( strpos( $locale, '-' ) !== false ) {
731
				$locale_pieces = explode( '-', $locale );
732
				$new_locale = $locale_pieces[0];
733
				$new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : '';
734
			} else {
735
				// .com might pass 'fr' because thats what our language files are named as, where core seems
736
				// to do fr_FR - so try that if we don't think we can load the file.
737
				if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) {
738
					$new_locale =  $locale . '_' . strtoupper( $locale );
739
				}
740
			}
741
742
			if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) {
743
				unload_textdomain( 'default' );
744
				load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' );
745
			}
746
		}
747
748
		$old_user = wp_get_current_user();
749
		wp_set_current_user( $user_id );
750
751
		if ( $user_id ) {
752
			$token_key = false;
753
		} else {
754
			$jetpack   = Jetpack::init();
755
			$verified  = $jetpack->verify_xml_rpc_signature();
756
			$token_key = $verified['token_key'];
757
		}
758
759
		$token = Jetpack_Data::get_access_token( $user_id, $token_key );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack_Data::get_access_token() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
760
		if ( !$token || is_wp_error( $token ) ) {
761
			return false;
762
		}
763
764
		define( 'REST_API_REQUEST', true );
765
		define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
766
767
		// needed?
768
		require_once ABSPATH . 'wp-admin/includes/admin.php';
769
770
		require_once JETPACK__PLUGIN_DIR . 'class.json-api.php';
771
		$api = WPCOM_JSON_API::init( $method, $url, $post_body );
772
		$api->token_details['user'] = $user_details;
773
		require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
774
775
		$display_errors = ini_set( 'display_errors', 0 );
776
		ob_start();
777
		$content_type = $api->serve( false );
0 ignored issues
show
Unused Code introduced by
$content_type is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
778
		$output = ob_get_clean();
779
		ini_set( 'display_errors', $display_errors );
780
781
		$nonce = wp_generate_password( 10, false );
782
		$hmac  = hash_hmac( 'md5', $nonce . $output, $token->secret );
783
784
		wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 );
785
786
		return array(
787
			(string) $output,
788
			(string) $nonce,
789
			(string) $hmac,
790
		);
791
	}
792
793
	/**
794
	 * Handles authorization actions after connecting a site, such as enabling modules.
795
	 *
796
	 * This do_post_authorization() is used in this class, as opposed to calling
797
	 * Jetpack::handle_post_authorization_actions() directly so that we can mock this method as necessary.
798
	 *
799
	 * @return void
800
	 */
801
	public function do_post_authorization() {
802
		/** This filter is documented in class.jetpack-cli.php */
803
		$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
804
		Jetpack::handle_post_authorization_actions( $enable_sso, false, false );
805
	}
806
}
807