Completed
Push — update/connection-package-xmlr... ( ca82eb...259e19 )
by
unknown
15:26 queued 05:56
created

Jetpack_XMLRPC_Server::disconnect_blog()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 20
rs 9.6
c 0
b 0
f 0
1
<?php
2
/**
3
 * Jetpack XMLRPC Server.
4
 *
5
 * @package automattic/jetpack-connection
6
 */
7
8
use Automattic\Jetpack\Connection\Client;
9
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
10
use Automattic\Jetpack\Connection\Secrets;
11
use Automattic\Jetpack\Connection\Tokens;
12
use Automattic\Jetpack\Roles;
13
use Automattic\Jetpack\Sync\Functions;
14
use Automattic\Jetpack\Sync\Sender;
15
16
/**
17
 * Just a sack of functions.  Not actually an IXR_Server
18
 */
19
class Jetpack_XMLRPC_Server {
20
	/**
21
	 * The current error object
22
	 *
23
	 * @var \WP_Error
24
	 */
25
	public $error = null;
26
27
	/**
28
	 * The current user
29
	 *
30
	 * @var \WP_User
31
	 */
32
	public $user = null;
33
34
	/**
35
	 * The connection manager object.
36
	 *
37
	 * @var Automattic\Jetpack\Connection\Manager
38
	 */
39
	private $connection;
40
41
	/**
42
	 * Creates a new XMLRPC server object.
43
	 */
44
	public function __construct() {
45
		$this->connection = new Connection_Manager();
46
	}
47
48
	/**
49
	 * Whitelist of the XML-RPC methods available to the Jetpack Server. If the
50
	 * user is not authenticated (->login()) then the methods are never added,
51
	 * so they will get a "does not exist" error.
52
	 *
53
	 * @param array $core_methods Core XMLRPC methods.
54
	 */
55
	public function xmlrpc_methods( $core_methods ) {
56
		$jetpack_methods = array(
57
			'jetpack.verifyAction'     => array( $this, 'verify_action' ),
58
			'jetpack.getUser'          => array( $this, 'get_user' ),
59
			'jetpack.remoteRegister'   => array( $this, 'remote_register' ),
60
			'jetpack.remoteProvision'  => array( $this, 'remote_provision' ),
61
			'jetpack.idcUrlValidation' => array( $this, 'validate_urls_for_idc_mitigation' ),
62
			'jetpack.unlinkUser'       => array( $this, 'unlink_user' ),
63
			'jetpack.testConnection'   => array( $this, 'test_connection' ),
64
		);
65
66
		if ( class_exists( 'Jetpack' ) ) {
67
			$jetpack_methods['jetpack.jsonAPI'] = array( $this, 'json_api' );
68
		}
69
70
		$this->user = $this->login();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->login() can also be of type boolean. However, the property $user is declared as type object<WP_User>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
71
72
		if ( $this->user ) {
73
			$jetpack_methods = array_merge(
74
				$jetpack_methods,
75
				array(
76
					'jetpack.testAPIUserCode' => array( $this, 'test_api_user_code' ),
77
				)
78
			);
79
80
			if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
81
				$jetpack_methods['metaWeblog.newMediaObject']      = $core_methods['metaWeblog.newMediaObject'];
82
				$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
83
			}
84
85
			/**
86
			 * Filters the XML-RPC methods available to Jetpack for authenticated users.
87
			 *
88
			 * @since 1.1.0
89
			 *
90
			 * @param array    $jetpack_methods XML-RPC methods available to the Jetpack Server.
91
			 * @param array    $core_methods    Available core XML-RPC methods.
92
			 * @param \WP_User $user            Information about a given WordPress user.
93
			 */
94
			$jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $this->user );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $core_methods.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
95
		}
96
97
		/**
98
		 * Filters the XML-RPC methods available to Jetpack for requests signed both with a blog token or a user token.
99
		 *
100
		 * @since 3.0.0
101
		 *
102
		 * @param array         $jetpack_methods XML-RPC methods available to the Jetpack Server.
103
		 * @param array         $core_methods    Available core XML-RPC methods.
104
		 * @param \WP_User|bool $user            Information about a given WordPress user. False if authenticated with blog token.
105
		 */
