Completed
Push — add/xmlrpc-user-api ( d35894...e9f504 )
by
unknown
467:32 queued 460:49
created

Jetpack_XMLRPC_Server::features_enabled()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 9
Ratio 100 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 9
loc 9
rs 9.9666
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.getUser'           => array( $this, 'get_user' ),
28
			'jetpack.remoteRegister'    => array( $this, 'remote_register' ),
29
			'jetpack.remoteProvision'   => array( $this, 'remote_provision' ),
30
		);
31
32
		$this->user = $this->login();
33
34
		if ( $this->user ) {
35
			$jetpack_methods = array_merge( $jetpack_methods, array(
36
				'jetpack.testConnection'    => array( $this, 'test_connection' ),
37
				'jetpack.testAPIUserCode'   => array( $this, 'test_api_user_code' ),
38
				'jetpack.featuresAvailable' => array( $this, 'features_available' ),
39
				'jetpack.featuresEnabled'   => array( $this, 'features_enabled' ),
40
				'jetpack.disconnectBlog'    => array( $this, 'disconnect_blog' ),
41
				'jetpack.unlinkUser'        => array( $this, 'unlink_user' ),
42
				'jetpack.syncObject'        => array( $this, 'sync_object' ),
43
				'jetpack.idcUrlValidation'  => array( $this, 'validate_urls_for_idc_mitigation' ),
44
			) );
45
46
			if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
47
				$jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject'];
48
				$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
49
			}
50
51
			/**
52
			 * Filters the XML-RPC methods available to Jetpack for authenticated users.
53
			 *
54
			 * @since 1.1.0
55
			 *
56
			 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
57
			 * @param array $core_methods Available core XML-RPC methods.
58
			 * @param WP_User $user Information about a given WordPress user.
59
			 */
60
			$jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $this->user );
61
		}
62
63
		/**
64
		 * Filters the XML-RPC methods available to Jetpack for unauthenticated users.
65
		 *
66
		 * @since 3.0.0
67
		 *
68
		 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
69
		 * @param array $core_methods Available core XML-RPC methods.
70
		 */
71
		return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods );
72
	}
73
74
	/**
75
	 * Whitelist of the bootstrap XML-RPC methods
76
	 */
77
	function bootstrap_xmlrpc_methods() {
78
		return array(
79
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
80
			'jetpack.remoteRegister' => array( $this, 'remote_register' ),
81
		);
82
	}
83
84
	function authorize_xmlrpc_methods() {
85
		return array(
86
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
87
		);
88
	}
89
90
	function provision_xmlrpc_methods() {
91
		return array(
92
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
93
			'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
94
			'jetpack.remoteConnect'   => array( $this, 'remote_connect' ),
95
			'jetpack.getUser'         => array( $this, 'get_user' ),
96
		);
97
	}
98
99
	/**
100
	 * Used to verify whether a local user exists and what role they have.
101
	 *
102
	 * @param int|string $user_id The local User's ID, username, or email address.
103
	 *
104
	 * @return array|IXR_Error Information about the user, or error if no such user found:
105
	 *                         roles:     string[] The user's rols.
106
	 *                         login:     string   The user's username.
107
	 *                         email_hash string[] The MD5 hash of the user's normalized email address.
108
	 *                         caps       string[] The user's capabilities.
109
	 *                         allcaps    string[] The user's granular capabilities, merged from role capabilities.
110
	 *                         token_key  string   The Token Key of the user's Jetpack token. Empty string if none.
111
	 */
