Completed
Push — try/remote-provision ( 40c913...e3842c )
by
unknown
08:42
created

Jetpack_XMLRPC_Server::error()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 14
nc 16
nop 3
dl 0
loc 23
rs 8.5906
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Just a sack of functions.  Not actually an IXR_Server
5
 */
6
class Jetpack_XMLRPC_Server {
7
	/**
8
	 * The current error object
9
	 */
10
	public $error = null;
11
12
	/**
13
	 * The current user
14
	 */
15
	public $user = null;
16
17
	/**
18
	 * Whitelist of the XML-RPC methods available to the Jetpack Server. If the
19
	 * user is not authenticated (->login()) then the methods are never added,
20
	 * so they will get a "does not exist" error.
21
	 */
22
	function xmlrpc_methods( $core_methods ) {
23
		$jetpack_methods = array(
24
			'jetpack.jsonAPI'      => array( $this, 'json_api' ),
25
			'jetpack.verifyAction' => array( $this, 'verify_action' ),
26
		);
27
28
		$this->user = $this->login();
29
30
		if ( $this->user ) {
31
			$jetpack_methods = array_merge( $jetpack_methods, array(
32
				'jetpack.testConnection'    => array( $this, 'test_connection' ),
33
				'jetpack.testAPIUserCode'   => array( $this, 'test_api_user_code' ),
34
				'jetpack.featuresAvailable' => array( $this, 'features_available' ),
35
				'jetpack.featuresEnabled'   => array( $this, 'features_enabled' ),
36
				'jetpack.disconnectBlog'    => array( $this, 'disconnect_blog' ),
37
				'jetpack.unlinkUser'        => array( $this, 'unlink_user' ),
38
				'jetpack.syncObject'        => array( $this, 'sync_object' ),
39
				'jetpack.idcUrlValidation'  => array( $this, 'validate_urls_for_idc_mitigation' ),
40
			) );
41
42
			if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
43
				$jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject'];
44
				$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
45
			}
46
47
			/**
48
			 * Filters the XML-RPC methods available to Jetpack for authenticated users.
49
			 *
50
			 * @since 1.1.0
51
			 *
52
			 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
53
			 * @param array $core_methods Available core XML-RPC methods.
54
			 * @param WP_User $user Information about a given WordPress user.
55
			 */
56
			$jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $this->user );
57
		}
58
59
		/**
60
		 * Filters the XML-RPC methods available to Jetpack for unauthenticated users.
61
		 *
62
		 * @since 3.0.0
63
		 *
64
		 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
65
		 * @param array $core_methods Available core XML-RPC methods.
66
		 */
67
		return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods );
68
	}
69
70
	/**
71
	 * Whitelist of the bootstrap XML-RPC methods
72
	 */
73
	function bootstrap_xmlrpc_methods() {
74
		return array(
75
			'jetpack.verifyRegistration' => array( $this, 'verify_registration' ),
76
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
77
			'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
78
		);
79
	}
80
81
	function authorize_xmlrpc_methods() {
82
		return array(
83
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
84
		);
85
	}
86
87
	function remote_authorize( $request ) {
88
		$user = get_user_by( 'id', $request['state'] );
89
		JetpackTracking::record_user_event( 'jpc_remote_authorize_begin', array(), $user );
90
91
		foreach( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
92
			if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
93
				return $this->error( new Jetpack_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ), 'jpc_remote_authorize_fail' );
94
			}
95
		}
96
97
		if ( ! $user ) {
98
			return $this->error( new Jetpack_Error( 'user_unknown', 'User not found.', 404 ), 'jpc_remote_authorize_fail' );
99
		}
100
101
		if ( Jetpack::is_active() && Jetpack::is_user_connected( $request['state'] ) ) {
102
			return $this->error( new Jetpack_Error( 'already_connected', 'User already connected.', 400 ), 'jpc_remote_authorize_fail' );
103
		}
104
105
		$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