106
		return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods, $this->user );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $core_methods.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
107
	}
108
109
	/**
110
	 * Whitelist of the bootstrap XML-RPC methods
111
	 */
112
	public function bootstrap_xmlrpc_methods() {
113
		return array(
114
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
115
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
116
		);
117
	}
118
119
	/**
120
	 * Additional method needed for authorization calls.
121
	 */
122
	public function authorize_xmlrpc_methods() {
123
		return array(
124
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
125
		);
126
	}
127
128
	/**
129
	 * Remote provisioning methods.
130
	 */
131
	public function provision_xmlrpc_methods() {
132
		return array(
133
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
134
			'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
135
			'jetpack.remoteConnect'   => array( $this, 'remote_connect' ),
136
			'jetpack.getUser'         => array( $this, 'get_user' ),
137
		);
138
	}
139
140
	/**
141
	 * Used to verify whether a local user exists and what role they have.
142
	 *
143
	 * @param int|string|array $request One of:
144
	 *                         int|string The local User's ID, username, or email address.
145
	 *                         array      A request array containing:
146
	 *                                    0: int|string The local User's ID, username, or email address.
147
	 *
148
	 * @return array|\IXR_Error Information about the user, or error if no such user found:
149
	 *                          roles:     string[] The user's rols.
150
	 *                          login:     string   The user's username.
151
	 *                          email_hash string[] The MD5 hash of the user's normalized email address.
152
	 *                          caps       string[] The user's capabilities.
153
	 *                          allcaps    string[] The user's granular capabilities, merged from role capabilities.
154
	 *                          token_key  string   The Token Key of the user's Jetpack token. Empty string if none.
155
	 */
156
	public function get_user( $request ) {
157
		$user_id = is_array( $request ) ? $request[0] : $request;
158
159
		if ( ! $user_id ) {
160
			return $this->error(
161
				new \WP_Error(
162
					'invalid_user',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_user'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
163
					__( 'Invalid user identifier.', 'jetpack' ),
164
					400
165
				),
166
				'get_user'
167
			);
168
		}
169
170
		$user = $this->get_user_by_anything( $user_id );
171
172
		if ( ! $user ) {
173
			return $this->error(
174
				new \WP_Error(
175
					'user_unknown',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'user_unknown'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
176
					__( 'User not found.', 'jetpack' ),
177
					404
178
				),
179
				'get_user'
180
			);
181
		}
182
183
		$user_token = ( new Tokens() )->get_access_token( $user->ID );
184
185
		if ( $user_token ) {
186
			list( $user_token_key ) = explode( '.', $user_token->secret );
187
			if ( $user_token_key === $user_token->secret ) {
188
				$user_token_key = '';
189
			}
190
		} else {
191
			$user_token_key = '';
192
		}
193
194
		return array(
195
			'id'         => $user->ID,
196
			'login'      => $user->user_login,
197
			'email_hash' => md5( strtolower( trim( $user->user_email ) ) ),
198
			'roles'      => $user->roles,
199
			'caps'       => $user->caps,
200
			'allcaps'    => $user->allcaps,
201
			'token_key'  => $user_token_key,
202
		);
203
	}
204
205
	/**
206
	 * Remote authorization XMLRPC method handler.
207
	 *
208
	 * @param array $request the request.
209
	 */
210
	public function remote_authorize( $request ) {
211
		$user = get_user_by( 'id', $request['state'] );
212
213
		/**
214
		 * Happens on various request handling events in the Jetpack XMLRPC server.
215
		 * The action combines several types of events:
216
		 *    - remote_authorize
217
		 *    - remote_provision
218
		 *    - get_user.
219
		 *
220
		 * @since 8.0.0
221
		 *
222
		 * @param String  $action the action name, i.e., 'remote_authorize'.
223
		 * @param String  $stage  the execution stage, can be 'begin', 'success', 'error', etc.
224
		 * @param array   $parameters extra parameters from the event.
225
		 * @param WP_User $user the acting user.
226
		 */
227
		do_action( 'jetpack_xmlrpc_server_event', 'remote_authorize', 'begin', array(), $user );
228
229
		foreach ( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
230
			if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
231
				return $this->error(
232
					new \WP_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ),
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'missing_parameter'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
233
					'remote_authorize'
234
				);
235
			}