112
	function get_user( $user_id ) {
113
		if ( ! $user_id ) {
114
			return $this->error(
115
				new Jetpack_Error(
116
					'invalid_user',
117
					__( 'Invalid user identifier.', 'jetpack' ),
118
					400
119
				),
120
				'jpc_get_user_fail'
121
			);
122
		}
123
124
		$user = $this->get_user_by_anything( $user_id );
125
126
		if ( ! $user ) {
127
			return $this->error(
128
				new Jetpack_Error(
129
					'user_unknown',
130
					__( 'User not found.', 'jetpack' ),
131
					404
132
				),
133
				'jpc_get_user_fail'
134
			);
135
		}
136
137
		$connection = Jetpack::connection();
138
		$user_token = $connection->get_access_token( $user->ID );
139
140
		if ( $user_token ) {
141
			list( $user_token_key, $user_token_private ) = explode( '.', $user_token->secret );
0 ignored issues
show
Unused Code introduced by
The assignment to $user_token_private is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
142
			if ( $user_token_key === $user_token->secret ) {
143
				$user_token_key = '';
144
			}
145
		} else {
146
			$user_token_key = '';
147
		}
148
149
		return array(
150
			'id'         => $user->ID,
151
			'login'      => $user->user_login,
152
			'email_hash' => md5( strtolower( trim( $user->user_email ) ) ),
153
			'roles'      => $user->roles,
154
			'caps'       => $user->caps,
155
			'allcaps'    => $user->allcaps,
156
			'token_key'  => $user_token_key,
157
		);
158
	}
159
160
	function remote_authorize( $request ) {
161
		$user = get_user_by( 'id', $request['state'] );
162
		Tracking::record_user_event( 'jpc_remote_authorize_begin', array(), $user );
163
164
		foreach( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
165
			if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
166
				return $this->error( new Jetpack_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ), 'jpc_remote_authorize_fail' );
167
			}
168
		}
169
170
		if ( ! $user ) {
171
			return $this->error( new Jetpack_Error( 'user_unknown', 'User not found.', 404 ), 'jpc_remote_authorize_fail' );
172
		}
173
174
		if ( Jetpack::is_active() && Jetpack::is_user_connected( $request['state'] ) ) {
175
			return $this->error( new Jetpack_Error( 'already_connected', 'User already connected.', 400 ), 'jpc_remote_authorize_fail' );
176
		}
177
178
		$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
179
180
		if ( is_a( $verified, 'IXR_Error' ) ) {
181
			return $this->error( $verified, 'jpc_remote_authorize_fail' );
182
		}
183
184
		wp_set_current_user( $request['state'] );
185
186
		$client_server = new Jetpack_Client_Server;
187
		$result = $client_server->authorize( $request );
188
189
		if ( is_wp_error( $result ) ) {
190
			return $this->error( $result, 'jpc_remote_authorize_fail' );
191
		}
192
193
		Tracking::record_user_event( 'jpc_remote_authorize_success' );
194
195
		return array(
196
			'result' => $result,
197
		);
198
	}
199
200
	/**
201
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
202
	 * register this site so that a plan can be provisioned.
203
	 *
204
	 * @param array $request An array containing at minimum nonce and local_user keys.
205
	 *
206
	 * @return WP_Error|array
207
	 */
208
	public function remote_register( $request ) {
209
		Tracking::record_user_event( 'jpc_remote_register_begin', array() );
210
211
		$user = $this->fetch_and_verify_local_user( $request );
212
213
		if ( ! $user ) {
214
			return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_register_fail' );
215
		}
216
217
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
218
			return $this->error( $user, 'jpc_remote_register_fail' );
219
		}
220
221
		if ( empty( $request['nonce'] ) ) {
222
			return $this->error(
223
				new Jetpack_Error(
224
					'nonce_missing',
225
					__( 'The required "nonce" parameter is missing.', 'jetpack' ),
226
					400
227
				),
228
				'jpc_remote_register_fail'
229
			);
230
		}
231
232
		$nonce = sanitize_text_field( $request['nonce'] );
233
		unset( $request['nonce'] );
234
235
		$api_url  = Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'partner_provision_nonce_check' ) );
236
		$response = Jetpack_Client::_wp_remote_request(
237
			esc_url_raw( add_query_arg( 'nonce', $nonce, $api_url ) ),
238
			array( 'method' => 'GET' ),
239
			true
240
		);
241
242
		if (
243
			200 !== wp_remote_retrieve_response_code( $response ) ||
244
			'OK' !== trim( wp_remote_retrieve_body( $response ) )
245
		) {
246
			return $this->error(
247
				new Jetpack_Error(
248
					'invalid_nonce',
249
					__( 'There was an issue validating this request.', 'jetpack' ),
250
					400
251
				),
252
				'jpc_remote_register_fail'
253
			);
254
		}
255
256
		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...
257
			wp_set_current_user( $user->ID );