106
107
		if ( is_a( $verified, 'IXR_Error' ) ) {
108
			return $this->error( $verified, 'jpc_remote_authorize_fail' );
109
		}
110
111
		wp_set_current_user( $request['state'] );
112
113
		$client_server = new Jetpack_Client_Server;
114
		$result = $client_server->authorize( $request );
115
116
		if ( is_wp_error( $result ) ) {
117
			return $this->error( $result, 'jpc_remote_authorize_fail' );
118
		}
119
120
		JetpackTracking::record_user_event( 'jpc_remote_authorize_success' );
121
122
		$response = array(
123
			'result' => $result,
124
		);
125
		return $response;
126
	}
127
128
	function remote_provision( $request ) {
129
		$access_token = $request['access_token'];
130
		$wpcom_user_id = $request['wpcom_user_id'];
131
		$local_username = $request['local_username'];
132
		$user = get_user_by( 'login', $local_username );
133
134
		if ( ! $user ) {
135
			$user = get_user_by( 'email', $local_username );
136
		}
137
138
		if ( ! $user ) {
139
			return $this->error( 'user_not_found', "The user wasn't found - d'oh!" );
140
		}
141
142
		require_once JETPACK__PLUGIN_DIR . '_inc/class.jetpack-provision.php';
143
144
		wp_set_current_user( $user->ID );
145
146
		$args = array(
147
			'wpcom_user_id' => $wpcom_user_id
148
		);
149
150
		$result = Jetpack_Provision::partner_provision( $access_token, $args );
151
152
		if ( isset( $result->access_token ) && ! empty( $result->access_token ) ) {
153
			return array( 'success' => true );
154
		} else {
155
			return $result;
156
		}
157
	}
158
159
	private function tracks_record_error( $name, $error, $user = null ) {
160
		if ( is_wp_error( $error ) ) {
161
			JetpackTracking::record_user_event( $name, array(
162
				'error_code' => $error->get_error_code(),
163
				'error_message' => $error->get_error_message()
164
			), $user );
165
		} elseif( is_a( $error, 'IXR_Error' ) ) {
166
			JetpackTracking::record_user_event( $name, array(
167
				'error_code' => $error->code,
168
				'error_message' => $error->message
169
			), $user );
170
		}
171
172
		return $error;
173
	}
174
175
	/**
176
	* Verifies that Jetpack.WordPress.com received a registration request from this site
177
	*/
178
	function verify_registration( $data ) {
179
		// failure modes will be recorded in tracks in the verify_action method
180
		return $this->verify_action( array( 'register', $data[0], $data[1] ) );
181
	}
182
183
	/**
184
	 * @return WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure
185
	 *
186
	 * Possible error_codes:
187
	 *
188
	 * verify_secret_1_missing
189
	 * verify_secret_1_malformed
190
	 * verify_secrets_missing: verification secrets are not found in database
191
	 * verify_secrets_incomplete: verification secrets are only partially found in database
192
	 * verify_secrets_expired: verification secrets have expired
193
	 * verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
194
	 * state_missing: required parameter of state not found
195
	 * state_malformed: state is not a digit
196
	 * invalid_state: state in request does not match the stored state
197
	 *
198
	 * The 'authorize' and 'register' actions have additional error codes
199
	 *
200
	 * state_missing: a state ( user id ) was not supplied
201
	 * state_malformed: state is not the correct data type
202
	 * invalid_state: supplied state does not match the stored state
203
	 */
204
	function verify_action( $params ) {
205
		$action = $params[0];
206
		$verify_secret = $params[1];
207
		$state = isset( $params[2] ) ? $params[2] : '';
208
		$user = get_user_by( 'id', $state );
209
		JetpackTracking::record_user_event( 'jpc_verify_' . $action . '_begin', array(), $user );
210
		$tracks_failure_event_name = 'jpc_verify_' . $action . '_fail';
211
212
		if ( empty( $verify_secret ) ) {
213
			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 );
214
		} else if ( ! is_string( $verify_secret ) ) {
215
			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 );