236
		}
237
238
		if ( ! $user ) {
239
			return $this->error( new \WP_Error( 'user_unknown', 'User not found.', 404 ), 'remote_authorize' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'user_unknown'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
240
		}
241
242
		if ( $this->connection->is_active() && $this->connection->is_user_connected( $request['state'] ) ) {
243
			return $this->error( new \WP_Error( 'already_connected', 'User already connected.', 400 ), 'remote_authorize' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'already_connected'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
244
		}
245
246
		$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
247
248
		if ( is_a( $verified, 'IXR_Error' ) ) {
249
			return $this->error( $verified, 'remote_authorize' );
250
		}
251
252
		wp_set_current_user( $request['state'] );
253
254
		$result = $this->connection->authorize( $request );
255
256
		if ( is_wp_error( $result ) ) {
257
			return $this->error( $result, 'remote_authorize' );
0 ignored issues
show
Bug introduced by
It seems like $result defined by $this->connection->authorize($request) on line 254 can also be of type string; however, Jetpack_XMLRPC_Server::error() does only seem to accept object<WP_Error>|object<IXR_Error>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
258
		}
259
260
		// This action is documented in class.jetpack-xmlrpc-server.php.
261
		do_action( 'jetpack_xmlrpc_server_event', 'remote_authorize', 'success' );
262
263
		return array(
264
			'result' => $result,
265
		);
266
	}
267
268
	/**
269
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
270
	 * register this site so that a plan can be provisioned.
271
	 *
272
	 * @param array $request An array containing at minimum nonce and local_user keys.
273
	 *
274
	 * @return \WP_Error|array
275
	 */
276
	public function remote_register( $request ) {
277
		// This action is documented in class.jetpack-xmlrpc-server.php.
278
		do_action( 'jetpack_xmlrpc_server_event', 'remote_register', 'begin', array() );
279
280
		$user = $this->fetch_and_verify_local_user( $request );
281
282
		if ( ! $user ) {
283
			return $this->error(
284
				new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ),
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'input_error'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
285
				'remote_register'
286
			);
287
		}
288
289
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
290
			return $this->error( $user, 'remote_register' );
291
		}
292
293
		if ( empty( $request['nonce'] ) ) {
294
			return $this->error(
295
				new \WP_Error(
296
					'nonce_missing',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'nonce_missing'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
297
					__( 'The required "nonce" parameter is missing.', 'jetpack' ),
298
					400
299
				),
300
				'remote_register'
301
			);
302
		}
303
304
		$nonce = sanitize_text_field( $request['nonce'] );
305
		unset( $request['nonce'] );
306
307
		$api_url  = $this->connection->api_url( 'partner_provision_nonce_check' );
308
		$response = Client::_wp_remote_request(
309
			esc_url_raw( add_query_arg( 'nonce', $nonce, $api_url ) ),
310
			array( 'method' => 'GET' ),
311
			true
312
		);
313
314
		if (
315
			200 !== wp_remote_retrieve_response_code( $response ) ||
316
			'OK' !== trim( wp_remote_retrieve_body( $response ) )
317
		) {
318
			return $this->error(
319
				new \WP_Error(
320
					'invalid_nonce',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_nonce'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
321
					__( 'There was an issue validating this request.', 'jetpack' ),
322
					400
323
				),
324
				'remote_register'
325
			);
326
		}