258
259
			// This code mostly copied from Jetpack::admin_page_load.
260
			Jetpack::maybe_set_version_option();
261
			$registered = Jetpack::try_registration();
262
			if ( is_wp_error( $registered ) ) {
263
				return $this->error( $registered, 'jpc_remote_register_fail' );
264
			} elseif ( ! $registered ) {
265
				return $this->error(
266
					new Jetpack_Error(
267
						'registration_error',
268
						__( 'There was an unspecified error registering the site', 'jetpack' ),
269
						400
270
					),
271
					'jpc_remote_register_fail'
272
				);
273
			}
274
		}
275
276
		Tracking::record_user_event( 'jpc_remote_register_success' );
277
278
		return array(
279
			'client_id' => Jetpack_Options::get_option( 'id' )
280
		);
281
	}
282
283
	/**
284
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
285
	 * register this site so that a plan can be provisioned.
286
	 *
287
	 * @param array $request An array containing at minimum a nonce key and a local_username key.
288
	 *
289
	 * @return WP_Error|array
290
	 */
291
	public function remote_provision( $request ) {
292
		$user = $this->fetch_and_verify_local_user( $request );
293
294
		if ( ! $user ) {
295
			return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_provision_fail' );
296
		}
297
298
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
299
			return $this->error( $user, 'jpc_remote_provision_fail' );
300
		}
301
302
		$site_icon = get_site_icon_url();
303
304
		$auto_enable_sso = ( ! Jetpack::is_active() || Jetpack::is_module_active( 'sso' ) );
305
306
		/** This filter is documented in class.jetpack-cli.php */
307 View Code Duplication
		if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) {
308
			$redirect_uri = add_query_arg(
309
				array(
310
					'action'      => 'jetpack-sso',
311
					'redirect_to' => rawurlencode( admin_url() ),
312
				),
313
				wp_login_url() // TODO: come back to Jetpack dashboard?
314
			);
315
		} else {
316
			$redirect_uri = admin_url();
317
		}
318
319
		// Generate secrets.
320
		$role    = Jetpack::translate_user_to_role( $user );
321
		$secrets = Jetpack::init()->generate_secrets( 'authorize', $user->ID );
322
323
		$response = array(
324
			'jp_version'   => JETPACK__VERSION,
325
			'redirect_uri' => $redirect_uri,
326
			'user_id'      => $user->ID,
327
			'user_email'   => $user->user_email,
328
			'user_login'   => $user->user_login,
329
			'scope'        => Jetpack::sign_role( $role, $user->ID ),
330
			'secret'       => $secrets['secret_1'],
331
			'is_active'    => Jetpack::is_active(),
332
		);
333
334
		if ( $site_icon ) {
335
			$response['site_icon'] = $site_icon;
336
		}
337
338
		if ( ! empty( $request['onboarding'] ) ) {
339
			Jetpack::create_onboarding_token();
340
			$response['onboarding_token'] = Jetpack_Options::get_option( 'onboarding' );
341
		}
342
343
		return $response;
344
	}
345
346
	/**
347
	 * Given an array containing a local user identifier and a nonce, will attempt to fetch and set
348
	 * an access token for the given user.
349
	 *
350
	 * @param array $request An array containing local_user and nonce keys at minimum.
351
	 * @return mixed
352
	 */
353
	public function remote_connect( $request, $ixr_client = false ) {
354
		if ( Jetpack::is_active() ) {
355
			return $this->error(
356
				new WP_Error(
357
					'already_connected',
358
					__( 'Jetpack is already connected.', 'jetpack' ),
359
					400
360
				),
361
				'jpc_remote_connect_fail'
362
			);
363
		}
364
365
		$user = $this->fetch_and_verify_local_user( $request );
366
367
		if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
368
			return $this->error(
369
				new WP_Error(
370
					'input_error',
371
					__( 'Valid user is required.', 'jetpack' ),
372
					400
373
				),
374
				'jpc_remote_connect_fail'
375
			);
376
		}
377
378
		if ( empty( $request['nonce'] ) ) {
379
			return $this->error(
380
				new WP_Error(
381
					'input_error',
382
					__( 'A non-empty nonce must be supplied.', 'jetpack' ),
383
					400
384
				),
385
				'jpc_remote_connect_fail'
386
			);
387
		}
