Completed
Push — renovate/wordpress-monorepo ( 8cdadc...6203b6 )
by
unknown
552:54 queued 542:45
created

Jetpack_XMLRPC_Server::xmlrpc_methods()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 50
rs 9.0909
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
		$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...
67
68
		if ( $this->user ) {
69
			$jetpack_methods = array_merge(
70
				$jetpack_methods,
71
				array(
72
					'jetpack.testAPIUserCode' => array( $this, 'test_api_user_code' ),
73
				)
74
			);
75
76
			if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
77
				$jetpack_methods['metaWeblog.newMediaObject']      = $core_methods['metaWeblog.newMediaObject'];
78
				$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
79
			}
80
81
			/**
82
			 * Filters the XML-RPC methods available to Jetpack for authenticated users.
83
			 *
84
			 * @since 1.1.0
85
			 *
86
			 * @param array    $jetpack_methods XML-RPC methods available to the Jetpack Server.
87
			 * @param array    $core_methods    Available core XML-RPC methods.
88
			 * @param \WP_User $user            Information about the user authenticated in the request.
89
			 */
90
			$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...
91
		}
92
93
		/**
94
		 * Filters the XML-RPC methods available to Jetpack for requests signed both with a blog token or a user token.
95
		 *
96
		 * @since 3.0.0
97
		 * @since 9.6.0 Introduced the $user parameter.
98
		 *
99
		 * @param array         $jetpack_methods XML-RPC methods available to the Jetpack Server.
100
		 * @param array         $core_methods    Available core XML-RPC methods.
101
		 * @param \WP_User|bool $user            Information about the user authenticated in the request. False if authenticated with blog token.
102
		 */
103
		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...
104
	}
105
106
	/**
107
	 * Whitelist of the bootstrap XML-RPC methods
108
	 */
109
	public function bootstrap_xmlrpc_methods() {
110
		return array(
111
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
112
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
113
		);
114
	}
115
116
	/**
117
	 * Additional method needed for authorization calls.
118
	 */
119
	public function authorize_xmlrpc_methods() {
120
		return array(
121
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
122
		);
123
	}
124
125
	/**
126
	 * Remote provisioning methods.
127
	 */
128
	public function provision_xmlrpc_methods() {
129
		return array(
130
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
131
			'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
132
			'jetpack.remoteConnect'   => array( $this, 'remote_connect' ),
133
			'jetpack.getUser'         => array( $this, 'get_user' ),
134
		);
135
	}
136
137
	/**
138
	 * Used to verify whether a local user exists and what role they have.
139
	 *
140
	 * @param int|string|array $request One of:
141
	 *                         int|string The local User's ID, username, or email address.
142
	 *                         array      A request array containing:
143
	 *                                    0: int|string The local User's ID, username, or email address.
144
	 *
145
	 * @return array|\IXR_Error Information about the user, or error if no such user found:
146
	 *                          roles:     string[] The user's rols.
147
	 *                          login:     string   The user's username.
148
	 *                          email_hash string[] The MD5 hash of the user's normalized email address.
149
	 *                          caps       string[] The user's capabilities.
150
	 *                          allcaps    string[] The user's granular capabilities, merged from role capabilities.
151
	 *                          token_key  string   The Token Key of the user's Jetpack token. Empty string if none.
152
	 */
153
	public function get_user( $request ) {
154
		$user_id = is_array( $request ) ? $request[0] : $request;
155
156
		if ( ! $user_id ) {
157
			return $this->error(
158
				new \WP_Error(
159
					'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...
160
					__( 'Invalid user identifier.', 'jetpack' ),
161
					400
162
				),
163
				'get_user'
164
			);
165
		}
166
167
		$user = $this->get_user_by_anything( $user_id );
168
169
		if ( ! $user ) {
170
			return $this->error(
171
				new \WP_Error(
172
					'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...
173
					__( 'User not found.', 'jetpack' ),
174
					404
175
				),
176
				'get_user'
177
			);
178
		}
179
180
		$user_token = ( new Tokens() )->get_access_token( $user->ID );
181
182
		if ( $user_token ) {
183
			list( $user_token_key ) = explode( '.', $user_token->secret );
184
			if ( $user_token_key === $user_token->secret ) {
185
				$user_token_key = '';
186
			}
187
		} else {
188
			$user_token_key = '';
189
		}
190
191
		return array(
192
			'id'         => $user->ID,
193
			'login'      => $user->user_login,
194
			'email_hash' => md5( strtolower( trim( $user->user_email ) ) ),
195
			'roles'      => $user->roles,
196
			'caps'       => $user->caps,
197
			'allcaps'    => $user->allcaps,
198
			'token_key'  => $user_token_key,
199
		);
200
	}