327
328
		if ( ! Jetpack_Options::get_option( 'id' ) || ! ( new Tokens() )->get_access_token() || ! empty( $request['force'] ) ) {
329
			wp_set_current_user( $user->ID );
330
331
			// This code mostly copied from Jetpack::admin_page_load.
332
			Jetpack::maybe_set_version_option();
333
			$registered = Jetpack::try_registration();
334
			if ( is_wp_error( $registered ) ) {
335
				return $this->error( $registered, 'remote_register' );
336
			} elseif ( ! $registered ) {
337
				return $this->error(
338
					new \WP_Error(
339
						'registration_error',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'registration_error'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
340
						__( 'There was an unspecified error registering the site', 'jetpack' ),
341
						400
342
					),
343
					'remote_register'
344
				);
345
			}
346
		}
347
348
		// This action is documented in class.jetpack-xmlrpc-server.php.
349
		do_action( 'jetpack_xmlrpc_server_event', 'remote_register', 'success' );
350
351
		return array(
352
			'client_id' => Jetpack_Options::get_option( 'id' ),
353
		);
354
	}
355
356
	/**
357
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
358
	 * register this site so that a plan can be provisioned.
359
	 *
360
	 * @param array $request An array containing at minimum a nonce key and a local_username key.
361
	 *
362
	 * @return \WP_Error|array
363
	 */
364
	public function remote_provision( $request ) {
365
		$user = $this->fetch_and_verify_local_user( $request );
366
367
		if ( ! $user ) {
368
			return $this->error(
369
				new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ),
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'input_error'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
370
				'remote_provision'
371
			);
372
		}
373
374
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
375
			return $this->error( $user, 'remote_provision' );
376
		}
377
378
		$site_icon = get_site_icon_url();
379
380
		$auto_enable_sso = ( ! $this->connection->is_active() || Jetpack::is_module_active( 'sso' ) );
381
382
		/** This filter is documented in class.jetpack-cli.php */
383 View Code Duplication
		if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) {
384
			$redirect_uri = add_query_arg(
385
				array(
386
					'action'      => 'jetpack-sso',
387
					'redirect_to' => rawurlencode( admin_url() ),
388
				),
389
				wp_login_url() // TODO: come back to Jetpack dashboard?
390
			);
391
		} else {
392
			$redirect_uri = admin_url();
393
		}
394
395
		// Generate secrets.
396
		$roles   = new Roles();
397
		$role    = $roles->translate_user_to_role( $user );
398
		$secrets = ( new Secrets() )->generate( 'authorize', $user->ID );
399
400
		$response = array(
401
			'jp_version'   => JETPACK__VERSION,
402
			'redirect_uri' => $redirect_uri,
403
			'user_id'      => $user->ID,
404
			'user_email'   => $user->user_email,
405
			'user_login'   => $user->user_login,
406
			'scope'        => $this->connection->sign_role( $role, $user->ID ),
407
			'secret'       => $secrets['secret_1'],
408
			'is_active'    => $this->connection->is_active(),
409
		);
410
411
		if ( $site_icon ) {
412
			$response['site_icon'] = $site_icon;
413
		}
414
415
		if ( ! empty( $request['onboarding'] ) ) {
416
			Jetpack::create_onboarding_token();
417
			$response['onboarding_token'] = Jetpack_Options::get_option( 'onboarding' );
418
		}
419
420
		return $response;
421
	}
422
423
	/**
424
	 * Given an array containing a local user identifier and a nonce, will attempt to fetch and set
425
	 * an access token for the given user.
426
	 *
427
	 * @param array       $request    An array containing local_user and nonce keys at minimum.
428
	 * @param \IXR_Client $ixr_client The client object, optional.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $ixr_client not be false|IXR_Client?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
429
	 * @return mixed
430
	 */
431
	public function remote_connect( $request, $ixr_client = false ) {
432
		if ( $this->connection->is_active() ) {
433
			return $this->error(
434
				new WP_Error(
435
					'already_connected',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'already_connected'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
436
					__( 'Jetpack is already connected.', 'jetpack' ),
437
					400
438
				),
439
				'remote_connect'
440
			);
441
		}
442
443
		$user = $this->fetch_and_verify_local_user( $request );
444
445
		if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
446
			return $this->error(
447
				new WP_Error(
448
					'input_error',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'input_error'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
449
					__( 'Valid user is required.', 'jetpack' ),
450
					400
451
				),
452
				'remote_connect'
453
			);
454
		}
