Make WordPress Core

Changeset 39222


Ignore:
Timestamp:
11/14/2016 04:35:35 PM (10 years ago)
Author:
joehoyle
Message:

REST API: Validate and Sanitize registered meta based off the schema.

With the addition of Array support in our schema validation functions, it's now possible to use these in the meta validation and sanitization steps. Also, this increases the test coverage of using registered via meta the API significantly.

Fixes #38531.
Props rachelbaker, tharsheblows.

Location:
trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/rest-api.php

    r39158 r39222  
    998998                if ( ! is_array( $value ) ) {
    999999                        $value = preg_split( '/[\s,]+/', $value );
     1000                }
     1001                if ( ! wp_is_numeric_array( $value ) ) {
     1002                        return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
    10001003                }
    10011004                foreach ( $value as $index => $v ) {
     
    11081111                        $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] );
    11091112                }
     1113                // Normalize to numeric array so nothing unexpected
     1114                // is in the keys.
     1115                $value = array_values( $value );
    11101116                return $value;
    11111117        }
     
    11411147        }
    11421148
     1149        if ( 'string' === $args['type'] ) {
     1150                return strval( $value );
     1151        }
     1152
    11431153        return $value;
    11441154}
  • trunk/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php

    r39179 r39222  
    8585                }
    8686
    87                 return (object) $response;
     87                return $response;
    8888        }
    8989
     
    134134                        if ( is_null( $request[ $name ] ) ) {
    135135                                $result = $this->delete_meta_value( $object_id, $name );
    136                         } elseif ( $args['single'] ) {
    137                                 $result = $this->update_meta_value( $object_id, $name, $request[ $name ] );
     136                                if ( is_wp_error( $result ) ) {
     137                                        return $result;
     138                                }
     139                                continue;
     140                        }
     141
     142                        $is_valid = rest_validate_value_from_schema( $request[ $name ], $args['schema'], 'meta.' . $name );
     143                        if ( is_wp_error( $is_valid ) ) {
     144                                $is_valid->add_data( array( 'status' => 400 ) );
     145                                return $is_valid;
     146                        }
     147
     148                        $value = rest_sanitize_value_from_schema( $request[ $name ], $args['schema'] );
     149
     150                        if ( $args['single'] ) {
     151                                $result = $this->update_meta_value( $object_id, $name, $value );
    138152                        } else {
    139                                 $result = $this->update_multi_meta_value( $object_id, $name, $request[ $name ] );
     153                                $result = $this->update_multi_meta_value( $object_id, $name, $value );
    140154                        }
    141155
     
    320334                                'name'             => $name,
    321335                                'single'           => $args['single'],
     336                                'type'             => ! empty( $args['type'] ) ? $args['type'] : null,
    322337                                'schema'           => array(),
    323338                                'prepare_callback' => array( $this, 'prepare_value' ),
     
    325340
    326341                        $default_schema = array(
    327                                 'type'        => null,
     342                                'type'        => $default_args['type'],
    328343                                'description' => empty( $args['description'] ) ? '' : $args['description'],
    329344                                'default'     => isset( $args['default'] ) ? $args['default'] : null,
     
    333348                        $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] );
    334349
    335                         if ( empty( $rest_args['schema']['type'] ) ) {
    336                                 // Skip over meta fields that don't have a defined type.
    337                                 if ( empty( $args['type'] ) ) {
    338                                         continue;
    339                                 }
    340 
    341                                 if ( $rest_args['single'] ) {
    342                                         $rest_args['schema']['type'] = $args['type'];
    343                                 } else {
    344                                         $rest_args['schema']['type'] = 'array';
    345                                         $rest_args['schema']['items'] = array(
    346                                                 'type' => $args['type'],
    347                                         );
    348                                 }
     350                        $type = ! empty( $rest_args['type'] ) ? $rest_args['type'] : null;
     351                        $type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type;
     352
     353                        if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number' ) ) ) {
     354                                continue;
     355                        }
     356
     357                        if ( empty( $rest_args['single'] ) ) {
     358                                $rest_args['schema']['items'] = array(
     359                                        'type' => $rest_args['type'],
     360                                );
     361                                $rest_args['schema']['type'] = 'array';
    349362                        }
    350363
  • trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php

    r38975 r39222  
    2727                        'show_in_rest' => true,
    2828                        'single' => true,
     29                        'type' => 'string',
    2930                ));
    3031                register_meta( 'post', 'test_multi', array(
    3132                        'show_in_rest' => true,
    3233                        'single' => false,
     34                        'type' => 'string',
    3335                ));
    3436                register_meta( 'post', 'test_bad_auth', array(
     
    3638                        'single' => true,
    3739                        'auth_callback' => '__return_false',
     40                        'type' => 'string',
    3841                ));
    3942                register_meta( 'post', 'test_bad_auth_multi', array(
     
    4144                        'single' => false,
    4245                        'auth_callback' => '__return_false',
     46                        'type' => 'string',
    4347                ));
    4448                register_meta( 'post', 'test_no_rest', array() );
    4549                register_meta( 'post', 'test_rest_disabled', array(
    4650                        'show_in_rest' => false,
     51                        'type' => 'string',
    4752                ));
    4853                register_meta( 'post', 'test_custom_schema', array(
     
    5560                        ),
    5661                ));
     62                register_meta( 'post', 'test_custom_schema_multi', array(
     63                        'single' => false,
     64                        'type' => 'integer',
     65                        'show_in_rest' => array(
     66                                'schema' => array(
     67                                        'type' => 'number',
     68                                ),
     69                        ),
     70                ));
    5771                register_meta( 'post', 'test_invalid_type', array(
    5872                        'single' => true,
    59                         'type' => false,
     73                        'type' => 'lalala',
     74                        'show_in_rest' => true,
     75                ));
     76                register_meta( 'post', 'test_no_type', array(
     77                        'single' => true,
     78                        'type' => null,
    6079                        'show_in_rest' => true,
    6180                ));
     
    342361        }
    343362
     363        public function test_set_value_invalid_type() {
     364                $values = get_post_meta( self::$post_id, 'test_invalid_type', false );
     365                $this->assertEmpty( $values );
     366
     367                $this->grant_write_permission();
     368
     369                $data = array(
     370                        'meta' => array(
     371                                'test_invalid_type' => 'test_value',
     372                        ),
     373                );
     374                $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     375                $request->set_body_params( $data );
     376
     377                $response = $this->server->dispatch( $request );
     378                $this->assertEmpty( get_post_meta( self::$post_id, 'test_invalid_type', false ) );
     379        }
     380
    344381        public function test_set_value_multiple() {
    345382                // Ensure no data exists currently.
     
    434471                $meta = get_post_meta( self::$post_id, 'test_multi', false );
    435472                $this->assertEmpty( $meta );
     473        }
     474
     475        public function test_set_value_invalid_value() {
     476                register_meta( 'post', 'my_meta_key', array(
     477                        'show_in_rest' => true,
     478                        'single' => true,
     479                        'type' => 'string',
     480                ));
     481
     482                $this->grant_write_permission();
     483
     484                $data = array(
     485                        'meta' => array(
     486                                'my_meta_key' => array( 'c', 'n' ),
     487                        ),
     488                );
     489                $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     490                $request->set_body_params( $data );
     491
     492                $response = $this->server->dispatch( $request );
     493                $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
     494        }
     495
     496        public function test_set_value_invalid_value_multiple() {
     497                register_meta( 'post', 'my_meta_key', array(
     498                        'show_in_rest' => true,
     499                        'single' => false,
     500                        'type' => 'string',
     501                ));
     502
     503                $this->grant_write_permission();
     504
     505                $data = array(
     506                        'meta' => array(
     507                                'my_meta_key' => array( array( 'a' ) ),
     508                        ),
     509                );
     510                $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     511                $request->set_body_params( $data );
     512
     513                $response = $this->server->dispatch( $request );
     514                $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
     515        }
     516
     517        public function test_set_value_sanitized() {
     518                register_meta( 'post', 'my_meta_key', array(
     519                        'show_in_rest' => true,
     520                        'single' => true,
     521                        'type' => 'integer',
     522                ));
     523
     524                $this->grant_write_permission();
     525
     526                $data = array(
     527                        'meta' => array(
     528                                'my_meta_key' => '1', // Set to a string.
     529                        ),
     530                );
     531                $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     532                $request->set_body_params( $data );
     533
     534                $response = $this->server->dispatch( $request );
     535                $data = $response->get_data();
     536                $this->assertEquals( 1, $data['meta']['my_meta_key'] );
     537        }
     538
     539        public function test_set_value_csv() {
     540                register_meta( 'post', 'my_meta_key', array(
     541                        'show_in_rest' => true,
     542                        'single' => false,
     543                        'type' => 'integer',
     544                ));
     545
     546                $this->grant_write_permission();
     547
     548                $data = array(
     549                        'meta' => array(
     550                                'my_meta_key' => '1,2,3', // Set to a string.
     551                        ),
     552                );
     553                $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     554                $request->set_body_params( $data );
     555
     556                $response = $this->server->dispatch( $request );
     557                $data = $response->get_data();
     558                $this->assertEquals( array( 1, 2, 3 ), $data['meta']['my_meta_key'] );
    436559        }
    437560
     
    486609        }
    487610
     611        /**
     612         * @depends test_get_value
     613         */
     614        public function test_set_value_single_custom_schema() {
     615                // Ensure no data exists currently.
     616                $values = get_post_meta( self::$post_id, 'test_custom_schema', false );
     617                $this->assertEmpty( $values );
     618
     619                $this->grant_write_permission();
     620
     621                $data = array(
     622                        'meta' => array(
     623                                'test_custom_schema' => 3,
     624                        ),
     625                );
     626                $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     627                $request->set_body_params( $data );
     628
     629                $response = $this->server->dispatch( $request );
     630                $this->assertEquals( 200, $response->get_status() );
     631
     632                $meta = get_post_meta( self::$post_id, 'test_custom_schema', false );
     633                $this->assertNotEmpty( $meta );
     634                $this->assertCount( 1, $meta );
     635                $this->assertEquals( 3, $meta[0] );
     636
     637                $data = $response->get_data();
     638                $meta = (array) $data['meta'];
     639                $this->assertArrayHasKey( 'test_custom_schema', $meta );
     640                $this->assertEquals( 3, $meta['test_custom_schema'] );
     641        }
     642
     643        public function test_set_value_multiple_custom_schema() {
     644                // Ensure no data exists currently.
     645                $values = get_post_meta( self::$post_id, 'test_custom_schema_multi', false );
     646                $this->assertEmpty( $values );
     647
     648                $this->grant_write_permission();
     649
     650                $data = array(
     651                        'meta' => array(
     652                                'test_custom_schema_multi' => array( 2 ),
     653                        ),
     654                );
     655                $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     656                $request->set_body_params( $data );
     657
     658                $response = $this->server->dispatch( $request );
     659                $this->assertEquals( 200, $response->get_status() );
     660
     661                $meta = get_post_meta( self::$post_id, 'test_custom_schema_multi', false );
     662                $this->assertNotEmpty( $meta );
     663                $this->assertCount( 1, $meta );
     664                $this->assertEquals( 2, $meta[0] );
     665
     666                // Add another value.
     667                $data = array(
     668                        'meta' => array(
     669                                'test_custom_schema_multi' => array( 2, 8 ),
     670                        ),
     671                );
     672                $request->set_body_params( $data );
     673
     674                $response = $this->server->dispatch( $request );
     675                $this->assertEquals( 200, $response->get_status() );
     676
     677                $meta = get_post_meta( self::$post_id, 'test_custom_schema_multi', false );
     678                $this->assertNotEmpty( $meta );
     679                $this->assertCount( 2, $meta );
     680                $this->assertContains( 2, $meta );
     681                $this->assertContains( 8, $meta );
     682        }
     683
    488684        public function test_remove_multi_value_db_error() {
    489685                add_post_meta( self::$post_id, 'test_multi', 'val1' );
     
    516712        }
    517713
     714
    518715        public function test_delete_value() {
    519716                add_post_meta( self::$post_id, 'test_single', 'val1' );
     
    619816                $this->assertArrayNotHasKey( 'test_rest_disabled', $meta_schema );
    620817                $this->assertArrayNotHasKey( 'test_invalid_type', $meta_schema );
     818                $this->assertArrayNotHasKey( 'test_no_type', $meta_schema );
    621819        }
    622820
  • trunk/tests/phpunit/tests/rest-api/rest-schema-sanitization.php

    r39104 r39222  
    7777        }
    7878
     79        public function test_type_array_nested() {
     80                $schema = array(
     81                        'type' => 'array',
     82                        'items' => array(
     83                                'type' => 'array',
     84                                'items' => array(
     85                                        'type' => 'number',
     86                                ),
     87                        ),
     88                );
     89                $this->assertEquals( array( array( 1 ), array( 2 ) ), rest_sanitize_value_from_schema( array( array( 1 ), array( 2 ) ), $schema ) );
     90                $this->assertEquals( array( array( 1 ), array( 2 ) ), rest_sanitize_value_from_schema( array( array( '1' ), array( '2' ) ), $schema ) );
     91        }
     92
    7993        public function test_type_array_as_csv() {
    8094                $schema = array(
     
    111125                $this->assertEquals( array( 'chicken', 'coleslaw' ), rest_sanitize_value_from_schema( 'chicken,coleslaw', $schema ) );
    112126        }
     127
     128        public function test_type_array_is_associative() {
     129                $schema = array(
     130                        'type' => 'array',
     131                        'items' => array(
     132                                'type' => 'string',
     133                        ),
     134                );
     135                $this->assertEquals( array( '1', '2' ), rest_sanitize_value_from_schema( array( 'first' => '1', 'second' => '2' ), $schema ) );
     136        }
     137
     138        public function test_type_unknown() {
     139                $schema = array(
     140                        'type' => 'lalala',
     141                );
     142                $this->assertEquals( 'Best lyrics', rest_sanitize_value_from_schema( 'Best lyrics', $schema ) );
     143                $this->assertEquals( 1.10, rest_sanitize_value_from_schema( 1.10, $schema ) );
     144                $this->assertEquals( 1, rest_sanitize_value_from_schema( 1, $schema ) );
     145        }
     146
     147        public function test_no_type() {
     148                $schema = array(
     149                        'type' => null,
     150                );
     151                $this->assertEquals( 'Nothing', rest_sanitize_value_from_schema( 'Nothing', $schema ) );
     152                $this->assertEquals( 1.10, rest_sanitize_value_from_schema( 1.10, $schema ) );
     153                $this->assertEquals( 1, rest_sanitize_value_from_schema( 1, $schema ) );
     154        }
    113155}
  • trunk/tests/phpunit/tests/rest-api/rest-schema-validation.php

    r39159 r39222  
    105105        }
    106106
     107        public function test_type_array_nested() {
     108                $schema = array(
     109                        'type' => 'array',
     110                        'items' => array(
     111                                'type' => 'array',
     112                                'items' => array(
     113                                        'type' => 'number',
     114                                ),
     115                        ),
     116                );
     117                $this->assertTrue( rest_validate_value_from_schema( array( array( 1 ), array( 2 ) ), $schema ) );
     118        }
     119
    107120        public function test_type_array_as_csv() {
    108121                $schema = array(
     
    140153                $this->assertWPError( rest_validate_value_from_schema( 'chicken,coleslaw', $schema ) );
    141154        }
     155
     156        public function test_type_array_is_associative() {
     157                $schema = array(
     158                        'type'  => 'array',
     159                        'items' => array(
     160                                'type' => 'string',
     161                        ),
     162                );
     163                $this->assertWPError( rest_validate_value_from_schema( array( 'first' => '1', 'second' => '2' ), $schema ) );
     164        }
     165
     166        public function test_type_unknown() {
     167                $schema = array(
     168                        'type'  => 'lalala',
     169                );
     170                $this->assertTrue( rest_validate_value_from_schema( 'Best lyrics', $schema ) );
     171                $this->assertTrue( rest_validate_value_from_schema( 1, $schema ) );
     172                $this->assertTrue( rest_validate_value_from_schema( array(), $schema ) );
     173        }
    142174}
Note: See TracChangeset for help on using the changeset viewer.