Make WordPress Core

Changeset 50010


Ignore:
Timestamp:
01/24/2021 04:50:39 PM (3 years ago)
Author:
TimothyBlynJacobs
Message:

REST API: Support type coercion when validating the enum JSON Schema keyword.

Previously, the enum keyword was validated by perform a strict equality check. For string types this is generally ok, but it prevented using alternative types like number when rich type support isn't available.

Now the same level of type coercion/sanitization is applied when validating enum as all other validation checks. This means that a value of "1" will be accepted for an enum of [ 0, 1 ]. Additionally, object types now properly ignore key order when checking for equality.

Props yakimun.
Fixes #51911.

Location:
trunk
Files:
2 edited

Legend:

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

    r50007 r50010  
    18731873
    18741874    return $matching_schemas[0]['schema_object'];
     1875}
     1876
     1877/**
     1878 * Checks the equality of two values, following JSON Schema semantics.
     1879 *
     1880 * Property order is ignored for objects.
     1881 *
     1882 * Values must have been previously sanitized/coerced to their native types.
     1883 *
     1884 * @since 5.7.0
     1885 *
     1886 * @param mixed $value1 The first value to check.
     1887 * @param mixed $value2 The second value to check.
     1888 * @return bool True if the values are equal or false otherwise.
     1889 */
     1890function rest_are_values_equal( $value1, $value2 ) {
     1891    if ( is_array( $value1 ) && is_array( $value2 ) ) {
     1892        if ( count( $value1 ) !== count( $value2 ) ) {
     1893            return false;
     1894        }
     1895
     1896        foreach ( $value1 as $index => $value ) {
     1897            if ( ! array_key_exists( $index, $value2 ) || ! rest_are_values_equal( $value, $value2[ $index ] ) ) {
     1898                return false;
     1899            }
     1900        }
     1901
     1902        return true;
     1903    }
     1904
     1905    return $value1 === $value2;
     1906}
     1907
     1908/**
     1909 * Validates that the given value is a member of the JSON Schema "enum".
     1910 *
     1911 * @since 5.7.0
     1912 *
     1913 * @param mixed  $value  The value to validate.
     1914 * @param array  $args   The schema array to use.
     1915 * @param string $param  The parameter name, used in error messages.
     1916 * @return true|WP_Error True if the "enum" contains the value or a WP_Error instance otherwise.
     1917 */
     1918function rest_validate_enum( $value, $args, $param ) {
     1919    $sanitized_value = rest_sanitize_value_from_schema( $value, $args, $param );
     1920    if ( is_wp_error( $sanitized_value ) ) {
     1921        return $sanitized_value;
     1922    }
     1923
     1924    foreach ( $args['enum'] as $enum_value ) {
     1925        if ( rest_are_values_equal( $sanitized_value, $enum_value ) ) {
     1926            return true;
     1927        }
     1928    }
     1929
     1930    $encoded_enum_values = array();
     1931    foreach ( $args['enum'] as $enum_value ) {
     1932        $encoded_enum_values[] = is_scalar( $enum_value ) ? $enum_value : wp_json_encode( $enum_value );
     1933    }
     1934
     1935    if ( count( $encoded_enum_values ) === 1 ) {
     1936        /* translators: 1: Parameter, 2: Valid values. */
     1937        return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not %2$s.' ), $param, $encoded_enum_values[0] ) );
     1938    }
     1939
     1940    /* translators: 1: Parameter, 2: List of valid values. */
     1941    return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not one of %2$l.' ), $param, $encoded_enum_values ) );
    18751942}
    18761943
     
    21542221    }
    21552222
    2156     if ( ! empty( $args['enum'] ) ) {
    2157         if ( ! in_array( $value, $args['enum'], true ) ) {
    2158             /* translators: 1: Parameter, 2: List of valid values. */
    2159             return new WP_Error( 'rest_not_in_enum', sprintf( __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) );
    2160         }
    2161     }
    2162 
    21632223    if ( in_array( $args['type'], array( 'integer', 'number' ), true ) ) {
    21642224        if ( ! is_numeric( $value ) ) {
     
    22322292            /* translators: 1: Parameter, 2: Pattern. */
    22332293            return new WP_Error( 'rest_invalid_pattern', sprintf( __( '%1$s does not match pattern %2$s.' ), $param, $args['pattern'] ) );
     2294        }
     2295    }
     2296
     2297    if ( ! empty( $args['enum'] ) ) {
     2298        $enum_contains_value = rest_validate_enum( $value, $args, $param );
     2299        if ( is_wp_error( $enum_contains_value ) ) {
     2300            return $enum_contains_value;
    22342301        }
    22352302    }
  • trunk/tests/phpunit/tests/rest-api/rest-schema-validation.php

    r49246 r50010  
    247247        $this->assertTrue( rest_validate_value_from_schema( 'ribs,chicken,', $schema ) );
    248248        $this->assertTrue( rest_validate_value_from_schema( '', $schema ) );
     249    }
     250
     251    /**
     252     * @ticket 51911
     253     *
     254     * @dataProvider data_different_types_of_value_and_enum_elements
     255     *
     256     * @param mixed $value
     257     * @param array $args
     258     * @param bool  $expected
     259     */
     260    public function test_different_types_of_value_and_enum_elements( $value, $args, $expected ) {
     261        $result = rest_validate_value_from_schema( $value, $args );
     262        if ( $expected ) {
     263            $this->assertTrue( $result );
     264        } else {
     265            $this->assertWPError( $result );
     266        }
     267    }
     268
     269    /**
     270     * @return array
     271     */
     272    public function data_different_types_of_value_and_enum_elements() {
     273        return array(
     274            // enum with integers
     275            array(
     276                0,
     277                array(
     278                    'type' => 'integer',
     279                    'enum' => array( 0, 1 ),
     280                ),
     281                true,
     282            ),
     283            array(
     284                0.0,
     285                array(
     286                    'type' => 'integer',
     287                    'enum' => array( 0, 1 ),
     288                ),
     289                true,
     290            ),
     291            array(
     292                '0',
     293                array(
     294                    'type' => 'integer',
     295                    'enum' => array( 0, 1 ),
     296                ),
     297                true,
     298            ),
     299            array(
     300                1,
     301                array(
     302                    'type' => 'integer',
     303                    'enum' => array( 0, 1 ),
     304                ),
     305                true,
     306            ),
     307            array(
     308                1.0,
     309                array(
     310                    'type' => 'integer',
     311                    'enum' => array( 0, 1 ),
     312                ),
     313                true,
     314            ),
     315            array(
     316                '1',
     317                array(
     318                    'type' => 'integer',
     319                    'enum' => array( 0, 1 ),
     320                ),
     321                true,
     322            ),
     323            array(
     324                2,
     325                array(
     326                    'type' => 'integer',
     327                    'enum' => array( 0, 1 ),
     328                ),
     329                false,
     330            ),
     331            array(
     332                2.0,
     333                array(
     334                    'type' => 'integer',
     335                    'enum' => array( 0, 1 ),
     336                ),
     337                false,
     338            ),
     339            array(
     340                '2',
     341                array(
     342                    'type' => 'integer',
     343                    'enum' => array( 0, 1 ),
     344                ),
     345                false,
     346            ),
     347
     348            // enum with floats
     349            array(
     350                0,
     351                array(
     352                    'type' => 'number',
     353                    'enum' => array( 0.0, 1.0 ),
     354                ),
     355                true,
     356            ),
     357            array(
     358                0.0,
     359                array(
     360                    'type' => 'number',
     361                    'enum' => array( 0.0, 1.0 ),
     362                ),
     363                true,
     364            ),
     365            array(
     366                '0',
     367                array(
     368                    'type' => 'number',
     369                    'enum' => array( 0.0, 1.0 ),
     370                ),
     371                true,
     372            ),
     373            array(
     374                1,
     375                array(
     376                    'type' => 'number',
     377                    'enum' => array( 0.0, 1.0 ),
     378                ),
     379                true,
     380            ),
     381            array(
     382                1.0,
     383                array(
     384                    'type' => 'number',
     385                    'enum' => array( 0.0, 1.0 ),
     386                ),
     387                true,
     388            ),
     389            array(
     390                '1',
     391                array(
     392                    'type' => 'number',
     393                    'enum' => array( 0.0, 1.0 ),
     394                ),
     395                true,
     396            ),
     397            array(
     398                2,
     399                array(
     400                    'type' => 'number',
     401                    'enum' => array( 0.0, 1.0 ),
     402                ),
     403                false,
     404            ),
     405            array(
     406                2.0,
     407                array(
     408                    'type' => 'number',
     409                    'enum' => array( 0.0, 1.0 ),
     410                ),
     411                false,
     412            ),
     413            array(
     414                '2',
     415                array(
     416                    'type' => 'number',
     417                    'enum' => array( 0.0, 1.0 ),
     418                ),
     419                false,
     420            ),
     421
     422            // enum with booleans
     423            array(
     424                true,
     425                array(
     426                    'type' => 'boolean',
     427                    'enum' => array( true ),
     428                ),
     429                true,
     430            ),
     431            array(
     432                1,
     433                array(
     434                    'type' => 'boolean',
     435                    'enum' => array( true ),
     436                ),
     437                true,
     438            ),
     439            array(
     440                'true',
     441                array(
     442                    'type' => 'boolean',
     443                    'enum' => array( true ),
     444                ),
     445                true,
     446            ),
     447            array(
     448                false,
     449                array(
     450                    'type' => 'boolean',
     451                    'enum' => array( true ),
     452                ),
     453                false,
     454            ),
     455            array(
     456                0,
     457                array(
     458                    'type' => 'boolean',
     459                    'enum' => array( true ),
     460                ),
     461                false,
     462            ),
     463            array(
     464                'false',
     465                array(
     466                    'type' => 'boolean',
     467                    'enum' => array( true ),
     468                ),
     469                false,
     470            ),
     471            array(
     472                false,
     473                array(
     474                    'type' => 'boolean',
     475                    'enum' => array( false ),
     476                ),
     477                true,
     478            ),
     479            array(
     480                0,
     481                array(
     482                    'type' => 'boolean',
     483                    'enum' => array( false ),
     484                ),
     485                true,
     486            ),
     487            array(
     488                'false',
     489                array(
     490                    'type' => 'boolean',
     491                    'enum' => array( false ),
     492                ),
     493                true,
     494            ),
     495            array(
     496                true,
     497                array(
     498                    'type' => 'boolean',
     499                    'enum' => array( false ),
     500                ),
     501                false,
     502            ),
     503            array(
     504                1,
     505                array(
     506                    'type' => 'boolean',
     507                    'enum' => array( false ),
     508                ),
     509                false,
     510            ),
     511            array(
     512                'true',
     513                array(
     514                    'type' => 'boolean',
     515                    'enum' => array( false ),
     516                ),
     517                false,
     518            ),
     519
     520            // enum with arrays
     521            array(
     522                array( 0, 1 ),
     523                array(
     524                    'type'  => 'array',
     525                    'items' => array( 'type' => 'integer' ),
     526                    'enum'  => array( array( 0, 1 ), array( 1, 2 ) ),
     527                ),
     528                true,
     529            ),
     530            array(
     531                array( '0', 1 ),
     532                array(
     533                    'type'  => 'array',
     534                    'items' => array( 'type' => 'integer' ),
     535                    'enum'  => array( array( 0, 1 ), array( 1, 2 ) ),
     536                ),
     537                true,
     538            ),
     539            array(
     540                array( 0, '1' ),
     541                array(
     542                    'type'  => 'array',
     543                    'items' => array( 'type' => 'integer' ),
     544                    'enum'  => array( array( 0, 1 ), array( 1, 2 ) ),
     545                ),
     546                true,
     547            ),
     548            array(
     549                array( '0', '1' ),
     550                array(
     551                    'type'  => 'array',
     552                    'items' => array( 'type' => 'integer' ),
     553                    'enum'  => array( array( 0, 1 ), array( 1, 2 ) ),
     554                ),
     555                true,
     556            ),
     557            array(
     558                array( 1, 2 ),
     559                array(
     560                    'type'  => 'array',
     561                    'items' => array( 'type' => 'integer' ),
     562                    'enum'  => array( array( 0, 1 ), array( 1, 2 ) ),
     563                ),
     564                true,
     565            ),
     566            array(
     567                array( 2, 3 ),
     568                array(
     569                    'type'  => 'array',
     570                    'items' => array( 'type' => 'integer' ),
     571                    'enum'  => array( array( 0, 1 ), array( 1, 2 ) ),
     572                ),
     573                false,
     574            ),
     575            array(
     576                array( 1, 0 ),
     577                array(
     578                    'type'  => 'array',
     579                    'items' => array( 'type' => 'integer' ),
     580                    'enum'  => array( array( 0, 1 ), array( 1, 2 ) ),
     581                ),
     582                false,
     583            ),
     584
     585            // enum with objects
     586            array(
     587                array(
     588                    'a' => 1,
     589                    'b' => 2,
     590                ),
     591                array(
     592                    'type'                 => 'object',
     593                    'additionalProperties' => array( 'type' => 'integer' ),
     594                    'enum'                 => array(
     595                        array(
     596                            'a' => 1,
     597                            'b' => 2,
     598                        ),
     599                        array(
     600                            'b' => 2,
     601                            'c' => 3,
     602                        ),
     603                    ),
     604                ),
     605                true,
     606            ),
     607            array(
     608                array(
     609                    'a' => '1',
     610                    'b' => 2,
     611                ),
     612                array(
     613                    'type'                 => 'object',
     614                    'additionalProperties' => array( 'type' => 'integer' ),
     615                    'enum'                 => array(
     616                        array(
     617                            'a' => 1,
     618                            'b' => 2,
     619                        ),
     620                        array(
     621                            'b' => 2,
     622                            'c' => 3,
     623                        ),
     624                    ),
     625                ),
     626                true,
     627            ),
     628            array(
     629                array(
     630                    'a' => 1,
     631                    'b' => '2',
     632                ),
     633                array(
     634                    'type'                 => 'object',
     635                    'additionalProperties' => array( 'type' => 'integer' ),
     636                    'enum'                 => array(
     637                        array(
     638                            'a' => 1,
     639                            'b' => 2,
     640                        ),
     641                        array(
     642                            'b' => 2,
     643                            'c' => 3,
     644                        ),
     645                    ),
     646                ),
     647                true,
     648            ),
     649            array(
     650                array(
     651                    'a' => '1',
     652                    'b' => '2',
     653                ),
     654                array(
     655                    'type'                 => 'object',
     656                    'additionalProperties' => array( 'type' => 'integer' ),
     657                    'enum'                 => array(
     658                        array(
     659                            'a' => 1,
     660                            'b' => 2,
     661                        ),
     662                        array(
     663                            'b' => 2,
     664                            'c' => 3,
     665                        ),
     666                    ),
     667                ),
     668                true,
     669            ),
     670            array(
     671                array(
     672                    'b' => 2,
     673                    'a' => 1,
     674                ),
     675                array(
     676                    'type'                 => 'object',
     677                    'additionalProperties' => array( 'type' => 'integer' ),
     678                    'enum'                 => array(
     679                        array(
     680                            'a' => 1,
     681                            'b' => 2,
     682                        ),
     683                        array(
     684                            'b' => 2,
     685                            'c' => 3,
     686                        ),
     687                    ),
     688                ),
     689                true,
     690            ),
     691            array(
     692                array(
     693                    'b' => 2,
     694                    'c' => 3,
     695                ),
     696                array(
     697                    'type'                 => 'object',
     698                    'additionalProperties' => array( 'type' => 'integer' ),
     699                    'enum'                 => array(
     700                        array(
     701                            'a' => 1,
     702                            'b' => 2,
     703                        ),
     704                        array(
     705                            'b' => 2,
     706                            'c' => 3,
     707                        ),
     708                    ),
     709                ),
     710                true,
     711            ),
     712            array(
     713                array(
     714                    'a' => 1,
     715                    'b' => 3,
     716                ),
     717                array(
     718                    'type'                 => 'object',
     719                    'additionalProperties' => array( 'type' => 'integer' ),
     720                    'enum'                 => array(
     721                        array(
     722                            'a' => 1,
     723                            'b' => 2,
     724                        ),
     725                        array(
     726                            'b' => 2,
     727                            'c' => 3,
     728                        ),
     729                    ),
     730                ),
     731                false,
     732            ),
     733            array(
     734                array(
     735                    'c' => 3,
     736                    'd' => 4,
     737                ),
     738                array(
     739                    'type'                 => 'object',
     740                    'additionalProperties' => array( 'type' => 'integer' ),
     741                    'enum'                 => array(
     742                        array(
     743                            'a' => 1,
     744                            'b' => 2,
     745                        ),
     746                        array(
     747                            'b' => 2,
     748                            'c' => 3,
     749                        ),
     750                    ),
     751                ),
     752                false,
     753            ),
     754        );
    249755    }
    250756
Note: See TracChangeset for help on using the changeset viewer.