455
456
		if ( empty( $request['nonce'] ) ) {
457
			return $this->error(
458
				new WP_Error(
459
					'input_error',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'input_error'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
460
					__( 'A non-empty nonce must be supplied.', 'jetpack' ),
461
					400
462
				),
463
				'remote_connect'
464
			);
465
		}
466
467
		if ( ! $ixr_client ) {
468
			$ixr_client = new Jetpack_IXR_Client();
469
		}
470
		// TODO: move this query into the Tokens class?
471
		$ixr_client->query(
472
			'jetpack.getUserAccessToken',
473
			array(
474
				'nonce'            => sanitize_text_field( $request['nonce'] ),
475
				'external_user_id' => $user->ID,
476
			)
477
		);
478
479
		$token = $ixr_client->isError() ? false : $ixr_client->getResponse();
480
		if ( empty( $token ) ) {
481
			return $this->error(
482
				new WP_Error(
483
					'token_fetch_failed',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'token_fetch_failed'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
484
					__( 'Failed to fetch user token from WordPress.com.', 'jetpack' ),
485
					400
486
				),
487
				'remote_connect'
488
			);
489
		}
490
		$token = sanitize_text_field( $token );
491
492
		( new Tokens() )->update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true );
493
494
		$this->do_post_authorization();
495
496
		return $this->connection->is_active();
497
	}
498
499
	/**
500
	 * Getter for the local user to act as.
501
	 *
502
	 * @param array $request the current request data.
503
	 */
504
	private function fetch_and_verify_local_user( $request ) {
505
		if ( empty( $request['local_user'] ) ) {
506
			return $this->error(
507
				new \WP_Error(
508
					'local_user_missing',
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'local_user_missing'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
509
					__( 'The required "local_user" parameter is missing.', 'jetpack' ),
510
					400
511
				),
512
				'remote_provision'
513
			);
514
		}
515
516
		// Local user is used to look up by login, email or ID.
517
		$local_user_info = $request['local_user'];
518
519
		return $this->get_user_by_anything( $local_user_info );
520
	}
521
522
	/**
523
	 * Gets the user object by its data.
524
	 *
525
	 * @param string $user_id can be any identifying user data.
526
	 */
527
	private function get_user_by_anything( $user_id ) {
528
		$user = get_user_by( 'login', $user_id );
529
530
		if ( ! $user ) {
531
			$user = get_user_by( 'email', $user_id );
532
		}
533
534
		if ( ! $user ) {
535
			$user = get_user_by( 'ID', $user_id );
536
		}
537
538
		return $user;
539
	}
540
541
	/**
542
	 * Possible error_codes:
543
	 *
544
	 * - verify_secret_1_missing
545
	 * - verify_secret_1_malformed
546
	 * - verify_secrets_missing: verification secrets are not found in database
547
	 * - verify_secrets_incomplete: verification secrets are only partially found in database
548
	 * - verify_secrets_expired: verification secrets have expired
549
	 * - verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
550
	 * - state_missing: required parameter of state not found
551
	 * - state_malformed: state is not a digit
552
	 * - invalid_state: state in request does not match the stored state
553
	 *
554
	 * The 'authorize' and 'register' actions have additional error codes
555
	 *
556
	 * state_missing: a state ( user id ) was not supplied
557
	 * state_malformed: state is not the correct data type
558
	 * invalid_state: supplied state does not match the stored state
559
	 *
560
	 * @param array $params action An array of 3 parameters:
561
	 *     [0]: string action. Possible values are `authorize`, `publicize` and `register`.
562
	 *     [1]: string secret_1.
563
	 *     [2]: int state.
564
	 * @return \IXR_Error|string IXR_Error on failure, secret_2 on success.
565
	 */