388
389
		if ( ! $ixr_client ) {
390
			Jetpack::load_xml_rpc_client();
391
			$ixr_client = new Jetpack_IXR_Client();
392
		}
393
		$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...
394
			'nonce'            => sanitize_text_field( $request['nonce'] ),
395
			'external_user_id' => $user->ID,
396
		) );
397
398
		$token = $ixr_client->isError() ? false : $ixr_client->getResponse();
399
		if ( empty( $token ) ) {
400
			return $this->error(
401
				new WP_Error(
402
					'token_fetch_failed',
403
					__( 'Failed to fetch user token from WordPress.com.', 'jetpack' ),
404
					400
405
				),
406
				'jpc_remote_connect_fail'
407
			);
408
		}
409
		$token = sanitize_text_field( $token );
410
411
		Jetpack::update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true );
412
413
		$this->do_post_authorization();
414
415
		return Jetpack::is_active();
416
	}
417
418
	private function fetch_and_verify_local_user( $request ) {
419
		if ( empty( $request['local_user'] ) ) {
420
			return $this->error(
421
				new Jetpack_Error(
422
					'local_user_missing',
423
					__( 'The required "local_user" parameter is missing.', 'jetpack' ),
424
					400
425
				),
426
				'jpc_remote_provision_fail'
427
			);
428
		}
429
430
		// local user is used to look up by login, email or ID
431
		$local_user_info = $request['local_user'];
432
433
		return $this->get_user_by_anything( $local_user_info );
434
	}
435
436
	private function get_user_by_anything( $user_id ) {
437
		$user = get_user_by( 'login', $user_id );
438
439
		if ( ! $user ) {
440
			$user = get_user_by( 'email', $user_id );
441
		}
442
443
		if ( ! $user ) {
444
			$user = get_user_by( 'ID', $user_id );
445
		}
446
447
		return $user;
448
	}
449
450
	private function tracks_record_error( $name, $error, $user = null ) {
451
		if ( is_wp_error( $error ) ) {
452
			Tracking::record_user_event( $name, array(
453
				'error_code' => $error->get_error_code(),
454
				'error_message' => $error->get_error_message()
455
			), $user );
456
		} elseif( is_a( $error, 'IXR_Error' ) ) {
457
			Tracking::record_user_event( $name, array(
458
				'error_code' => $error->code,
459
				'error_message' => $error->message
460
			), $user );
461
		}
462
463
		return $error;
464
	}
465
466
	/**
467
	 * @return WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure
468
	 *
469
	 * Possible error_codes:
470
	 *
471
	 * verify_secret_1_missing
472
	 * verify_secret_1_malformed
473
	 * verify_secrets_missing: verification secrets are not found in database
474
	 * verify_secrets_incomplete: verification secrets are only partially found in database
475
	 * verify_secrets_expired: verification secrets have expired
476
	 * verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
477
	 * state_missing: required parameter of state not found
478
	 * state_malformed: state is not a digit
479
	 * invalid_state: state in request does not match the stored state
480
	 *
481
	 * The 'authorize' and 'register' actions have additional error codes
482
	 *
483
	 * Possible values for action are `authorize`, `publicize` and `register`.
484
	 *
485
	 * state_missing: a state ( user id ) was not supplied
486
	 * state_malformed: state is not the correct data type
487
	 * invalid_state: supplied state does not match the stored state
488
	 */
489
	function verify_action( $params ) {
490
		$action = $params[0];
491
		$verify_secret = $params[1];
492
		$state = isset( $params[2] ) ? $params[2] : '';
493
		$user = get_user_by( 'id', $state );
494
		$tracks_failure_event_name = '';
495
496
		if ( 'authorize' === $action ) {
497
			$tracks_failure_event_name = 'jpc_verify_authorize_fail';
498
			Tracking::record_user_event( 'jpc_verify_authorize_begin', array(), $user );
499
		}
500
		if ( 'publicize' === $action ) {
501
			// This action is used on a response from a direct XML-RPC done from WordPress.com
502
			$tracks_failure_event_name = 'jpc_verify_publicize_fail';
503
			Tracking::record_user_event( 'jpc_verify_publicize_begin', array(), $user );
504
		}
505
		if ( 'register' === $action ) {
506
			$tracks_failure_event_name = 'jpc_verify_register_fail';
507
			Tracking::record_user_event( 'jpc_verify_register_begin', array(), $user );
508
		}
509
510
		if ( empty( $verify_secret ) ) {
511
			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 );
512
		} else if ( ! is_string( $verify_secret ) ) {
513
			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 );
