Completed
Push — add/tracks ( a0fac3...1c285b )
by
unknown
09:13
created

Jetpack_XMLRPC_Server::get_comments()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 8
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 1
dl 8
loc 8
rs 9.4285
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
	 * Whitelist of the XML-RPC methods available to the Jetpack Server. If the
14
	 * user is not authenticated (->login()) then the methods are never added,
15
	 * so they will get a "does not exist" error.
16
	 */
17
	function xmlrpc_methods( $core_methods ) {
18
		$jetpack_methods = array(
19
			'jetpack.jsonAPI'      => array( $this, 'json_api' ),
20
			'jetpack.verifyAction' => array( $this, 'verify_action' ),
21
		);
22
23
		$user = $this->login();
24
25
		if ( $user ) {
26
			$jetpack_methods = array_merge( $jetpack_methods, array(
27
				'jetpack.testConnection'    => array( $this, 'test_connection' ),
28
				'jetpack.testAPIUserCode'   => array( $this, 'test_api_user_code' ),
29
				'jetpack.featuresAvailable' => array( $this, 'features_available' ),
30
				'jetpack.featuresEnabled'   => array( $this, 'features_enabled' ),
31
				'jetpack.getPost'           => array( $this, 'get_post' ),
32
				'jetpack.getPosts'          => array( $this, 'get_posts' ),
33
				'jetpack.getComment'        => array( $this, 'get_comment' ),
34
				'jetpack.getComments'       => array( $this, 'get_comments' ),
35
				'jetpack.disconnectBlog'    => array( $this, 'disconnect_blog' ),
36
				'jetpack.unlinkUser'        => array( $this, 'unlink_user' ),
37
			) );
38
39
			if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
40
				$jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject'];
41
				$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
42
			}
43
44
			/**
45
			 * Filters the XML-RPC methods available to Jetpack for authenticated users.
46
			 *
47
			 * @since 1.1.0
48
			 *
49
			 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
50
			 * @param array $core_methods Available core XML-RPC methods.
51
			 * @param WP_User $user Information about a given WordPress user.
52
			 */
53
			$jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $user );
54
		}
55
56
		/**
57
		 * Filters the XML-RPC methods available to Jetpack for unauthenticated users.
58
		 *
59
		 * @since 3.0.0
60
		 *
61
		 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
62
		 * @param array $core_methods Available core XML-RPC methods.
63
		 */
64
		return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods );
65
	}
66
67
	/**
68
	 * Whitelist of the bootstrap XML-RPC methods
69
	 */
70
	function bootstrap_xmlrpc_methods() {
71
		return array(
72
			'jetpack.verifyRegistration' => array( $this, 'verify_registration' ),
73
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
74
		);
75
	}
76
77
	function authorize_xmlrpc_methods() {
78
		return array(
79
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
80
			'jetpack.activateManage'    => array( $this, 'activate_manage' ),
81
		);
82
	}
83
84
	function activate_manage( $request ) {
85 View Code Duplication
		foreach( array( 'secret', 'state' ) as $required ) {
86
			if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
87
				return $this->error( new Jetpack_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ) );
88
			}
89
		}
90
		$verified = $this->verify_action( array( 'activate_manage', $request['secret'], $request['state'] ) );
91
		if ( is_a( $verified, 'IXR_Error' ) ) {
92
			return $verified;
93
		}
94
		$activated = Jetpack::activate_module( 'manage', false, false );
95
		if ( false === $activated || ! Jetpack::is_module_active( 'manage' ) ) {
96
			return $this->error( new Jetpack_Error( 'activation_error', 'There was an error while activating the module.', 500 ) );
97
		}
98
		return 'active';
99
	}
100
101
	function remote_authorize( $request ) {
102 View Code Duplication
		foreach( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
103
			if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
104
				return $this->error( new Jetpack_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ) );
105
			}
106
		}
107
108
		if ( ! get_user_by( 'id', $request['state'] ) ) {
109
			return $this->error( new Jetpack_Error( 'user_unknown', 'User not found.', 404 ) );
110
		}
111
112
		if ( Jetpack::is_active() && Jetpack::is_user_connected( $request['state'] ) ) {
113
			return $this->error( new Jetpack_Error( 'already_connected', 'User already connected.', 400 ) );
114
		}
115
116
		$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
117
118
		if ( is_a( $verified, 'IXR_Error' ) ) {
119
			return $verified;
120
		}
121
122
		wp_set_current_user( $request['state'] );
123
124
		$client_server = new Jetpack_Client_Server;