566
	public function verify_action( $params ) {
567
		$action        = isset( $params[0] ) ? $params[0] : '';
568
		$verify_secret = isset( $params[1] ) ? $params[1] : '';
569
		$state         = isset( $params[2] ) ? $params[2] : '';
570
571
		$result = ( new Secrets() )->verify( $action, $verify_secret, $state );
572
573
		if ( is_wp_error( $result ) ) {
574
			return $this->error( $result );
0 ignored issues
show
Bug introduced by
It seems like $result defined by (new \Automattic\Jetpack...$verify_secret, $state) on line 571 can also be of type string; however, Jetpack_XMLRPC_Server::error() does only seem to accept object<WP_Error>|object<IXR_Error>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
575
		}
576
577
		return $result;
578
	}
579
580
	/**
581
	 * Wrapper for wp_authenticate( $username, $password );
582
	 *
583
	 * @return \WP_User|bool
584
	 */
585
	public function login() {
586
		$this->connection->require_jetpack_authentication();
587
		$user = wp_authenticate( 'username', 'password' );
588
		if ( is_wp_error( $user ) ) {
589
			if ( 'authentication_failed' === $user->get_error_code() ) { // Generic error could mean most anything.
590
				$this->error = new \WP_Error( 'invalid_request', 'Invalid Request', 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_request'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
591
			} else {
592
				$this->error = $user;
593
			}
594
			return false;
595
		} elseif ( ! $user ) { // Shouldn't happen.
596
			$this->error = new \WP_Error( 'invalid_request', 'Invalid Request', 403 );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_request'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
597
			return false;
598
		}
599
600
		wp_set_current_user( $user->ID );
601
602
		return $user;
603
	}
604
605
	/**
606
	 * Returns the current error as an \IXR_Error
607
	 *
608
	 * @param \WP_Error|\IXR_Error $error             The error object, optional.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $error not be WP_Error|IXR_Error|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
609
	 * @param string               $event_name The event name.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $event_name not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
610
	 * @param \WP_User             $user              The user object.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $user not be WP_User|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
611
	 * @return bool|\IXR_Error
612
	 */
613
	public function error( $error = null, $event_name = null, $user = null ) {
614
		if ( null !== $event_name ) {
615
			// This action is documented in class.jetpack-xmlrpc-server.php.
616
			do_action( 'jetpack_xmlrpc_server_event', $event_name, 'fail', $error, $user );
617
		}
618
619
		if ( ! is_null( $error ) ) {
620
			$this->error = $error;
0 ignored issues
show
Documentation Bug introduced by
It seems like $error can also be of type object<IXR_Error>. However, the property $error is declared as type object<WP_Error>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
621
		}
622
623
		if ( is_wp_error( $this->error ) ) {
624
			$code = $this->error->get_error_data();
0 ignored issues
show
Bug introduced by
The method get_error_data() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
625
			if ( ! $code ) {
626
				$code = -10520;
627
			}
628
			$message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() );
0 ignored issues
show
Bug introduced by
The method get_error_code() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method get_error_message() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
629
			return new \IXR_Error( $code, $message );
630
		} elseif ( is_a( $this->error, 'IXR_Error' ) ) {
631
			return $this->error;
632
		}
633
634
		return false;
635
	}
636
637
	/* API Methods */
638
639
	/**
640
	 * Just authenticates with the given Jetpack credentials.
641
	 *
642
	 * @return string A success string. The Jetpack plugin filters it and make it retun the Jetpack plugin version.
643
	 */
644
	public function test_connection() {
645
		/**
646
		 * Filters the successful response of the XMLRPC test_connection method
647
		 *
648
		 * @param string $response The response string.
649
		 */
650
		return apply_filters( 'jetpack_xmlrpc_test_connection_response', 'success' );
651
	}
652
653
	/**
654
	 * Test the API user code.
655
	 *
656
	 * @param array $args arguments identifying the test site.
657
	 */