514
		} else if ( empty( $state ) ) {
515
			return $this->error( new Jetpack_Error( 'state_missing', sprintf( 'The required "%s" parameter is missing.', 'state' ), 400 ), $tracks_failure_event_name, $user );
516
		} else if ( ! ctype_digit( $state ) ) {
517
			return $this->error( new Jetpack_Error( 'state_malformed', sprintf( 'The required "%s" parameter is malformed.', 'state' ), 400 ), $tracks_failure_event_name, $user );
518
		}
519
520
		$secrets = Jetpack::get_secrets( $action, $state );
521
522
		if ( ! $secrets ) {
523
			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...
524
			return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification secrets not found', 400 ), $tracks_failure_event_name, $user );
525
		}
526
527
		if ( is_wp_error( $secrets ) ) {
528
			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...
529
			return $this->error( new Jetpack_Error( $secrets->get_error_code(), $secrets->get_error_message(), 400 ), $tracks_failure_event_name, $user );
530
		}
531
532
		if ( empty( $secrets['secret_1'] ) || empty( $secrets['secret_2'] ) || empty( $secrets['exp'] ) ) {
533
			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...
534
			return $this->error( new Jetpack_Error( 'verify_secrets_incomplete', 'Verification secrets are incomplete', 400 ), $tracks_failure_event_name, $user );
535
		}
536
537
		if ( ! hash_equals( $verify_secret, $secrets['secret_1'] ) ) {
538
			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...
539
			return $this->error( new Jetpack_Error( 'verify_secrets_mismatch', 'Secret mismatch', 400 ), $tracks_failure_event_name, $user );
540
		}
541
542
		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...
543
544
		if ( 'authorize' === $action ) {
545
			Tracking::record_user_event( 'jpc_verify_authorize_success', array(), $user );
546
		}
547
		if ( 'publicize' === $action ) {
548
			Tracking::record_user_event( 'jpc_verify_publicize_success', array(), $user );
549
		}
550
		if ( 'register' === $action ) {
551
			Tracking::record_user_event( 'jpc_verify_register_success', array(), $user );
552
		}
553
554
		return $secrets['secret_2'];
555
	}
556
557
	/**
558
	 * Wrapper for wp_authenticate( $username, $password );
559
	 *
560
	 * @return WP_User|bool
561
	 */
562
	function login() {
563
		Jetpack::init()->require_jetpack_authentication();
564
		$user = wp_authenticate( 'username', 'password' );
565
		if ( is_wp_error( $user ) ) {
566
			if ( 'authentication_failed' == $user->get_error_code() ) { // Generic error could mean most anything.
567
				$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
568
			} else {
569
				$this->error = $user;
570
			}
571
			return false;
572
		} else if ( !$user ) { // Shouldn't happen.
573
			$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
574
			return false;
575
		}
576
577
		return $user;
578
	}
579
580
	/**
581
	 * Returns the current error as an IXR_Error
582
	 *
583
	 * @return bool|IXR_Error
584
	 */
585
	function error( $error = null, $tracks_event_name = null, $user = null ) {
586
		// record using Tracks
587
		if ( null !== $tracks_event_name ) {
588
			$this->tracks_record_error( $tracks_event_name, $error, $user );
589
		}
590
591
		if ( !is_null( $error ) ) {
592
			$this->error = $error;
593
		}
594
595
		if ( is_wp_error( $this->error ) ) {
596
			$code = $this->error->get_error_data();
597
			if ( !$code ) {
598
				$code = -10520;
599
			}
600
			$message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() );
601
			return new IXR_Error( $code, $message );
602
		} else if ( is_a( $this->error, 'IXR_Error' ) ) {
603
			return $this->error;
604
		}
605
606
		return false;
607
	}