201
202
	/**
203
	 * Remote authorization XMLRPC method handler.
204
	 *
205
	 * @param array $request the request.
206
	 */
207
	public function remote_authorize( $request ) {
208
		$user = get_user_by( 'id', $request['state'] );
209
210
		/**
211
		 * Happens on various request handling events in the Jetpack XMLRPC server.
212
		 * The action combines several types of events:
213
		 *    - remote_authorize
214
		 *    - remote_provision
215
		 *    - get_user.
216
		 *
217
		 * @since 8.0.0
218
		 *
219
		 * @param String  $action the action name, i.e., 'remote_authorize'.
220
		 * @param String  $stage  the execution stage, can be 'begin', 'success', 'error', etc.
221
		 * @param array   $parameters extra parameters from the event.
222
		 * @param WP_User $user the acting user.
223
		 */
224
		do_action( 'jetpack_xmlrpc_server_event', 'remote_authorize', 'begin', array(), $user );
225
226
		foreach ( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
227
			if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
228
				return $this->error(
229
					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...
230
					'remote_authorize'
231
				);
232
			}
233
		}
234
235
		if ( ! $user ) {
236
			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...
237
		}
238
239
		if ( $this->connection->has_connected_owner() && $this->connection->is_user_connected( $request['state'] ) ) {
240
			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...
241
		}
242
243
		$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
244
245
		if ( is_a( $verified, 'IXR_Error' ) ) {
246
			return $this->error( $verified, 'remote_authorize' );
247
		}
248
249
		wp_set_current_user( $request['state'] );
250
251
		$result = $this->connection->authorize( $request );
252
253
		if ( is_wp_error( $result ) ) {
254
			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 251 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...
255
		}
256
257
		// This action is documented in class.jetpack-xmlrpc-server.php.
258
		do_action( 'jetpack_xmlrpc_server_event', 'remote_authorize', 'success' );
259
260
		return array(
261
			'result' => $result,
262
		);
263
	}
264
265
	/**
266
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
267
	 * register this site so that a plan can be provisioned.
268
	 *
269
	 * @param array $request An array containing at minimum nonce and local_user keys.
270
	 *
271
	 * @return \WP_Error|array
272
	 */
273
	public function remote_register( $request ) {
274
		// This action is documented in class.jetpack-xmlrpc-server.php.
275
		do_action( 'jetpack_xmlrpc_server_event', 'remote_register', 'begin', array() );
276
277
		$user = $this->fetch_and_verify_local_user( $request );
278
279
		if ( ! $user ) {
280
			return $this->error(
281
				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...
282
				'remote_register'
283
			);
284
		}
285
286
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
287
			return $this->error( $user, 'remote_register' );
288
		}
289
290
		if ( empty( $request['nonce'] ) ) {
291
			return $this->error(
292
				new \WP_Error(
293
					'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...
294
					__( 'The required "nonce" parameter is missing.', 'jetpack' ),
295
					400
296
				),
297
				'remote_register'
298
			);
299
		}
300
301
		$nonce = sanitize_text_field( $request['nonce'] );
302
		unset( $request['nonce'] );
303
304
		$api_url  = $this->connection->api_url( 'partner_provision_nonce_check' );
305
		$response = Client::_wp_remote_request(
306
			esc_url_raw( add_query_arg( 'nonce', $nonce, $api_url ) ),
307
			array( 'method' => 'GET' ),
308
			true
309
		);
310
311
		if (
312
			200 !== wp_remote_retrieve_response_code( $response ) ||
313
			'OK' !== trim( wp_remote_retrieve_body( $response ) )
314
		) {
315
			return $this->error(
316
				new \WP_Error(
317
					'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...
318
					__( 'There was an issue validating this request.', 'jetpack' ),
319
					400
320
				),
321
				'remote_register'
322
			);
323
		}