658
	public function test_api_user_code( $args ) {
659
		$client_id = (int) $args[0];
660
		$user_id   = (int) $args[1];
661
		$nonce     = (string) $args[2];
662
		$verify    = (string) $args[3];
663
664
		if ( ! $client_id || ! $user_id || ! strlen( $nonce ) || 32 !== strlen( $verify ) ) {
665
			return false;
666
		}
667
668
		$user = get_user_by( 'id', $user_id );
669
		if ( ! $user || is_wp_error( $user ) ) {
670
			return false;
671
		}
672
673
		/* phpcs:ignore
674
		 debugging
675
		error_log( "CLIENT: $client_id" );
676
		error_log( "USER:   $user_id" );
677
		error_log( "NONCE:  $nonce" );
678
		error_log( "VERIFY: $verify" );
679
		*/
680
681
		$jetpack_token = ( new Tokens() )->get_access_token( $user_id );
682
683
		$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
684
		if ( ! $api_user_code ) {
685
			return false;
686
		}
687
688
		$hmac = hash_hmac(
689
			'md5',
690
			json_encode( // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
691
				(object) array(
692
					'client_id' => (int) $client_id,
693
					'user_id'   => (int) $user_id,
694
					'nonce'     => (string) $nonce,
695
					'code'      => (string) $api_user_code,
696
				)
697
			),
698
			$jetpack_token->secret
699
		);
700
701
		if ( ! hash_equals( $hmac, $verify ) ) {
702
			return false;
703
		}
704
705
		return $user_id;
706
	}
707
708
	/**
709
	 * Unlink a user from WordPress.com
710
	 *
711
	 * When the request is done without any parameter, this XMLRPC callback gets an empty array as input.
712
	 *
713
	 * If $user_id is not provided, it will try to disconnect the current logged in user. This will fail if called by the Master User.
714
	 *
715
	 * If $user_id is is provided, it will try to disconnect the informed user, even if it's the Master User.
716
	 *
717
	 * @param mixed $user_id The user ID to disconnect from this site.
718
	 */
719
	public function unlink_user( $user_id = array() ) {
720
		$user_id = (int) $user_id;
721
		if ( $user_id < 1 ) {
722
			$user_id = null;
723
		}
724
		/**
725
		 * Fired when we want to log an event to the Jetpack event log.
726
		 *
727
		 * @since 7.7.0
728
		 *
729
		 * @param string $code Unique name for the event.
730
		 * @param string $data Optional data about the event.
731
		 */
732
		do_action( 'jetpack_event_log', 'unlink' );
733
		return $this->connection->disconnect_user(
734
			$user_id,
735
			(bool) $user_id
736
		);
737
	}
738
739
	/**
740
	 * Returns any object that is able to be synced.
741
	 *
742
	 * @deprecated since 7.8.0
743
	 * @see Automattic\Jetpack\Sync\Sender::sync_object()
744
	 *
745
	 * @param array $args the synchronized object parameters.
746
	 * @return string Encoded sync object.
747
	 */
748
	public function sync_object( $args ) {
749
		_deprecated_function( __METHOD__, 'jetpack-7.8', 'Automattic\\Jetpack\\Sync\\Sender::sync_object' );
750
		return Sender::get_instance()->sync_object( $args );
751
	}
752
753
	/**
754
	 * Returns the home URL and site URL for the current site which can be used on the WPCOM side for
755
	 * IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
756
	 * and the remote Jetpack site.
757
	 *
758
	 * @return array
759
	 */
760
	public function validate_urls_for_idc_mitigation() {
761
		return array(
762
			'home'    => Functions::home_url(),
763
			'siteurl' => Functions::site_url(),
764
		);
765
	}
766
767
	/**
768
	 * Updates the attachment parent object.
769
	 *
770
	 * @param array $args attachment and parent identifiers.
771
	 */
772
	public function update_attachment_parent( $args ) {
773
		$attachment_id = (int) $args[0];
774
		$parent_id     = (int) $args[1];
775
776
		return wp_update_post(
777
			array(
778
				'ID'          => $attachment_id,
779
				'post_parent' => $parent_id,
780
			)
781
		);
782
	}
783
784
	/**
785
	 * Serve a JSON API request.
786
	 *
787
	 * @param array $args request arguments.
788
	 */
789
	public function json_api( $args = array() ) {
790
		$json_api_args        = $args[0];
791
		$verify_api_user_args = $args[1];
792
793
		$method       = (string) $json_api_args[0];
794
		$url          = (string) $json_api_args[1];
795
		$post_body    = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2];