608
609
/* API Methods */
610
611
	/**
612
	 * Just authenticates with the given Jetpack credentials.
613
	 *
614
	 * @return string The current Jetpack version number
615
	 */
616
	function test_connection() {
617
		return JETPACK__VERSION;
618
	}
619
620
	function test_api_user_code( $args ) {
621
		$client_id = (int) $args[0];
622
		$user_id   = (int) $args[1];
623
		$nonce     = (string) $args[2];
624
		$verify    = (string) $args[3];
625
626
		if ( !$client_id || !$user_id || !strlen( $nonce ) || 32 !== strlen( $verify ) ) {
627
			return false;
628
		}
629
630
		$user = get_user_by( 'id', $user_id );
631
		if ( !$user || is_wp_error( $user ) ) {
632
			return false;
633
		}
634
635
		/* debugging
636
		error_log( "CLIENT: $client_id" );
637
		error_log( "USER:   $user_id" );
638
		error_log( "NONCE:  $nonce" );
639
		error_log( "VERIFY: $verify" );
640
		*/
641
642
		$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...
643
644
		$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
645
		if ( !$api_user_code ) {
646
			return false;
647
		}
648
649
		$hmac = hash_hmac( 'md5', json_encode( (object) array(
650
			'client_id' => (int) $client_id,
651
			'user_id'   => (int) $user_id,
652
			'nonce'     => (string) $nonce,
653
			'code'      => (string) $api_user_code,
654
		) ), $jetpack_token->secret );
655
656
		if ( ! hash_equals( $hmac, $verify ) ) {
657
			return false;
658
		}
659
660
		return $user_id;
661
	}
662
663
	/**
664
	* Disconnect this blog from the connected wordpress.com account
665
	* @return boolean
666
	*/
667
	function disconnect_blog() {
668
669
		// For tracking
670
		if ( ! empty( $this->user->ID ) ) {
671
			wp_set_current_user( $this->user->ID );
672
		}
673
674
		Jetpack::log( 'disconnect' );
675
		Jetpack::disconnect();
676
677
		return true;
678
	}
679
680
	/**
681
	 * Unlink a user from WordPress.com
682
	 *
683
	 * This will fail if called by the Master User.
684
	 */
685
	function unlink_user() {
686
		Jetpack::log( 'unlink' );
687
		return Jetpack::unlink_user();
688
	}
689
690
	/**
691
	 * Returns any object that is able to be synced
692
	 */
693
	function sync_object( $args ) {
694
		// e.g. posts, post, 5
695
		list( $module_name, $object_type, $id ) = $args;
696
697
		$sync_module = Jetpack_Sync_Modules::get_module( $module_name );
698
		$codec = Jetpack_Sync_Sender::get_instance()->get_codec();
699
700
		return $codec->encode( $sync_module->get_object_by_id( $object_type, $id ) );
701
	}
702
703
	/**
704
	 * Returns the home URL and site URL for the current site which can be used on the WPCOM side for
705
	 * IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
706
	 * and the remote Jetpack site.
707
	 *
708
	 * @return array
709
	 */
710
	function validate_urls_for_idc_mitigation() {
711
		return array(
712
			'home'    => Jetpack_Sync_Functions::home_url(),
713
			'siteurl' => Jetpack_Sync_Functions::site_url(),
714
		);
715
	}
716
717
	/**
718
	 * Returns what features are available. Uses the slug of the module files.
719
	 *
720
	 * @return array
721
	 */
722 View Code Duplication
	function features_available() {
723
		$raw_modules = Jetpack::get_available_modules();
724
		$modules = array();
725
		foreach ( $raw_modules as $module ) {
726
			$modules[] = Jetpack::get_module_slug( $module );
727
		}
728
729
		return $modules;
730
	}
731
732
	/**
733
	 * Returns what features are enabled. Uses the slug of the modules files.
734
	 *
735
	 * @return array
736
	 */
737 View Code Duplication
	function features_enabled() {
738
		$raw_modules = Jetpack::get_active_modules();
739
		$modules = array();
740
		foreach ( $raw_modules as $module ) {
741
			$modules[] = Jetpack::get_module_slug( $module );
742
		}
743
744
		return $modules;
745
	}