324
325
		if ( ! Jetpack_Options::get_option( 'id' ) || ! ( new Tokens() )->get_access_token() || ! empty( $request['force'] ) ) {
326
			wp_set_current_user( $user->ID );
327
328
			// This code mostly copied from Jetpack::admin_page_load.
329
			Jetpack::maybe_set_version_option();
330
			$registered = Jetpack::try_registration();
331
			if ( is_wp_error( $registered ) ) {
332
				return $this->error( $registered, 'remote_register' );
333
			} elseif ( ! $registered ) {
334
				return $this->error(
335
					new \WP_Error(
336
						'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...
337
						__( 'There was an unspecified error registering the site', 'jetpack' ),
338
						400
339
					),
340
					'remote_register'
341
				);
342
			}
343
		}
344
345
		// This action is documented in class.jetpack-xmlrpc-server.php.
346
		do_action( 'jetpack_xmlrpc_server_event', 'remote_register', 'success' );
347
348
		return array(
349
			'client_id' => Jetpack_Options::get_option( 'id' ),
350
		);
351
	}
352
353
	/**
354
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
355
	 * register this site so that a plan can be provisioned.
356
	 *
357
	 * @param array $request An array containing at minimum a nonce key and a local_username key.
358
	 *
359
	 * @return \WP_Error|array
360
	 */
361
	public function remote_provision( $request ) {
362
		$user = $this->fetch_and_verify_local_user( $request );
363
364
		if ( ! $user ) {
365
			return $this->error(
366
				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...
367
				'remote_provision'
368
			);
369
		}
370
371
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
372
			return $this->error( $user, 'remote_provision' );
373
		}
374
375
		$site_icon = get_site_icon_url();
376
377
		$auto_enable_sso = ( ! $this->connection->has_connected_owner() || Jetpack::is_module_active( 'sso' ) );
378
379
		/** This filter is documented in class.jetpack-cli.php */
380 View Code Duplication
		if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) {
381
			$redirect_uri = add_query_arg(
382
				array(
383
					'action'      => 'jetpack-sso',
384
					'redirect_to' => rawurlencode( admin_url() ),
385
				),
386
				wp_login_url() // TODO: come back to Jetpack dashboard?
387
			);
388
		} else {
389
			$redirect_uri = admin_url();
390
		}
391
392
		// Generate secrets.
393
		$roles   = new Roles();
394
		$role    = $roles->translate_user_to_role( $user );
395
		$secrets = ( new Secrets() )->generate( 'authorize', $user->ID );
396
397
		$response = array(
398
			'jp_version'   => JETPACK__VERSION,
399
			'redirect_uri' => $redirect_uri,
400
			'user_id'      => $user->ID,
401
			'user_email'   => $user->user_email,
402
			'user_login'   => $user->user_login,
403
			'scope'        => $this->connection->sign_role( $role, $user->ID ),
404
			'secret'       => $secrets['secret_1'],
405
			'is_active'    => $this->connection->has_connected_owner(),
406
		);
407
408
		if ( $site_icon ) {
409
			$response['site_icon'] = $site_icon;
410
		}
411
412
		if ( ! empty( $request['onboarding'] ) ) {
413
			Jetpack::create_onboarding_token();
414
			$response['onboarding_token'] = Jetpack_Options::get_option( 'onboarding' );
415
		}
416
417
		return $response;
418
	}
419
420
	/**
421
	 * Given an array containing a local user identifier and a nonce, will attempt to fetch and set
422
	 * an access token for the given user.
423
	 *
424
	 * @param array       $request    An array containing local_user and nonce keys at minimum.
425
	 * @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...
426
	 * @return mixed
427
	 */
428
	public function remote_connect( $request, $ixr_client = false ) {
429
		if ( $this->connection->has_connected_owner() ) {
430
			return $this->error(
431
				new WP_Error(
432
					'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...
433
					__( 'Jetpack is already connected.', 'jetpack' ),
434
					400
435
				),
436
				'remote_connect'
437
			);
438
		}
439
440
		$user = $this->fetch_and_verify_local_user( $request );
441
442
		if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
443
			return $this->error(
444
				new WP_Error(
445
					'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...
446
					__( 'Valid user is required.', 'jetpack' ),
447
					400
448
				),
449
				'remote_connect'
450
			);
451
		}
452
453
		if ( empty( $request['nonce'] ) ) {
454
			return $this->error(
455
				new WP_Error(
456
					'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...
457
					__( 'A non-empty nonce must be supplied.', 'jetpack' ),
458
					400
459
				),
460
				'remote_connect'
461
			);
462
		}
463
464
		if ( ! $ixr_client ) {
465
			$ixr_client = new Jetpack_IXR_Client();
466
		}
467
		// TODO: move this query into the Tokens class?
