Completed
Push — update/connection-package-xmlr... ( e808f1...1dbda8 )
by
unknown
240:07 queued 229:57
created

Jetpack_XMLRPC_Server::remote_connect()   B

Complexity

Conditions 9
Paths 11

Size

Total Lines 67

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 11
nop 2
dl 0
loc 67
rs 7.1644
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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