Make WordPress Core

Changeset 50010


Ignore:
Timestamp:
01/24/2021 04:50:39 PM (6 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.