746
747
	function update_attachment_parent( $args ) {
748
		$attachment_id = (int) $args[0];
749
		$parent_id     = (int) $args[1];
750
751
		return wp_update_post( array(
752
			'ID'          => $attachment_id,
753
			'post_parent' => $parent_id,
754
		) );
755
	}
756
757
	function json_api( $args = array() ) {
758
		$json_api_args = $args[0];
759
		$verify_api_user_args = $args[1];
760
761
		$method       = (string) $json_api_args[0];
762
		$url          = (string) $json_api_args[1];
763
		$post_body    = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2];
764
		$user_details = (array) $json_api_args[4];
765
		$locale       = (string) $json_api_args[5];
766
767
		if ( !$verify_api_user_args ) {
768
			$user_id = 0;
769
		} elseif ( 'internal' === $verify_api_user_args[0] ) {
770
			$user_id = (int) $verify_api_user_args[1];
771
			if ( $user_id ) {
772
				$user = get_user_by( 'id', $user_id );
773
				if ( !$user || is_wp_error( $user ) ) {
774
					return false;
775
				}
776
			}
777
		} else {
778
			$user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args );
779
			if ( !$user_id ) {
780
				return false;
781
			}
782
		}
783
784
		/* debugging
785
		error_log( "-- begin json api via jetpack debugging -- " );
786
		error_log( "METHOD: $method" );
787
		error_log( "URL: $url" );
788
		error_log( "POST BODY: $post_body" );
789
		error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) );
790
		error_log( "VERIFIED USER_ID: " . (int) $user_id );
791
		error_log( "-- end json api via jetpack debugging -- " );
792
		*/
793
794
		if ( 'en' !== $locale ) {
795
			// .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them.
796
			$new_locale = $locale;
797
			if ( strpos( $locale, '-' ) !== false ) {
798
				$locale_pieces = explode( '-', $locale );
799
				$new_locale = $locale_pieces[0];
800
				$new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : '';
801
			} else {
802
				// .com might pass 'fr' because thats what our language files are named as, where core seems
803
				// to do fr_FR - so try that if we don't think we can load the file.
804
				if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) {
805
					$new_locale =  $locale . '_' . strtoupper( $locale );
806
				}
807
			}
808
809
			if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) {
810
				unload_textdomain( 'default' );
811
				load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' );
812
			}
813
		}
814
815
		$old_user = wp_get_current_user();
816
		wp_set_current_user( $user_id );
817
818
		if ( $user_id ) {
819
			$token_key = false;
820
		} else {
821
			$jetpack   = Jetpack::init();
822
			$verified  = $jetpack->verify_xml_rpc_signature();
823
			$token_key = $verified['token_key'];
824
		}
825
826
		$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...
827
		if ( !$token || is_wp_error( $token ) ) {
828
			return false;
829
		}
830
831
		define( 'REST_API_REQUEST', true );
832
		define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
833
834
		// needed?
835
		require_once ABSPATH . 'wp-admin/includes/admin.php';
836
837
		require_once JETPACK__PLUGIN_DIR . 'class.json-api.php';
838
		$api = WPCOM_JSON_API::init( $method, $url, $post_body );
839
		$api->token_details['user'] = $user_details;
840
		require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
841
842
		$display_errors = ini_set( 'display_errors', 0 );
843
		ob_start();
844
		$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...
845
		$output = ob_get_clean();
846
		ini_set( 'display_errors', $display_errors );
847
848
		$nonce = wp_generate_password( 10, false );
849
		$hmac  = hash_hmac( 'md5', $nonce . $output, $token->secret );
850
851
		wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 );
852
853
		return array(
854
			(string) $output,
855
			(string) $nonce,
856
			(string) $hmac,
857
		);
858
	}
859
860
	/**
861
	 * Handles authorization actions after connecting a site, such as enabling modules.
862
	 *
863
	 * This do_post_authorization() is used in this class, as opposed to calling
864
	 * Jetpack::handle_post_authorization_actions() directly so that we can mock this method as necessary.
865
	 *
866
	 * @return void
867
	 */
868
	public function do_post_authorization() {
869
		/** This filter is documented in class.jetpack-cli.php */
870
		$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
871
		Jetpack::handle_post_authorization_actions( $enable_sso, false, false );
872
	}
873
}
874