216
		} else if ( empty( $state ) ) {
217
			return $this->error( new Jetpack_Error( 'state_missing', sprintf( 'The required "%s" parameter is missing.', 'state' ), 400 ), $tracks_failure_event_name, $user );
218
		} else if ( ! ctype_digit( $state ) ) {
219
			return $this->error( new Jetpack_Error( 'state_malformed', sprintf( 'The required "%s" parameter is malformed.', 'state' ), 400 ), $tracks_failure_event_name, $user );
220
		}
221
222
		$secrets = Jetpack::get_secrets( $action, $state );
223
224
		if ( ! $secrets ) {
225
			Jetpack::delete_secrets( $action, $state );
226
			return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification secrets not found', 400 ), $tracks_failure_event_name, $user );
227
		}
228
229
		if ( is_wp_error( $secrets ) ) {
230
			Jetpack::delete_secrets( $action, $state );
231
			return $this->error( new Jetpack_Error( $secrets->get_error_code(), $secrets->get_error_message(), 400 ), $tracks_failure_event_name, $user );
232
		}
233
234
		if ( empty( $secrets['secret_1'] ) || empty( $secrets['secret_2'] ) || empty( $secrets['exp'] ) ) {
235
			Jetpack::delete_secrets( $action, $state );
236
			return $this->error( new Jetpack_Error( 'verify_secrets_incomplete', 'Verification secrets are incomplete', 400 ), $tracks_failure_event_name, $user );
237
		}
238
239
		if ( ! hash_equals( $verify_secret, $secrets['secret_1'] ) ) {
240
			Jetpack::delete_secrets( $action, $state );
241
			return $this->error( new Jetpack_Error( 'verify_secrets_mismatch', 'Secret mismatch', 400 ), $tracks_failure_event_name, $user );
242
		}
243
244
		Jetpack::delete_secrets( $action, $state );
245
246
		JetpackTracking::record_user_event( 'jpc_verify_' . $action . '_success', array(), $user );
247
248
		return $secrets['secret_2'];
249
	}
250
251
	/**
252
	 * Wrapper for wp_authenticate( $username, $password );
253
	 *
254
	 * @return WP_User|bool
255
	 */
256
	function login() {
257
		Jetpack::init()->require_jetpack_authentication();
258
		$user = wp_authenticate( 'username', 'password' );
259
		if ( is_wp_error( $user ) ) {
260
			if ( 'authentication_failed' == $user->get_error_code() ) { // Generic error could mean most anything.
261
				$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
262
			} else {
263
				$this->error = $user;
264
			}
265
			return false;
266
		} else if ( !$user ) { // Shouldn't happen.
267
			$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
268
			return false;
269
		}
270
271
		return $user;
272
	}
273
274
	/**
275
	 * Returns the current error as an IXR_Error
276
	 *
277
	 * @return bool|IXR_Error
278
	 */
279
	function error( $error = null, $tracks_event_name = null, $user = null ) {
280
		// record using Tracks
281
		if ( null !== $tracks_event_name ) {
282
			$this->tracks_record_error( $tracks_event_name, $error, $user );
283
		}
284
285
		if ( !is_null( $error ) ) {
286
			$this->error = $error;
287
		}
288
289
		if ( is_wp_error( $this->error ) ) {
290
			$code = $this->error->get_error_data();
291
			if ( !$code ) {
292
				$code = -10520;
293
			}
294
			$message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() );
295
			return new IXR_Error( $code, $message );
296
		} else if ( is_a( $this->error, 'IXR_Error' ) ) {
297
			return $this->error;
298
		}
299
300
		return false;
301
	}
302
303
/* API Methods */
304
305
	/**
306
	 * Just authenticates with the given Jetpack credentials.
307
	 *
308
	 * @return string The current Jetpack version number
309
	 */
