WordPress.org

Make WordPress Core

Changeset 39222


Ignore:
Timestamp:
11/14/2016 04:35:35 PM (5 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.