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