PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.2
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.2
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 +52 -22 6.0.12 → 6.2.2 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,17 +243,21 @@
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 - // Save api key v2.
253 + // Save api key v2. insert_rest_api_key() returns false when the
254 + // underlying wpdb->insert() fails, so we guard against the bool
255 + // case before treating $api_key as an array; PHP 8+ otherwise
256 + // raises a deprecation notice on array-offset access against a
257 + // bool, and the empty() check would silently pass.
251 258 $api_key = $this->db->insert_rest_api_key( $consumer_key, $consumer_secret, $scope, $desc, $active );
252 - if ( empty( $api_key['key_id'] ) ) {
259 + if ( ! is_array( $api_key ) || empty( $api_key['key_id'] ) ) {
253 260 return new WP_Error(
254 261 'create_key_failed',
255 262 __( 'Create API key failed.', 'mainwp' ),
256 263 );
@@ -289,17 +296,27 @@
289 296 __( 'API key not found.', 'mainwp' ),
290 297 );
291 298 }
292 299
293 - // Get request body.
294 - $body = $request->get_json_params();
295 - 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' ) ) {
296 309 return new WP_Error(
297 310 'empty_body',
298 311 __( 'Request body is empty.', 'mainwp' ),
312 + array( 'status' => 400 )
299 313 );
300 314 }
301 315
316 + $description_param = $request->get_param( 'description' );
317 + $permissions_param = $request->get_param( 'permissions' );
318 +
302 319 // Get key identifier.
303 320 $cons_key_id = $request->get_param( 'key_identifier' );
304 321
305 322 // Determine defaults based on key version (v1 = array, v2 = object).
@@ -314,17 +331,17 @@
314 331 }
315 332
316 333 // Determine active.
317 334 $active = $current_enabled;
318 - if ( isset( $body['active'] ) ) {
319 - $active = $body['active'] ? 1 : 0;
335 + if ( $request->has_param( 'active' ) ) {
336 + $active = $request->get_param( 'active' ) ? 1 : 0;
320 337 }
321 338 // Determine description.
322 - $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;
323 340
324 341 // Edit api key v1.
325 342 if ( ! is_numeric( $cons_key_id ) ) {
326 - $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;
327 344 $save = false;
328 345 $all_keys = get_option( 'mainwp_rest_api_keys', false );
329 346 if ( is_array( $all_keys ) && isset( $all_keys[ $cons_key_id ] ) ) {
330 347 $item = $all_keys[ $cons_key_id ];
@@ -345,9 +362,9 @@
345 362 }
346 363
347 364 MainWP_Utility::update_option( 'mainwp_rest_api_keys', $all_keys );
348 365 } else {
349 - $scope = ! empty( $body['permissions'] ) ? $this->determine_scope( $body['permissions'] ) : $current_permissions;
366 + $scope = ! empty( $permissions_param ) ? $this->determine_scope( $permissions_param ) : $current_permissions;
350 367 // Update api key.
351 368 $updated = MainWP_DB::instance()->update_rest_api_key( $api_key->key_id, $scope, $desc, $active );
352 369 if ( false === $updated ) {
353 370 return new WP_Error(
@@ -460,9 +477,12 @@
460 477 'active' => array(
461 478 'required' => true,
462 479 'type' => 'boolean',
463 480 'description' => __( 'Active or disable API key.', 'mainwp' ),
464 - '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',
465 485 ),
466 486 'description' => array(
467 487 'required' => false,
468 488 'type' => 'string',
@@ -467,8 +487,9 @@
467 487 'required' => false,
468 488 'type' => 'string',
469 489 'description' => __( 'API key description.', 'mainwp' ),
470 490 'sanitize_callback' => 'sanitize_text_field',
491 + 'validate_callback' => 'rest_validate_request_arg',
471 492 ),
472 493 'permissions' => array(
473 494 'required' => true,
474 495 'type' => 'string',
@@ -491,9 +512,10 @@
491 512 'active' => array(
492 513 'required' => false,
493 514 'type' => 'boolean',
494 515 'description' => __( 'Active or disable API key.', 'mainwp' ),
495 - 'sanitize_callback' => 'rest_sanitize_boolean',
516 + 'sanitize_callback' => 'rest_parse_request_arg',
517 + 'validate_callback' => 'rest_validate_request_arg',
496 518 ),
497 519 'description' => array(
498 520 'required' => false,
499 521 'type' => 'string',
@@ -498,8 +520,9 @@
498 520 'required' => false,
499 521 'type' => 'string',
500 522 'description' => __( 'API key description.', 'mainwp' ),
501 523 'sanitize_callback' => 'sanitize_text_field',
524 + 'validate_callback' => 'rest_validate_request_arg',
502 525 ),
503 526 'permissions' => array(
504 527 'required' => false,
505 528 'type' => 'string',
@@ -519,8 +542,15 @@
519 542 * @param string $param Parameter name.
520 543 * @return bool|WP_Error
521 544 */
522 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 +
523 553 if ( empty( $value ) ) {
524 554 return false;
525 555 }
526 556