Completed
Push — e2e/slack-notification ( 4d0aca...d43a76 )
by
unknown
19:59 queued 10:28
created

Jetpack_XMLRPC_Server::do_post_authorization()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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