125
		$result = $client_server->authorize( $request );
126
127
		if ( is_wp_error( $result ) ) {
128
			return $this->error( $result );
129
		}
130
		// Creates a new secret, allowing someone to activate the manage module for up to 1 day after authorization.
131
		$secrets = Jetpack::init()->generate_secrets( 'activate_manage', DAY_IN_SECONDS );
132
		@list( $secret ) = explode( ':', $secrets );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
133
		$response = array(
134
			'result' => $result,
135
			'activate_manage' => $secret,
136
		);
137
		return $response;
138
	}
139
140
	/**
141
	* Verifies that Jetpack.WordPress.com received a registration request from this site
142
	*/
143
	function verify_registration( $data ) {
144
		return $this->verify_action( array( 'register', $data[0], $data[1] ) );
145
	}
146
147
	/**
148
	 * @return WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure
149
	 *
150
	 * Possible error_codes:
151
	 *
152
	 * verify_secret_1_missing
153
	 * verify_secret_1_malformed
154
	 * verify_secrets_missing: No longer have verification secrets stored
155
	 * verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
156
	 *
157
	 * The 'authorize' and 'register' actions have additional error codes
158
	 *
159
	 * state_missing: a state ( user id ) was not supplied
160
	 * state_malformed: state is not the correct data type
161
	 * invalid_state: supplied state does not match the stored state
162
	 */
163
	function verify_action( $params ) {
164
		$action = $params[0];
165
		$verify_secret = $params[1];
166
		$state = isset( $params[2] ) ? $params[2] : '';
167
168
		if ( empty( $verify_secret ) ) {
169
			return $this->error( new Jetpack_Error( 'verify_secret_1_missing', sprintf( 'The required "%s" parameter is missing.', 'secret_1' ), 400 ) );
170
		} else if ( ! is_string( $verify_secret ) ) {
171
			return $this->error( new Jetpack_Error( 'verify_secret_1_malformed', sprintf( 'The required "%s" parameter is malformed.', 'secret_1' ), 400 ) );
172
		}
173
174
		$secrets = Jetpack_Options::get_option( $action );
175
		if ( !$secrets || is_wp_error( $secrets ) ) {
176
			Jetpack_Options::delete_option( $action );
177
			return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification took too long', 400 ) );
178
		}
179
180
		@list( $secret_1, $secret_2, $secret_eol, $user_id ) = explode( ':', $secrets );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
181
182
		if ( empty( $secret_1 ) || empty( $secret_2 ) || empty( $secret_eol ) || $secret_eol < time() ) {
183
			Jetpack_Options::delete_option( $action );
184
			return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification took too long', 400 ) );
185
		}
186
187
		if ( ! hash_equals( $verify_secret, $secret_1 ) ) {
188
			Jetpack_Options::delete_option( $action );
189
			return $this->error( new Jetpack_Error( 'verify_secrets_mismatch', 'Secret mismatch', 400 ) );
190
		}
191
192
		if ( in_array( $action, array( 'authorize', 'register' ) ) ) {
193
			// 'authorize' and 'register' actions require further testing
194
			if ( empty( $state ) ) {
195
				return $this->error( new Jetpack_Error( 'state_missing', sprintf( 'The required "%s" parameter is missing.', 'state' ), 400 ) );
196
			} else if ( ! ctype_digit( $state ) ) {
197
				return $this->error( new Jetpack_Error( 'state_malformed', sprintf( 'The required "%s" parameter is malformed.', 'state' ), 400 ) );
198
			}
199
			if ( empty( $user_id ) || $user_id !== $state ) {
200
				Jetpack_Options::delete_option( $action );
201
				return $this->error( new Jetpack_Error( 'invalid_state', 'State is invalid', 400 ) );
202
			}
203
		}
204
205
		Jetpack_Options::delete_option( $action );
206
207
		return $secret_2;
208
	}
209
210
	/**
211
	 * Wrapper for wp_authenticate( $username, $password );
212
	 *
213
	 * @return WP_User|IXR_Error
214
	 */
215
	function login() {
216
		Jetpack::init()->require_jetpack_authentication();
217
		$user = wp_authenticate( 'username', 'password' );
218
		if ( is_wp_error( $user ) ) {
219
			if ( 'authentication_failed' == $user->get_error_code() ) { // Generic error could mean most anything.
220
				$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
221
			} else {
222
				$this->error = $user;
223
			}
224
			return false;
225
		} else if ( !$user ) { // Shouldn't happen.
226
			$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
227
			return false;
228
		}
229
230
		return $user;
231
	}
