These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /** |
||
4 | * Just a sack of functions. Not actually an IXR_Server |
||
5 | */ |
||
6 | class Jetpack_XMLRPC_Server { |
||
7 | /** |
||
8 | * The current error object |
||
9 | */ |
||
10 | public $error = null; |
||
11 | |||
12 | /** |
||
13 | * Whitelist of the XML-RPC methods available to the Jetpack Server. If the |
||
14 | * user is not authenticated (->login()) then the methods are never added, |
||
15 | * so they will get a "does not exist" error. |
||
16 | */ |
||
17 | function xmlrpc_methods( $core_methods ) { |
||
18 | $jetpack_methods = array( |
||
19 | 'jetpack.jsonAPI' => array( $this, 'json_api' ), |
||
20 | 'jetpack.verifyAction' => array( $this, 'verify_action' ), |
||
21 | ); |
||
22 | |||
23 | $user = $this->login(); |
||
24 | |||
25 | if ( $user ) { |
||
26 | $jetpack_methods = array_merge( $jetpack_methods, array( |
||
27 | 'jetpack.testConnection' => array( $this, 'test_connection' ), |
||
28 | 'jetpack.testAPIUserCode' => array( $this, 'test_api_user_code' ), |
||
29 | 'jetpack.featuresAvailable' => array( $this, 'features_available' ), |
||
30 | 'jetpack.featuresEnabled' => array( $this, 'features_enabled' ), |
||
31 | 'jetpack.getPost' => array( $this, 'get_post' ), |
||
32 | 'jetpack.getPosts' => array( $this, 'get_posts' ), |
||
33 | 'jetpack.getComment' => array( $this, 'get_comment' ), |
||
34 | 'jetpack.getComments' => array( $this, 'get_comments' ), |
||
35 | 'jetpack.disconnectBlog' => array( $this, 'disconnect_blog' ), |
||
36 | 'jetpack.unlinkUser' => array( $this, 'unlink_user' ), |
||
37 | ) ); |
||
38 | |||
39 | if ( isset( $core_methods['metaWeblog.editPost'] ) ) { |
||
40 | $jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject']; |
||
41 | $jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' ); |
||
42 | } |
||
43 | |||
44 | /** |
||
45 | * Filters the XML-RPC methods available to Jetpack for authenticated users. |
||
46 | * |
||
47 | * @since 1.1.0 |
||
48 | * |
||
49 | * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server. |
||
50 | * @param array $core_methods Available core XML-RPC methods. |
||
51 | * @param WP_User $user Information about a given WordPress user. |
||
52 | */ |
||
53 | $jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $user ); |
||
54 | } |
||
55 | |||
56 | /** |
||
57 | * Filters the XML-RPC methods available to Jetpack for unauthenticated users. |
||
58 | * |
||
59 | * @since 3.0.0 |
||
60 | * |
||
61 | * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server. |
||
62 | * @param array $core_methods Available core XML-RPC methods. |
||
63 | */ |
||
64 | return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods ); |
||
65 | } |
||
66 | |||
67 | /** |
||
68 | * Whitelist of the bootstrap XML-RPC methods |
||
69 | */ |
||
70 | function bootstrap_xmlrpc_methods() { |
||
71 | return array( |
||
72 | 'jetpack.verifyRegistration' => array( $this, 'verify_registration' ), |
||
73 | ); |
||
74 | } |
||
75 | |||
76 | /** |
||
77 | * Verifies that Jetpack.WordPress.com received a registration request from this site |
||
78 | */ |
||
79 | function verify_registration( $verify_secret ) { |
||
80 | return $this->verify_action( array( 'register', $verify_secret ) ); |
||
81 | } |
||
82 | |||
83 | /** |
||
84 | * @return WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure |
||
85 | * |
||
86 | * Possible error_codes: |
||
87 | * |
||
88 | * verify_secret_1_missing |
||
89 | * verify_secret_1_malformed |
||
90 | * verify_secrets_missing: No longer have verification secrets stored |
||
91 | * verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com |
||
92 | */ |
||
93 | function verify_action( $params ) { |
||
94 | $action = $params[0]; |
||
95 | $verify_secret = $params[1]; |
||
96 | |||
97 | if ( empty( $verify_secret ) ) { |
||
98 | return $this->error( new Jetpack_Error( 'verify_secret_1_missing', sprintf( 'The required "%s" parameter is missing.', 'secret_1' ), 400 ) ); |
||
99 | } else if ( !is_string( $verify_secret ) ) { |
||
100 | return $this->error( new Jetpack_Error( 'verify_secret_1_malformed', sprintf( 'The required "%s" parameter is malformed.', 'secret_1' ), 400 ) ); |
||
101 | } |
||
102 | |||
103 | $secrets = Jetpack_Options::get_option( $action ); |
||
104 | if ( !$secrets || is_wp_error( $secrets ) ) { |
||
105 | Jetpack_Options::delete_option( $action ); |
||
106 | return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification took too long', 400 ) ); |
||
107 | } |
||
108 | |||
109 | @list( $secret_1, $secret_2, $secret_eol ) = explode( ':', $secrets ); |
||
110 | if ( empty( $secret_1 ) || empty( $secret_2 ) || empty( $secret_eol ) || $secret_eol < time() ) { |
||
111 | Jetpack_Options::delete_option( $action ); |
||
112 | return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification took too long', 400 ) ); |
||
113 | } |
||
114 | |||
115 | if ( ! hash_equals( $verify_secret, $secret_1 ) ) { |
||
116 | Jetpack_Options::delete_option( $action ); |
||
117 | return $this->error( new Jetpack_Error( 'verify_secrets_mismatch', 'Secret mismatch', 400 ) ); |
||
118 | } |
||
119 | |||
120 | Jetpack_Options::delete_option( $action ); |
||
121 | |||
122 | return $secret_2; |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * Wrapper for wp_authenticate( $username, $password ); |
||
127 | * |
||
128 | * @return WP_User|IXR_Error |
||
129 | */ |
||
130 | function login() { |
||
131 | Jetpack::init()->require_jetpack_authentication(); |
||
132 | $user = wp_authenticate( 'username', 'password' ); |
||
133 | if ( is_wp_error( $user ) ) { |
||
134 | if ( 'authentication_failed' == $user->get_error_code() ) { // Generic error could mean most anything. |
||
135 | $this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 ); |
||
136 | } else { |
||
137 | $this->error = $user; |
||
138 | } |
||
139 | return false; |
||
140 | } else if ( !$user ) { // Shouldn't happen. |
||
141 | $this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 ); |
||
142 | return false; |
||
143 | } |
||
144 | |||
145 | return $user; |
||
146 | } |
||
147 | |||
148 | /** |
||
149 | * Returns the current error as an IXR_Error |
||
150 | * |
||
151 | * @return null|IXR_Error |
||
152 | */ |
||
153 | function error( $error = null ) { |
||
154 | if ( !is_null( $error ) ) { |
||
155 | $this->error = $error; |
||
156 | } |
||
157 | |||
158 | if ( is_wp_error( $this->error ) ) { |
||
159 | $code = $this->error->get_error_data(); |
||
160 | if ( !$code ) { |
||
161 | $code = -10520; |
||
162 | } |
||
163 | $message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() ); |
||
164 | return new IXR_Error( $code, $message ); |
||
165 | } else if ( is_a( $this->error, 'IXR_Error' ) ) { |
||
166 | return $this->error; |
||
167 | } |
||
168 | |||
169 | return false; |
||
170 | } |
||
171 | |||
172 | /* API Methods */ |
||
173 | |||
174 | /** |
||
175 | * Just authenticates with the given Jetpack credentials. |
||
176 | * |
||
177 | * @return bool|IXR_Error |
||
178 | */ |
||
179 | function test_connection() { |
||
180 | return JETPACK__VERSION; |
||
181 | } |
||
182 | |||
183 | function test_api_user_code( $args ) { |
||
184 | $client_id = (int) $args[0]; |
||
185 | $user_id = (int) $args[1]; |
||
186 | $nonce = (string) $args[2]; |
||
187 | $verify = (string) $args[3]; |
||
188 | |||
189 | if ( !$client_id || !$user_id || !strlen( $nonce ) || 32 !== strlen( $verify ) ) { |
||
190 | return false; |
||
191 | } |
||
192 | |||
193 | $user = get_user_by( 'id', $user_id ); |
||
194 | if ( !$user || is_wp_error( $user ) ) { |
||
195 | return false; |
||
196 | } |
||
197 | |||
198 | /* debugging |
||
0 ignored issues
–
show
|
|||
199 | error_log( "CLIENT: $client_id" ); |
||
200 | error_log( "USER: $user_id" ); |
||
201 | error_log( "NONCE: $nonce" ); |
||
202 | error_log( "VERIFY: $verify" ); |
||
203 | */ |
||
204 | |||
205 | $jetpack_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER ); |
||
206 | |||
207 | $api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true ); |
||
208 | if ( !$api_user_code ) { |
||
209 | return false; |
||
210 | } |
||
211 | |||
212 | $hmac = hash_hmac( 'md5', json_encode( (object) array( |
||
213 | 'client_id' => (int) $client_id, |
||
214 | 'user_id' => (int) $user_id, |
||
215 | 'nonce' => (string) $nonce, |
||
216 | 'code' => (string) $api_user_code, |
||
217 | ) ), $jetpack_token->secret ); |
||
218 | |||
219 | if ( $hmac !== $verify ) { |
||
220 | return false; |
||
221 | } |
||
222 | |||
223 | return $user_id; |
||
224 | } |
||
225 | |||
226 | /** |
||
227 | * Disconnect this blog from the connected wordpress.com account |
||
228 | * @return boolean |
||
229 | */ |
||
230 | function disconnect_blog() { |
||
231 | Jetpack::log( 'disconnect' ); |
||
232 | Jetpack::disconnect(); |
||
233 | |||
234 | return true; |
||
235 | } |
||
236 | |||
237 | /** |
||
238 | * Unlink a user from WordPress.com |
||
239 | * |
||
240 | * This will fail if called by the Master User. |
||
241 | */ |
||
242 | function unlink_user() { |
||
243 | Jetpack::log( 'unlink' ); |
||
244 | return Jetpack::unlink_user(); |
||
245 | } |
||
246 | |||
247 | /** |
||
248 | * Returns what features are available. Uses the slug of the module files. |
||
249 | * |
||
250 | * @return array|IXR_Error |
||
251 | */ |
||
252 | View Code Duplication | function features_available() { |
|
253 | $raw_modules = Jetpack::get_available_modules(); |
||
254 | $modules = array(); |
||
255 | foreach ( $raw_modules as $module ) { |
||
256 | $modules[] = Jetpack::get_module_slug( $module ); |
||
257 | } |
||
258 | |||
259 | return $modules; |
||
260 | } |
||
261 | |||
262 | /** |
||
263 | * Returns what features are enabled. Uses the slug of the modules files. |
||
264 | * |
||
265 | * @return array|IXR_Error |
||
266 | */ |
||
267 | View Code Duplication | function features_enabled() { |
|
268 | $raw_modules = Jetpack::get_active_modules(); |
||
269 | $modules = array(); |
||
270 | foreach ( $raw_modules as $module ) { |
||
271 | $modules[] = Jetpack::get_module_slug( $module ); |
||
272 | } |
||
273 | |||
274 | return $modules; |
||
275 | } |
||
276 | |||
277 | function get_post( $id ) { |
||
278 | if ( !$id = (int) $id ) { |
||
279 | return false; |
||
280 | } |
||
281 | |||
282 | $jetpack = Jetpack::init(); |
||
283 | |||
284 | $post = $jetpack->sync->get_post( $id ); |
||
285 | return $post; |
||
286 | } |
||
287 | |||
288 | View Code Duplication | function get_posts( $args ) { |
|
289 | list( $post_ids ) = $args; |
||
290 | $post_ids = array_map( 'intval', (array) $post_ids ); |
||
291 | $jp = Jetpack::init(); |
||
292 | $sync_data = $jp->sync->get_content( array( 'posts' => $post_ids ) ); |
||
293 | |||
294 | return $sync_data; |
||
295 | } |
||
296 | |||
297 | function get_comment( $id ) { |
||
298 | if ( !$id = (int) $id ) { |
||
299 | return false; |
||
300 | } |
||
301 | |||
302 | $jetpack = Jetpack::init(); |
||
303 | |||
304 | $comment = $jetpack->sync->get_comment( $id ); |
||
305 | if ( !is_array( $comment ) ) |
||
306 | return false; |
||
307 | |||
308 | $post = $jetpack->sync->get_post( $comment['comment_post_ID'] ); |
||
309 | if ( !$post ) { |
||
310 | return false; |
||
311 | } |
||
312 | |||
313 | return $comment; |
||
314 | } |
||
315 | |||
316 | View Code Duplication | function get_comments( $args ) { |
|
317 | list( $comment_ids ) = $args; |
||
318 | $comment_ids = array_map( 'intval', (array) $comment_ids ); |
||
319 | $jp = Jetpack::init(); |
||
320 | $sync_data = $jp->sync->get_content( array( 'comments' => $comment_ids ) ); |
||
321 | |||
322 | return $sync_data; |
||
323 | } |
||
324 | |||
325 | function update_attachment_parent( $args ) { |
||
326 | $attachment_id = (int) $args[0]; |
||
327 | $parent_id = (int) $args[1]; |
||
328 | |||
329 | return wp_update_post( array( |
||
330 | 'ID' => $attachment_id, |
||
331 | 'post_parent' => $parent_id, |
||
332 | ) ); |
||
333 | } |
||
334 | |||
335 | function json_api( $args = array() ) { |
||
336 | $json_api_args = $args[0]; |
||
337 | $verify_api_user_args = $args[1]; |
||
338 | |||
339 | $method = (string) $json_api_args[0]; |
||
340 | $url = (string) $json_api_args[1]; |
||
341 | $post_body = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2]; |
||
342 | $user_details = (array) $json_api_args[4]; |
||
343 | $locale = (string) $json_api_args[5]; |
||
344 | |||
345 | if ( !$verify_api_user_args ) { |
||
346 | $user_id = 0; |
||
347 | } elseif ( 'internal' === $verify_api_user_args[0] ) { |
||
348 | $user_id = (int) $verify_api_user_args[1]; |
||
349 | if ( $user_id ) { |
||
350 | $user = get_user_by( 'id', $user_id ); |
||
351 | if ( !$user || is_wp_error( $user ) ) { |
||
352 | return false; |
||
353 | } |
||
354 | } |
||
355 | } else { |
||
356 | $user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args ); |
||
357 | if ( !$user_id ) { |
||
358 | return false; |
||
359 | } |
||
360 | } |
||
361 | |||
362 | /* debugging |
||
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
47% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. ![]() |
|||
363 | error_log( "-- begin json api via jetpack debugging -- " ); |
||
364 | error_log( "METHOD: $method" ); |
||
365 | error_log( "URL: $url" ); |
||
366 | error_log( "POST BODY: $post_body" ); |
||
367 | error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) ); |
||
368 | error_log( "VERIFIED USER_ID: " . (int) $user_id ); |
||
369 | error_log( "-- end json api via jetpack debugging -- " ); |
||
370 | */ |
||
371 | |||
372 | if ( 'en' !== $locale ) { |
||
373 | // .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them. |
||
374 | $new_locale = $locale; |
||
375 | if ( strpos( $locale, '-' ) !== false ) { |
||
376 | $pieces = explode( '-', $locale ); |
||
377 | $new_locale = $locale_pieces[0]; |
||
378 | $new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : ''; |
||
379 | } else { |
||
380 | // .com might pass 'fr' because thats what our language files are named as, where core seems |
||
381 | // to do fr_FR - so try that if we don't think we can load the file. |
||
382 | if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) { |
||
383 | $new_locale = $locale . '_' . strtoupper( $locale ); |
||
384 | } |
||
385 | } |
||
386 | |||
387 | if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) { |
||
388 | unload_textdomain( 'default' ); |
||
389 | load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' ); |
||
390 | } |
||
391 | } |
||
392 | |||
393 | $old_user = wp_get_current_user(); |
||
394 | wp_set_current_user( $user_id ); |
||
395 | |||
396 | $token = Jetpack_Data::get_access_token( get_current_user_id() ); |
||
397 | if ( !$token || is_wp_error( $token ) ) { |
||
398 | return false; |
||
399 | } |
||
400 | |||
401 | define( 'REST_API_REQUEST', true ); |
||
402 | define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' ); |
||
403 | |||
404 | // needed? |
||
405 | require_once ABSPATH . 'wp-admin/includes/admin.php'; |
||
406 | |||
407 | require_once JETPACK__PLUGIN_DIR . 'class.json-api.php'; |
||
408 | $api = WPCOM_JSON_API::init( $method, $url, $post_body ); |
||
409 | $api->token_details['user'] = $user_details; |
||
410 | require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php'; |
||
411 | |||
412 | $display_errors = ini_set( 'display_errors', 0 ); |
||
413 | ob_start(); |
||
414 | $content_type = $api->serve( false ); |
||
415 | $output = ob_get_clean(); |
||
416 | ini_set( 'display_errors', $display_errors ); |
||
417 | |||
418 | $nonce = wp_generate_password( 10, false ); |
||
419 | $hmac = hash_hmac( 'md5', $nonce . $output, $token->secret ); |
||
420 | |||
421 | wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 ); |
||
422 | |||
423 | return array( |
||
424 | (string) $output, |
||
425 | (string) $nonce, |
||
426 | (string) $hmac, |
||
427 | ); |
||
428 | } |
||
429 | } |
||
430 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.