796
		$user_details = (array) $json_api_args[4];
797
		$locale       = (string) $json_api_args[5];
798
799
		if ( ! $verify_api_user_args ) {
800
			$user_id = 0;
801
		} elseif ( 'internal' === $verify_api_user_args[0] ) {
802
			$user_id = (int) $verify_api_user_args[1];
803
			if ( $user_id ) {
804
				$user = get_user_by( 'id', $user_id );
805
				if ( ! $user || is_wp_error( $user ) ) {
806
					return false;
807
				}
808
			}
809
		} else {
810
			$user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args );
811
			if ( ! $user_id ) {
812
				return false;
813
			}
814
		}
815
816
		/* phpcs:ignore
817
		 debugging
818
		error_log( "-- begin json api via jetpack debugging -- " );
819
		error_log( "METHOD: $method" );
820
		error_log( "URL: $url" );
821
		error_log( "POST BODY: $post_body" );
822
		error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) );
823
		error_log( "VERIFIED USER_ID: " . (int) $user_id );
824
		error_log( "-- end json api via jetpack debugging -- " );
825
		*/
826
827
		if ( 'en' !== $locale ) {
828
			// .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them.
829
			$new_locale = $locale;
830
			if ( strpos( $locale, '-' ) !== false ) {
831
				$locale_pieces = explode( '-', $locale );
832
				$new_locale    = $locale_pieces[0];
833
				$new_locale   .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : '';
834
			} else {
835
				// .com might pass 'fr' because thats what our language files are named as, where core seems
836
				// to do fr_FR - so try that if we don't think we can load the file.
837
				if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) {
838
					$new_locale = $locale . '_' . strtoupper( $locale );
839
				}
840
			}
841
842
			if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) {
843
				unload_textdomain( 'default' );
844
				load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' );
845
			}
846
		}
847
848
		$old_user = wp_get_current_user();
849
		wp_set_current_user( $user_id );
850
851
		if ( $user_id ) {
852
			$token_key = false;
853
		} else {
854
			$verified  = $this->connection->verify_xml_rpc_signature();
855
			$token_key = $verified['token_key'];
856
		}
857
858
		$token = ( new Tokens() )->get_access_token( $user_id, $token_key );
859
		if ( ! $token || is_wp_error( $token ) ) {
860
			return false;
861
		}
862
863
		define( 'REST_API_REQUEST', true );
864
		define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
865
866
		// needed?
867
		require_once ABSPATH . 'wp-admin/includes/admin.php';
868
869
		require_once JETPACK__PLUGIN_DIR . 'class.json-api.php';
870
		$api                        = WPCOM_JSON_API::init( $method, $url, $post_body );
871
		$api->token_details['user'] = $user_details;
872
		require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
873
874
		$display_errors = ini_set( 'display_errors', 0 ); // phpcs:ignore WordPress.PHP.IniSet
875
		ob_start();
876
		$api->serve( false );
877
		$output = ob_get_clean();
878
		ini_set( 'display_errors', $display_errors ); // phpcs:ignore WordPress.PHP.IniSet
879
880
		$nonce = wp_generate_password( 10, false );
881
		$hmac  = hash_hmac( 'md5', $nonce . $output, $token->secret );
882
883
		wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 );
884
885
		return array(
886
			(string) $output,
887
			(string) $nonce,
888
			(string) $hmac,
889
		);
890
	}
891
892
	/**
893
	 * Handles authorization actions after connecting a site, such as enabling modules.
894
	 *
895
	 * This do_post_authorization() is used in this class, as opposed to calling
896
	 * Jetpack::handle_post_authorization_actions() directly so that we can mock this method as necessary.
897
	 *
898
	 * @return void
899
	 */
900
	public function do_post_authorization() {
901
		/** This filter is documented in class.jetpack-cli.php */
902
		$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
903
		Jetpack::handle_post_authorization_actions( $enable_sso, false, false );
904
	}
905
}
906