468
		$ixr_client->query(
469
			'jetpack.getUserAccessToken',
470
			array(
471
				'nonce'            => sanitize_text_field( $request['nonce'] ),
472
				'external_user_id' => $user->ID,
473
			)
474
		);
475
476
		$token = $ixr_client->isError() ? false : $ixr_client->getResponse();
477
		if ( empty( $token ) ) {
478
			return $this->error(
479
				new WP_Error(
480
					'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...
481
					__( 'Failed to fetch user token from WordPress.com.', 'jetpack' ),
482
					400
483
				),
484
				'remote_connect'
485
			);
486
		}
487
		$token = sanitize_text_field( $token );
488
489
		( new Tokens() )->update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true );
490
491
		$this->do_post_authorization();
492
493
		return $this->connection->has_connected_owner();
494
	}
495
496
	/**
497
	 * Getter for the local user to act as.
498
	 *
499
	 * @param array $request the current request data.
500
	 */
501
	private function fetch_and_verify_local_user( $request ) {
502
		if ( empty( $request['local_user'] ) ) {
503
			return $this->error(
504
				new \WP_Error(
505
					'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...
506
					__( 'The required "local_user" parameter is missing.', 'jetpack' ),
507
					400
508
				),
509
				'remote_provision'
510
			);
511
		}
512
513
		// Local user is used to look up by login, email or ID.
514
		$local_user_info = $request['local_user'];
515
516
		return $this->get_user_by_anything( $local_user_info );
517
	}
518
519
	/**
520
	 * Gets the user object by its data.
521
	 *
522
	 * @param string $user_id can be any identifying user data.
523
	 */
524
	private function get_user_by_anything( $user_id ) {
525
		$user = get_user_by( 'login', $user_id );
526
527
		if ( ! $user ) {
528
			$user = get_user_by( 'email', $user_id );
529
		}
530
531
		if ( ! $user ) {
532
			$user = get_user_by( 'ID', $user_id );
533
		}
534
535
		return $user;
536
	}
537
538
	/**
539
	 * Possible error_codes:
540
	 *
541
	 * - verify_secret_1_missing
542
	 * - verify_secret_1_malformed
543
	 * - verify_secrets_missing: verification secrets are not found in database
544
	 * - verify_secrets_incomplete: verification secrets are only partially found in database
545
	 * - verify_secrets_expired: verification secrets have expired
546
	 * - verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
547
	 * - state_missing: required parameter of state not found
548
	 * - state_malformed: state is not a digit
549
	 * - invalid_state: state in request does not match the stored state
550
	 *
551
	 * The 'authorize' and 'register' actions have additional error codes
552
	 *
553
	 * state_missing: a state ( user id ) was not supplied
554
	 * state_malformed: state is not the correct data type
555
	 * invalid_state: supplied state does not match the stored state
556
	 *
557
	 * @param array $params action An array of 3 parameters:
558
	 *     [0]: string action. Possible values are `authorize`, `publicize` and `register`.
559
	 *     [1]: string secret_1.
560
	 *     [2]: int state.
561
	 * @return \IXR_Error|string IXR_Error on failure, secret_2 on success.
562
	 */
563
	public function verify_action( $params ) {
564
		$action        = isset( $params[0] ) ? $params[0] : '';
565
		$verify_secret = isset( $params[1] ) ? $params[1] : '';
566
		$state         = isset( $params[2] ) ? $params[2] : '';
567
568
		$result = ( new Secrets() )->verify( $action, $verify_secret, $state );
569
570
		if ( is_wp_error( $result ) ) {
571
			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 568 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...
572
		}
573
574
		return $result;
575
	}
576
577
	/**
578
	 * Wrapper for wp_authenticate( $username, $password );
579
	 *
580
	 * @return \WP_User|bool
581
	 */
582
	public function login() {
583
		$this->connection->require_jetpack_authentication();
584
		$user = wp_authenticate( 'username', 'password' );
585
		if ( is_wp_error( $user ) ) {
586
			if ( 'authentication_failed' === $user->get_error_code() ) { // Generic error could mean most anything.
587
				$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...
588
			} else {
589
				$this->error = $user;
590
			}
591
			return false;
592
		} elseif ( ! $user ) { // Shouldn't happen.
593
			$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...
594
			return false;
595
		}
596
597
		wp_set_current_user( $user->ID );
598
599
		return $user;
600
	}