310
	function test_connection() {
311
		return JETPACK__VERSION;
312
	}
313
314
	function test_api_user_code( $args ) {
315
		$client_id = (int) $args[0];
316
		$user_id   = (int) $args[1];
317
		$nonce     = (string) $args[2];
318
		$verify    = (string) $args[3];
319
320
		if ( !$client_id || !$user_id || !strlen( $nonce ) || 32 !== strlen( $verify ) ) {
321
			return false;
322
		}
323
324
		$user = get_user_by( 'id', $user_id );
325
		if ( !$user || is_wp_error( $user ) ) {
326
			return false;
327
		}
328
329
		/* debugging
330
		error_log( "CLIENT: $client_id" );
331
		error_log( "USER:   $user_id" );
332
		error_log( "NONCE:  $nonce" );
333
		error_log( "VERIFY: $verify" );
334
		*/
335
336
		$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...
337
338
		$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
339
		if ( !$api_user_code ) {
340
			return false;
341
		}
342
343
		$hmac = hash_hmac( 'md5', json_encode( (object) array(
344
			'client_id' => (int) $client_id,
345
			'user_id'   => (int) $user_id,
346
			'nonce'     => (string) $nonce,
347
			'code'      => (string) $api_user_code,
348
		) ), $jetpack_token->secret );
349
350
		if ( ! hash_equals( $hmac, $verify ) ) {
351
			return false;
352
		}
353
354
		return $user_id;
355
	}
356
357
	/**
358
	* Disconnect this blog from the connected wordpress.com account
359
	* @return boolean
360
	*/
361
	function disconnect_blog() {
362
363
		// For tracking
364
		if ( ! empty( $this->user->ID ) ) {
365
			wp_set_current_user( $this->user->ID );
366
		}
367
368
		Jetpack::log( 'disconnect' );
369
		Jetpack::disconnect();
370
371
		return true;
372
	}
373
374
	/**
375
	 * Unlink a user from WordPress.com
376
	 *
377
	 * This will fail if called by the Master User.
378
	 */
379
	function unlink_user() {
380
		Jetpack::log( 'unlink' );
381
		return Jetpack::unlink_user();
382
	}
383
384
	/**
385
	 * Returns any object that is able to be synced
386
	 */
387
	function sync_object( $args ) {
388
		// e.g. posts, post, 5
389
		list( $module_name, $object_type, $id ) = $args;
390
		require_once dirname( __FILE__ ) . '/sync/class.jetpack-sync-modules.php';
391
		require_once dirname( __FILE__ ) . '/sync/class.jetpack-sync-sender.php';
392
393
		$sync_module = Jetpack_Sync_Modules::get_module( $module_name );
394
		$codec = Jetpack_Sync_Sender::get_instance()->get_codec();
395
396
		return $codec->encode( $sync_module->get_object_by_id( $object_type, $id ) );
397
	}
398
399
	/**
400
	 * Returns the home URL and site URL for the current site which can be used on the WPCOM side for
401
	 * IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
402
	 * and the remote Jetpack site.
403
	 *
404
	 * @return array
405
	 */
406
	function validate_urls_for_idc_mitigation() {
407
		require_once JETPACK__PLUGIN_DIR . 'sync/class.jetpack-sync-functions.php';
408
		return array(
409
			'home'    => Jetpack_Sync_Functions::home_url(),
410
			'siteurl' => Jetpack_Sync_Functions::site_url(),
411
		);
412
	}
413
414
	/**
415
	 * Returns what features are available. Uses the slug of the module files.
416
	 *
417
	 * @return array
418
	 */
419 View Code Duplication
	function features_available() {
420
		$raw_modules = Jetpack::get_available_modules();
421
		$modules = array();
422
		foreach ( $raw_modules as $module ) {
423
			$modules[] = Jetpack::get_module_slug( $module );
424
		}
425
426
		return $modules;
427
	}
428
429
	/**
430
	 * Returns what features are enabled. Uses the slug of the modules files.
431
	 *
432
	 * @return array
433
	 */
