PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
← All changes | includes/rest-api/controller/version2/class-mainwp-rest-api-keys-controller.php +46 -20 6.1.8 → 6.2.1 View file →
@@ -224,17 +224,20 @@
224 224 *
225 225 * @return WP_Error|WP_REST_Response
226 226 */
227 227 public function create_new_key( $request ) { // phpcs:ignore -- NOSONAR - complex.
228 - // Get request body.
229 - $body = $request->get_body_params();
230 - if ( empty( $body ) ) {
231 - return new WP_Error(
232 - 'empty_body',
233 - __( 'Request body is empty.', 'mainwp' ),
234 - );
228 + $body = $this->get_request_body_data( $request );
229 + if ( is_wp_error( $body ) ) {
230 + return $body;
235 231 }
236 232
233 + // Core validated and sanitized the registered args from every bag
234 + // (query included); reading only the body bag would ignore a query
235 + // value that satisfied the required-arg check.
236 + $active_param = $request->get_param( 'active' );
237 + $permissions_param = $request->get_param( 'permissions' );
238 + $description_param = $request->get_param( 'description' );
239 +
237 240 // Generate consumer key and secret.
238 241 $_consumer_key = MainWP_Rest_Api_Page::mainwp_generate_rand_hash();
239 242 $_consumer_secret = MainWP_Rest_Api_Page::mainwp_generate_rand_hash();
240 243
@@ -240,11 +243,11 @@
240 243
241 244 // Map data.
242 245 $consumer_key = 'ck_' . $_consumer_key;
243 246 $consumer_secret = 'cs_' . $_consumer_secret;
244 - $active = ! empty( $body['active'] ) ? 1 : 0;
245 - $permission = ! empty( $body['permissions'] ) ? sanitize_text_field( wp_unslash( $body['permissions'] ) ) : '';
246 - $desc = ! empty( $body['description'] ) ? sanitize_text_field( wp_unslash( $body['description'] ) ) : '';
247 + $active = ! empty( $active_param ) ? 1 : 0;
248 + $permission = ! empty( $permissions_param ) ? sanitize_text_field( $permissions_param ) : '';
249 + $desc = ! empty( $description_param ) ? sanitize_text_field( $description_param ) : '';
247 250 $scope = $this->determine_scope( $permission );
248 251
249 252 try {
250 253 // Save api key v2. insert_rest_api_key() returns false when the
@@ -293,17 +296,27 @@
293 296 __( 'API key not found.', 'mainwp' ),
294 297 );
295 298 }
296 299
297 - // Get request body.
298 - $body = $request->get_json_params();
299 - if ( empty( $body ) ) {
300 + $body = $this->get_request_body_data( $request );
301 + if ( is_wp_error( $body ) ) {
302 + return $body;
303 + }
304 +
305 + // Every field here is optional, so a JSON payload sent without its content type
306 + // (which core parses as one URL-encoded key) would otherwise "succeed" and change nothing.
307 + // has_param() also sees a validated query value the body bag alone would miss.
308 + if ( ! $request->has_param( 'active' ) && ! $request->has_param( 'description' ) && ! $request->has_param( 'permissions' ) ) {
300 309 return new WP_Error(
301 310 'empty_body',
302 311 __( 'Request body is empty.', 'mainwp' ),
312 + array( 'status' => 400 )
303 313 );
304 314 }
305 315
316 + $description_param = $request->get_param( 'description' );
317 + $permissions_param = $request->get_param( 'permissions' );
318 +
306 319 // Get key identifier.
307 320 $cons_key_id = $request->get_param( 'key_identifier' );
308 321
309 322 // Determine defaults based on key version (v1 = array, v2 = object).
@@ -318,17 +331,17 @@
318 331 }
319 332
320 333 // Determine active.
321 334 $active = $current_enabled;
322 - if ( isset( $body['active'] ) ) {
323 - $active = $body['active'] ? 1 : 0;
335 + if ( $request->has_param( 'active' ) ) {
336 + $active = $request->get_param( 'active' ) ? 1 : 0;
324 337 }
325 338 // Determine description.
326 - $desc = ! empty( $body['description'] ) ? sanitize_text_field( wp_unslash( $body['description'] ) ) : $current_description;
339 + $desc = ! empty( $description_param ) ? sanitize_text_field( $description_param ) : $current_description;
327 340
328 341 // Edit api key v1.
329 342 if ( ! is_numeric( $cons_key_id ) ) {
330 - $scope = ! empty( $body['permissions'] ) ? $this->determine_scope( $body['permissions'], 'v1' ) : $current_permissions;
343 + $scope = ! empty( $permissions_param ) ? $this->determine_scope( $permissions_param, 'v1' ) : $current_permissions;
331 344 $save = false;
332 345 $all_keys = get_option( 'mainwp_rest_api_keys', false );
333 346 if ( is_array( $all_keys ) && isset( $all_keys[ $cons_key_id ] ) ) {
334 347 $item = $all_keys[ $cons_key_id ];
@@ -349,9 +362,9 @@
349 362 }
350 363
351 364 MainWP_Utility::update_option( 'mainwp_rest_api_keys', $all_keys );
352 365 } else {
353 - $scope = ! empty( $body['permissions'] ) ? $this->determine_scope( $body['permissions'] ) : $current_permissions;
366 + $scope = ! empty( $permissions_param ) ? $this->determine_scope( $permissions_param ) : $current_permissions;
354 367 // Update api key.
355 368 $updated = MainWP_DB::instance()->update_rest_api_key( $api_key->key_id, $scope, $desc, $active );
356 369 if ( false === $updated ) {
357 370 return new WP_Error(
@@ -464,9 +477,12 @@
464 477 'active' => array(
465 478 'required' => true,
466 479 'type' => 'boolean',
467 480 'description' => __( 'Active or disable API key.', 'mainwp' ),
468 - 'sanitize_callback' => 'rest_sanitize_boolean',
481 + // rest_parse_request_arg validates before it sanitizes, so an explicit
482 + // JSON null is a 400 instead of being coerced to false past the validator.
483 + 'sanitize_callback' => 'rest_parse_request_arg',
484 + 'validate_callback' => 'rest_validate_request_arg',
469 485 ),
470 486 'description' => array(
471 487 'required' => false,
472 488 'type' => 'string',
@@ -471,8 +487,9 @@
471 487 'required' => false,
472 488 'type' => 'string',
473 489 'description' => __( 'API key description.', 'mainwp' ),
474 490 'sanitize_callback' => 'sanitize_text_field',
491 + 'validate_callback' => 'rest_validate_request_arg',
475 492 ),
476 493 'permissions' => array(
477 494 'required' => true,
478 495 'type' => 'string',
@@ -495,9 +512,10 @@
495 512 'active' => array(
496 513 'required' => false,
497 514 'type' => 'boolean',
498 515 'description' => __( 'Active or disable API key.', 'mainwp' ),
499 - 'sanitize_callback' => 'rest_sanitize_boolean',
516 + 'sanitize_callback' => 'rest_parse_request_arg',
517 + 'validate_callback' => 'rest_validate_request_arg',
500 518 ),
501 519 'description' => array(
502 520 'required' => false,
503 521 'type' => 'string',
@@ -502,8 +520,9 @@
502 520 'required' => false,
503 521 'type' => 'string',
504 522 'description' => __( 'API key description.', 'mainwp' ),
505 523 'sanitize_callback' => 'sanitize_text_field',
524 + 'validate_callback' => 'rest_validate_request_arg',
506 525 ),
507 526 'permissions' => array(
508 527 'required' => false,
509 528 'type' => 'string',
@@ -523,8 +542,15 @@
523 542 * @param string $param Parameter name.
524 543 * @return bool|WP_Error
525 544 */
526 545 public function rest_api_validate_permissions_param( $value, $request, $param ) {
546 + // A custom validate_callback replaces the registered-type check, so the
547 + // declared string type has to be enforced here before explode() sees an array.
548 + $valid = rest_validate_request_arg( $value, $request, $param );
549 + if ( is_wp_error( $valid ) ) {
550 + return $valid;
551 + }
552 +
527 553 if ( empty( $value ) ) {
528 554 return false;
529 555 }
530 556