601
602
	/**
603
	 * Returns the current error as an \IXR_Error
604
	 *
605
	 * @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...
606
	 * @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...
607
	 * @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...
608
	 * @return bool|\IXR_Error
609
	 */
610
	public function error( $error = null, $event_name = null, $user = null ) {
611
		if ( null !== $event_name ) {
612
			// This action is documented in class.jetpack-xmlrpc-server.php.
613
			do_action( 'jetpack_xmlrpc_server_event', $event_name, 'fail', $error, $user );
614
		}
615
616
		if ( ! is_null( $error ) ) {
617
			$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...
618
		}
619
620
		if ( is_wp_error( $this->error ) ) {
621
			$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...
622
			if ( ! $code ) {
623
				$code = -10520;
624
			}
625
			$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...
626
			return new \IXR_Error( $code, $message );
627
		} elseif ( is_a( $this->error, 'IXR_Error' ) ) {
628
			return $this->error;
629
		}
630
631
		return false;
632
	}
633
634
	/* API Methods */
635
636
	/**
637
	 * Just authenticates with the given Jetpack credentials.
638
	 *
639
	 * @return string A success string. The Jetpack plugin filters it and make it return the Jetpack plugin version.
640
	 */
641
	public function test_connection() {
642
		/**
643
		 * Filters the successful response of the XMLRPC test_connection method
644
		 *
645
		 * @param string $response The response string.
646
		 */
647
		return apply_filters( 'jetpack_xmlrpc_test_connection_response', 'success' );
648
	}
649
650
	/**
651
	 * Test the API user code.
652
	 *
653
	 * @param array $args arguments identifying the test site.
654
	 */
655
	public function test_api_user_code( $args ) {
656
		$client_id = (int) $args[0];
657
		$user_id   = (int) $args[1];
658
		$nonce     = (string) $args[2];
659
		$verify    = (string) $args[3];
660
661
		if ( ! $client_id || ! $user_id || ! strlen( $nonce ) || 32 !== strlen( $verify ) ) {
662
			return false;
663
		}
664
665
		$user = get_user_by( 'id', $user_id );
666
		if ( ! $user || is_wp_error( $user ) ) {
667
			return false;
668
		}
669
670
		/* phpcs:ignore
671
		 debugging
672
		error_log( "CLIENT: $client_id" );
673
		error_log( "USER:   $user_id" );
674
		error_log( "NONCE:  $nonce" );
675
		error_log( "VERIFY: $verify" );
676
		*/
677
678
		$jetpack_token = ( new Tokens() )->get_access_token( $user_id );
679
680
		$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
681
		if ( ! $api_user_code ) {
682
			return false;
683
		}
684
685
		$hmac = hash_hmac(
686
			'md5',
687
			json_encode( // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
688
				(object) array(
689
					'client_id' => (int) $client_id,
690
					'user_id'   => (int) $user_id,
691
					'nonce'     => (string) $nonce,
692
					'code'      => (string) $api_user_code,
693
				)
694
			),
695
			$jetpack_token->secret
696
		);
697
698
		if ( ! hash_equals( $hmac, $verify ) ) {
699
			return false;
700
		}
701
702
		return $user_id;
703
	}
704
705
	/**
706
	 * Unlink a user from WordPress.com
707
	 *
708
	 * When the request is done without any parameter, this XMLRPC callback gets an empty array as input.
709
	 *
710
	 * 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.
711
	 *
712
	 * If $user_id is is provided, it will try to disconnect the informed user, even if it's the Master User.
713
	 *
714
	 * @param mixed $user_id The user ID to disconnect from this site.
715
	 */
716
	public function unlink_user( $user_id = array() ) {
717
		$user_id = (int) $user_id;
718
		if ( $user_id < 1 ) {
719
			$user_id = null;
720
		}
721
		/**
722
		 * Fired when we want to log an event to the Jetpack event log.
723
		 *
724
		 * @since 7.7.0
725
		 *
726
		 * @param string $code Unique name for the event.
727
		 * @param string $data Optional data about the event.
728
		 */
729
		do_action( 'jetpack_event_log', 'unlink' );
730
		return $this->connection->disconnect_user(
731
			$user_id,
732
			(bool) $user_id
733
		);
734
	}
735
736
	/**
737
	 * Returns any object that is able to be synced.
738
	 *
739
	 * @deprecated since 7.8.0
740
	 * @see Automattic\Jetpack\Sync\Sender::sync_object()
741
	 *
742
	 * @param array $args the synchronized object parameters.
743
	 * @return string Encoded sync object.
744
	 */