434 View Code Duplication
	function features_enabled() {
435
		$raw_modules = Jetpack::get_active_modules();
436
		$modules = array();
437
		foreach ( $raw_modules as $module ) {
438
			$modules[] = Jetpack::get_module_slug( $module );
439
		}
440
441
		return $modules;
442
	}
443
444
	function update_attachment_parent( $args ) {
445
		$attachment_id = (int) $args[0];
446
		$parent_id     = (int) $args[1];
447
448
		return wp_update_post( array(
449
			'ID'          => $attachment_id,
450
			'post_parent' => $parent_id,
451
		) );
452
	}
453
454
	function json_api( $args = array() ) {
455
		$json_api_args = $args[0];
456
		$verify_api_user_args = $args[1];
457
458
		$method       = (string) $json_api_args[0];
459
		$url          = (string) $json_api_args[1];
460
		$post_body    = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2];
461
		$user_details = (array) $json_api_args[4];
462
		$locale       = (string) $json_api_args[5];
463
464
		if ( !$verify_api_user_args ) {
465
			$user_id = 0;
466
		} elseif ( 'internal' === $verify_api_user_args[0] ) {
467
			$user_id = (int) $verify_api_user_args[1];
468
			if ( $user_id ) {
469
				$user = get_user_by( 'id', $user_id );
470
				if ( !$user || is_wp_error( $user ) ) {
471
					return false;
472
				}
473
			}
474
		} else {
475
			$user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args );
476
			if ( !$user_id ) {
477
				return false;
478
			}
479
		}
480
481
		/* debugging
482
		error_log( "-- begin json api via jetpack debugging -- " );
483
		error_log( "METHOD: $method" );
484
		error_log( "URL: $url" );
485
		error_log( "POST BODY: $post_body" );
486
		error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) );
487
		error_log( "VERIFIED USER_ID: " . (int) $user_id );
488
		error_log( "-- end json api via jetpack debugging -- " );
489
		*/
490
491
		if ( 'en' !== $locale ) {
492
			// .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them.
493
			$new_locale = $locale;
494
			if ( strpos( $locale, '-' ) !== false ) {
495
				$locale_pieces = explode( '-', $locale );
496
				$new_locale = $locale_pieces[0];
497
				$new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : '';
498
			} else {
499
				// .com might pass 'fr' because thats what our language files are named as, where core seems
500
				// to do fr_FR - so try that if we don't think we can load the file.
501
				if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) {
502
					$new_locale =  $locale . '_' . strtoupper( $locale );
503
				}
504
			}
505
506
			if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) {
507
				unload_textdomain( 'default' );
508
				load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' );
509
			}
510
		}
511
512
		$old_user = wp_get_current_user();
513
		wp_set_current_user( $user_id );
514
515
		$token = Jetpack_Data::get_access_token( get_current_user_id() );
516
		if ( !$token || is_wp_error( $token ) ) {
517
			return false;
518
		}
519
520
		define( 'REST_API_REQUEST', true );
521
		define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
522
523
		// needed?
524
		require_once ABSPATH . 'wp-admin/includes/admin.php';
525
526
		require_once JETPACK__PLUGIN_DIR . 'class.json-api.php';
527
		$api = WPCOM_JSON_API::init( $method, $url, $post_body );
528
		$api->token_details['user'] = $user_details;
529
		require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
530
531
		$display_errors = ini_set( 'display_errors', 0 );
532
		ob_start();
533
		$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...
534
		$output = ob_get_clean();
535
		ini_set( 'display_errors', $display_errors );
536
537
		$nonce = wp_generate_password( 10, false );
538
		$hmac  = hash_hmac( 'md5', $nonce . $output, $token->secret );
539
540
		wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 );
541
542
		return array(
543
			(string) $output,
544
			(string) $nonce,
545
			(string) $hmac,
546
		);
547
	}
548
}
549