Completed
Push — add/get-object-api-for-sync ( 3aa2c5 )
by
unknown
123:54 queued 113:39
created

Jetpack_XMLRPC_Server::verify_action()   C

Complexity

Conditions 16
Paths 22

Size

Total Lines 51
Code Lines 32

Duplication

Lines 8
Ratio 15.69 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 16
eloc 32
c 1
b 0
f 0
nc 22
nop 1
dl 8
loc 51
rs 5.6194

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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