232
233
	/**
234
	 * Returns the current error as an IXR_Error
235
	 *
236
	 * @return null|IXR_Error
237
	 */
238
	function error( $error = null ) {
239
		if ( !is_null( $error ) ) {
240
			$this->error = $error;
241
		}
242
243
		if ( is_wp_error( $this->error ) ) {
244
			$code = $this->error->get_error_data();
245
			if ( !$code ) {
246
				$code = -10520;
247
			}
248
			$message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() );
249
			return new IXR_Error( $code, $message );
250
		} else if ( is_a( $this->error, 'IXR_Error' ) ) {
251
			return $this->error;
252
		}
253
254
		return false;
255
	}
256
257
/* API Methods */
258
259
	/**
260
	 * Just authenticates with the given Jetpack credentials.
261
	 *
262
	 * @return bool|IXR_Error
263
	 */
264
	function test_connection() {
265
		return JETPACK__VERSION;
266
	}
267
268
	function test_api_user_code( $args ) {
269
		$client_id = (int) $args[0];
270
		$user_id   = (int) $args[1];
271
		$nonce     = (string) $args[2];
272
		$verify    = (string) $args[3];
273
274
		if ( !$client_id || !$user_id || !strlen( $nonce ) || 32 !== strlen( $verify ) ) {
275
			return false;
276
		}
277
278
		$user = get_user_by( 'id', $user_id );
279
		if ( !$user || is_wp_error( $user ) ) {
280
			return false;
281
		}
282
283
		/* debugging
284
		error_log( "CLIENT: $client_id" );
285
		error_log( "USER:   $user_id" );
286
		error_log( "NONCE:  $nonce" );
287
		error_log( "VERIFY: $verify" );
288
		*/
289
290
		$jetpack_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
291
292
		$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
293
		if ( !$api_user_code ) {
294
			return false;
295
		}
296
297
		$hmac = hash_hmac( 'md5', json_encode( (object) array(
298
			'client_id' => (int) $client_id,
299
			'user_id'   => (int) $user_id,
300
			'nonce'     => (string) $nonce,
301
			'code'      => (string) $api_user_code,
302
		) ), $jetpack_token->secret );
303
304
		if ( $hmac !== $verify ) {
305
			return false;
306
		}
307
308
		return $user_id;
309
	}
310
311
	/**
312
	* Disconnect this blog from the connected wordpress.com account
313
	* @return boolean
314
	*/
315
	function disconnect_blog() {
316
		Jetpack::log( 'disconnect' );
317
		Jetpack::disconnect();
318
319
		return true;
320
	}
321
322
	/**
323
	 * Unlink a user from WordPress.com
324
	 *
325
	 * This will fail if called by the Master User.
326
	 */
327
	function unlink_user() {
328
		Jetpack::log( 'unlink' );
329
		return Jetpack::unlink_user();
330
	}
331
332
	/**
333
	 * Returns what features are available. Uses the slug of the module files.
334
	 *
335
	 * @return array|IXR_Error
336
	 */
337 View Code Duplication
	function features_available() {
338
		$raw_modules = Jetpack::get_available_modules();
339
		$modules = array();
340
		foreach ( $raw_modules as $module ) {
341
			$modules[] = Jetpack::get_module_slug( $module );
342
		}
343
344
		return $modules;
345
	}
346
347
	/**
348
	 * Returns what features are enabled. Uses the slug of the modules files.
349
	 *
350
	 * @return array|IXR_Error
351
	 */
352 View Code Duplication
	function features_enabled() {
353
		$raw_modules = Jetpack::get_active_modules();
354
		$modules = array();
355
		foreach ( $raw_modules as $module ) {
356
			$modules[] = Jetpack::get_module_slug( $module );
357
		}
358
359
		return $modules;
360
	}
361
362
	function get_post( $id ) {
363
		if ( !$id = (int) $id ) {
364
			return false;
365
		}
366
367
		$jetpack = Jetpack::init();
368
369
		$post = $jetpack->sync->get_post( $id );
370
		return $post;
371
	}
372
373 View Code Duplication
	function get_posts( $args ) {
374
		list( $post_ids ) = $args;
375
		$post_ids = array_map( 'intval', (array) $post_ids );
376
		$jp = Jetpack::init();
377
		$sync_data = $jp->sync->get_content( array( 'posts' => $post_ids ) );
378
379
		return $sync_data;
380
	}