745
	public function sync_object( $args ) {
746
		_deprecated_function( __METHOD__, 'jetpack-7.8', 'Automattic\\Jetpack\\Sync\\Sender::sync_object' );
747
		return Sender::get_instance()->sync_object( $args );
748
	}
749
750
	/**
751
	 * Returns the home URL and site URL for the current site which can be used on the WPCOM side for
752
	 * IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
753
	 * and the remote Jetpack site.
754
	 *
755
	 * @return array
756
	 */
757
	public function validate_urls_for_idc_mitigation() {
758
		return array(
759
			'home'    => Functions::home_url(),
760
			'siteurl' => Functions::site_url(),
761
		);
762
	}
763
764
	/**
765
	 * Updates the attachment parent object.
766
	 *
767
	 * @param array $args attachment and parent identifiers.
768
	 */
769
	public function update_attachment_parent( $args ) {
770
		$attachment_id = (int) $args[0];
771
		$parent_id     = (int) $args[1];
772
773
		return wp_update_post(
774
			array(
775
				'ID'          => $attachment_id,
776
				'post_parent' => $parent_id,
777
			)
778
		);
779
	}
780
781
	/**
782
	 * Handles authorization actions after connecting a site, such as enabling modules.
783
	 *
784
	 * This do_post_authorization() is used in this class, as opposed to calling
785
	 * Jetpack::handle_post_authorization_actions() directly so that we can mock this method as necessary.
786
	 *
787
	 * @return void
788
	 */
789
	public function do_post_authorization() {
790
		/** This filter is documented in class.jetpack-cli.php */
791
		$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
792
		Jetpack::handle_post_authorization_actions( $enable_sso, false, false );
793
	}
794
795
	/**
796
	 * Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
797
	 *
798
	 * Disconnect this blog from the connected wordpress.com account
799
	 *
800
	 * @deprecated since 9.6.0
801
	 * @see Jetpack_XMLRPC_Methods::disconnect_blog() in the Jetpack plugin
802
	 *
803
	 * @return boolean
804
	 */
805
	public function disconnect_blog() {
806
		_deprecated_function( __METHOD__, 'jetpack-9.6', 'Jetpack_XMLRPC_Methods::disconnect_blog()' );
807
		if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
808
			return Jetpack_XMLRPC_Methods::disconnect_blog();
809
		}
810
		return false;
811
	}
812
813
	/**
814
	 * Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
815
	 *
816
	 * Returns what features are available. Uses the slug of the module files.
817
	 *
818
	 * @deprecated since 9.6.0
819
	 * @see Jetpack_XMLRPC_Methods::features_available() in the Jetpack plugin
820
	 *
821
	 * @return array
822
	 */
823
	public function features_available() {
824
		_deprecated_function( __METHOD__, 'jetpack-9.6', 'Jetpack_XMLRPC_Methods::features_available()' );
825
		if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
826
			return Jetpack_XMLRPC_Methods::features_available();
827
		}
828
		return array();
829
	}
830
831
	/**
832
	 * Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
833
	 *
834
	 * Returns what features are enabled. Uses the slug of the modules files.
835
	 *
836
	 * @deprecated since 9.6.0
837
	 * @see Jetpack_XMLRPC_Methods::features_enabled() in the Jetpack plugin
838
	 *
839
	 * @return array
840
	 */
841
	public function features_enabled() {
842
		_deprecated_function( __METHOD__, 'jetpack-9.6', 'Jetpack_XMLRPC_Methods::features_enabled()' );
843
		if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
844
			return Jetpack_XMLRPC_Methods::features_enabled();
845
		}
846
		return array();
847
	}
848
849
	/**
850
	 * Deprecated: This method is no longer part of the Connection package and now lives on the Jetpack plugin.
851
	 *
852
	 * Serve a JSON API request.
853
	 *
854
	 * @deprecated since 9.6.0
855
	 * @see Jetpack_XMLRPC_Methods::json_api() in the Jetpack plugin
856
	 *
857
	 * @param array $args request arguments.
858
	 */
859
	public function json_api( $args = array() ) {
860
		_deprecated_function( __METHOD__, 'jetpack-9.6', 'Jetpack_XMLRPC_Methods::json_api()' );
861
		if ( class_exists( 'Jetpack_XMLRPC_Methods' ) ) {
862
			return Jetpack_XMLRPC_Methods::json_api( $args );
863
		}
864
		return array();
865
	}
866
867
}
868