381
382
	function get_comment( $id ) {
383
		if ( !$id = (int) $id ) {
384
			return false;
385
		}
386
387
		$jetpack = Jetpack::init();
388
389
		$comment = $jetpack->sync->get_comment( $id );
390
		if ( !is_array( $comment ) )
391
			return false;
392
393
		$post = $jetpack->sync->get_post( $comment['comment_post_ID'] );
394
		if ( !$post ) {
395
			return false;
396
		}
397
398
		return $comment;
399
	}
400
401 View Code Duplication
	function get_comments( $args ) {
402
		list( $comment_ids ) = $args;
403
		$comment_ids = array_map( 'intval', (array) $comment_ids );
404
		$jp = Jetpack::init();
405
		$sync_data = $jp->sync->get_content( array( 'comments' => $comment_ids ) );
406
407
		return $sync_data;
408
	}
409
410
	function update_attachment_parent( $args ) {
411
		$attachment_id = (int) $args[0];
412
		$parent_id     = (int) $args[1];
413
414
		return wp_update_post( array(
415
			'ID'          => $attachment_id,
416
			'post_parent' => $parent_id,
417
		) );
418
	}
419
420
	function json_api( $args = array() ) {
421
		$json_api_args = $args[0];
422
		$verify_api_user_args = $args[1];
423
424
		$method       = (string) $json_api_args[0];
425
		$url          = (string) $json_api_args[1];
426
		$post_body    = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2];
427
		$user_details = (array) $json_api_args[4];
428
		$locale       = (string) $json_api_args[5];
429
430
		if ( !$verify_api_user_args ) {
431
			$user_id = 0;
432
		} elseif ( 'internal' === $verify_api_user_args[0] ) {
433
			$user_id = (int) $verify_api_user_args[1];
434
			if ( $user_id ) {
435
				$user = get_user_by( 'id', $user_id );
436
				if ( !$user || is_wp_error( $user ) ) {
437
					return false;
438
				}
439
			}
440
		} else {
441
			$user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args );
442
			if ( !$user_id ) {
443
				return false;
444
			}
445
		}
446
447
		/* debugging
448
		error_log( "-- begin json api via jetpack debugging -- " );
449
		error_log( "METHOD: $method" );
450
		error_log( "URL: $url" );
451
		error_log( "POST BODY: $post_body" );
452
		error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) );
453
		error_log( "VERIFIED USER_ID: " . (int) $user_id );
454
		error_log( "-- end json api via jetpack debugging -- " );
455
		*/
456
457
		if ( 'en' !== $locale ) {
458
			// .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them.
459
			$new_locale = $locale;
460
			if ( strpos( $locale, '-' ) !== false ) {
461
				$pieces = explode( '-', $locale );
0 ignored issues
show
Unused Code introduced by
$pieces 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...
462
				$new_locale = $locale_pieces[0];
0 ignored issues
show
Bug introduced by
The variable $locale_pieces does not exist. Did you mean $locale?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
463
				$new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : '';
0 ignored issues
show
Bug introduced by
The variable $locale_pieces does not exist. Did you mean $locale?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
464
			} else {
465
				// .com might pass 'fr' because thats what our language files are named as, where core seems
466
				// to do fr_FR - so try that if we don't think we can load the file.
467
				if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) {
468
					$new_locale =  $locale . '_' . strtoupper( $locale );
469
				}
470
			}
471
472
			if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) {
473
				unload_textdomain( 'default' );
474
				load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' );
475
			}
476
		}
477
478
		$old_user = wp_get_current_user();
479
		wp_set_current_user( $user_id );
480
481
		$token = Jetpack_Data::get_access_token( get_current_user_id() );
482
		if ( !$token || is_wp_error( $token ) ) {
483
			return false;
484
		}
485
486
		define( 'REST_API_REQUEST', true );
487
		define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
488
489
		// needed?
490
		require_once ABSPATH . 'wp-admin/includes/admin.php';
491
492
		require_once JETPACK__PLUGIN_DIR . 'class.json-api.php';
493
		$api = WPCOM_JSON_API::init( $method, $url, $post_body );
494
		$api->token_details['user'] = $user_details;
495
		require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
496
497
		$display_errors = ini_set( 'display_errors', 0 );
498
		ob_start();
499
		$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...
500
		$output = ob_get_clean();
501
		ini_set( 'display_errors', $display_errors );
502
503
		$nonce = wp_generate_password( 10, false );
504
		$hmac  = hash_hmac( 'md5', $nonce . $output, $token->secret );
505
506
		wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 );
507
508
		return array(
509
			(string) $output,
510
			(string) $nonce,
511
			(string) $hmac,
512
		);
513
	}
514
}
515