Make WordPress Core

Changeset 48526


Ignore:
Timestamp:
07/21/2020 12:01:10 PM (4 years ago)
Author:
TimothyBlynJacobs
Message:

REST API: Issue a _doing_it_wrong when registering a route without a permission callback.

The REST API treats routes without a permission_callback as public. Because this happens without any warning to the user, if the permission callback is unintentionally omitted or misspelled, the endpoint can end up being available to the public. Such a scenario has happened multiple times in the wild, and the results can be catostrophic when it occurs.

For REST API routes that are intended to be public, it is recommended to set the permission callback to the __return_true built in function.

Fixes #50075.
Props rmccue, sorenbronsted, whyisjake, SergeyBiryukov, TimothyBlynJacobs.

Location:
trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-oembed-controller.php

    r48135 r48526  
    3737            array(
    3838                array(
    39                     'methods'  => WP_REST_Server::READABLE,
    40                     'callback' => array( $this, 'get_item' ),
    41                     'args'     => array(
     39                    'methods'             => WP_REST_Server::READABLE,
     40                    'callback'            => array( $this, 'get_item' ),
     41                    'permission_callback' => '__return_true',
     42                    'args'                => array(
    4243                        'url'      => array(
    4344                            'description' => __( 'The URL of the resource for which to fetch oEmbed data.' ),
  • trunk/src/wp-includes/rest-api.php

    r48367 r48526  
    8989        $arg_group         = array_merge( $defaults, $arg_group );
    9090        $arg_group['args'] = array_merge( $common_args, $arg_group['args'] );
     91
     92        if ( ! isset( $arg_group['permission_callback'] ) ) {
     93            _doing_it_wrong(
     94                __FUNCTION__,
     95                sprintf(
     96                    /* translators: 1. The REST API route being registered. 2. The argument name. 3. The suggested function name. */
     97                    __( 'The REST API route definition for %1$s is missing the required %2$s argument. For REST API routes that are intended to be public, use %3$s as the permission callback.', 'LION' ),
     98                    '<code>' . $clean_namespace . '/' . trim( $route, '/' ) . '</code>',
     99                    '<code>permission_callback</code>',
     100                    '<code>__return_true</code>'
     101                ),
     102                '5.5.0'
     103            );
     104        }
    91105    }
    92106
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php

    r47122 r48526  
    6161                ),
    6262                array(
    63                     'methods'  => WP_REST_Server::READABLE,
    64                     'callback' => array( $this, 'get_item' ),
    65                     'args'     => array(
     63                    'methods'             => WP_REST_Server::READABLE,
     64                    'callback'            => array( $this, 'get_item' ),
     65                    'permission_callback' => '__return_true',
     66                    'args'                => array(
    6667                        'context' => $this->get_context_param( array( 'default' => 'view' ) ),
    6768                    ),
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php

    r48195 r48526  
    117117            array(
    118118                array(
    119                     'methods'  => WP_REST_Server::READABLE,
    120                     'callback' => array( $this, 'get_current_item' ),
    121                     'args'     => array(
     119                    'methods'             => WP_REST_Server::READABLE,
     120                    'permission_callback' => '__return_true',
     121                    'callback'            => array( $this, 'get_current_item' ),
     122                    'args'                => array(
    122123                        'context' => $this->get_context_param( array( 'default' => 'view' ) ),
    123124                    ),
  • trunk/tests/phpunit/tests/rest-api.php

    r48365 r48526  
    8585            '/test',
    8686            array(
    87                 'methods'  => array( 'GET' ),
    88                 'callback' => '__return_null',
     87                'methods'             => array( 'GET' ),
     88                'callback'            => '__return_null',
     89                'permission_callback' => '__return_true',
    8990            )
    9091        );
     
    121122            array(
    122123                array(
    123                     'methods'  => array( 'GET' ),
    124                     'callback' => '__return_null',
    125                 ),
    126                 array(
    127                     'methods'  => array( 'POST' ),
    128                     'callback' => '__return_null',
     124                    'methods'             => array( 'GET' ),
     125                    'callback'            => '__return_null',
     126                    'permission_callback' => '__return_true',
     127                ),
     128                array(
     129                    'methods'             => array( 'POST' ),
     130                    'callback'            => '__return_null',
     131                    'permission_callback' => '__return_true',
    129132                ),
    130133            )
     
    161164            '/test',
    162165            array(
    163                 'methods'  => array( 'GET' ),
    164                 'callback' => '__return_null',
     166                'methods'             => array( 'GET' ),
     167                'callback'            => '__return_null',
     168                'permission_callback' => '__return_true',
    165169            )
    166170        );
     
    169173            '/test',
    170174            array(
    171                 'methods'  => array( 'POST' ),
    172                 'callback' => '__return_null',
     175                'methods'             => array( 'POST' ),
     176                'callback'            => '__return_null',
     177                'permission_callback' => '__return_true',
    173178            )
    174179        );
     
    188193            '/test',
    189194            array(
    190                 'methods'      => array( 'GET' ),
    191                 'callback'     => '__return_null',
    192                 'should_exist' => false,
     195                'methods'             => array( 'GET' ),
     196                'callback'            => '__return_null',
     197                'permission_callback' => '__return_true',
     198                'should_exist'        => false,
    193199            )
    194200        );
     
    197203            '/test',
    198204            array(
    199                 'methods'      => array( 'POST' ),
    200                 'callback'     => '__return_null',
    201                 'should_exist' => true,
     205                'methods'             => array( 'POST' ),
     206                'callback'            => '__return_null',
     207                'permission_callback' => '__return_true',
     208                'should_exist'        => true,
    202209            ),
    203210            true
     
    224231            '/test-empty-namespace',
    225232            array(
    226                 'methods'  => array( 'POST' ),
    227                 'callback' => '__return_null',
     233                'methods'             => array( 'POST' ),
     234                'callback'            => '__return_null',
     235                'permission_callback' => '__return_true',
    228236            ),
    229237            true
     
    243251            '',
    244252            array(
    245                 'methods'  => array( 'POST' ),
    246                 'callback' => '__return_null',
     253                'methods'             => array( 'POST' ),
     254                'callback'            => '__return_null',
     255                'permission_callback' => '__return_true',
    247256            ),
    248257            true
     
    265274            '/test',
    266275            array(
    267                 'methods'  => array( 'GET' ),
    268                 'callback' => '__return_null',
     276                'methods'             => array( 'GET' ),
     277                'callback'            => '__return_null',
     278                'permission_callback' => '__return_true',
    269279            )
    270280        );
     
    283293            '/test',
    284294            array(
    285                 'methods'  => 'GET',
    286                 'callback' => '__return_null',
     295                'methods'             => 'GET',
     296                'callback'            => '__return_null',
     297                'permission_callback' => '__return_true',
    287298            )
    288299        );
     
    301312            '/test',
    302313            array(
    303                 'methods'  => array( 'GET', 'POST' ),
    304                 'callback' => '__return_null',
     314                'methods'             => array( 'GET', 'POST' ),
     315                'callback'            => '__return_null',
     316                'permission_callback' => '__return_true',
    305317            )
    306318        );
     
    325337            '/test',
    326338            array(
    327                 'methods'  => 'GET,POST',
    328                 'callback' => '__return_null',
     339                'methods'             => 'GET,POST',
     340                'callback'            => '__return_null',
     341                'permission_callback' => '__return_true',
    329342            )
    330343        );
     
    346359            '/test',
    347360            array(
    348                 'methods'  => 'GET,POST',
    349                 'callback' => '__return_null',
     361                'methods'             => 'GET,POST',
     362                'callback'            => '__return_null',
     363                'permission_callback' => '__return_true',
    350364            )
    351365        );
     
    368382            '/test',
    369383            array(
    370                 'methods'  => 'GET,POST',
    371                 'callback' => '__return_true',
     384                'methods'             => 'GET,POST',
     385                'callback'            => '__return_true',
     386                'permission_callback' => '__return_true',
    372387            )
    373388        );
     
    911926            '/test',
    912927            array(
    913                 'methods'  => array( 'GET' ),
    914                 'callback' => '__return_null',
     928                'methods'             => array( 'GET' ),
     929                'callback'            => '__return_null',
     930                'permission_callback' => '__return_true',
    915931            )
    916932        );
     
    9971013            '/my-route',
    9981014            array(
    999                 'callback' => '__return_true',
     1015                'callback'            => '__return_true',
     1016                'permission_callback' => '__return_true',
    10001017            )
    10011018        );
     
    10051022
    10061023        $this->assertTrue( rest_do_request( '/my-namespace/v1/my-route' )->get_data() );
     1024    }
     1025
     1026    /**
     1027     * @ticket 50075
     1028     */
     1029    public function test_register_route_with_missing_permission_callback_top_level_route() {
     1030        $this->setExpectedIncorrectUsage( 'register_rest_route' );
     1031
     1032        $registered = register_rest_route(
     1033            'my-ns/v1',
     1034            '/my-route',
     1035            array(
     1036                'callback' => '__return_true',
     1037            )
     1038        );
     1039
     1040        $this->assertTrue( $registered );
     1041    }
     1042
     1043    /**
     1044     * @ticket 50075
     1045     */
     1046    public function test_register_route_with_missing_permission_callback_single_wrapped_route() {
     1047        $this->setExpectedIncorrectUsage( 'register_rest_route' );
     1048
     1049        $registered = register_rest_route(
     1050            'my-ns/v1',
     1051            '/my-route',
     1052            array(
     1053                array(
     1054                    'callback' => '__return_true',
     1055                ),
     1056            )
     1057        );
     1058
     1059        $this->assertTrue( $registered );
     1060    }
     1061
     1062
     1063    /**
     1064     * @ticket 50075
     1065     */
     1066    public function test_register_route_with_missing_permission_callback_multiple_wrapped_route() {
     1067        $this->setExpectedIncorrectUsage( 'register_rest_route' );
     1068
     1069        $registered = register_rest_route(
     1070            'my-ns/v1',
     1071            '/my-route',
     1072            array(
     1073                array(
     1074                    'callback' => '__return_true',
     1075                ),
     1076                array(
     1077                    'callback'            => '__return_true',
     1078                    'permission_callback' => '__return_true',
     1079                ),
     1080            )
     1081        );
     1082
     1083        $this->assertTrue( $registered );
    10071084    }
    10081085
  • trunk/tests/phpunit/tests/rest-api/rest-server.php

    r47351 r48526  
    6767            '/test',
    6868            array(
    69                 'methods'  => array( 'GET' ),
    70                 'callback' => '__return_null',
    71                 'args'     => array(
     69                'methods'             => array( 'GET' ),
     70                'callback'            => '__return_null',
     71                'permission_callback' => '__return_true',
     72                'args'                => array(
    7273                    'foo' => array(
    7374                        'default' => 'bar',
     
    8990            '/test',
    9091            array(
    91                 'methods'  => array( 'GET' ),
    92                 'callback' => '__return_null',
    93                 'args'     => array(
     92                'methods'             => array( 'GET' ),
     93                'callback'            => '__return_null',
     94                'permission_callback' => '__return_true',
     95                'args'                => array(
    9496                    'foo' => array(
    9597                        'default' => 'bar',
     
    111113            '/test',
    112114            array(
    113                 'methods'  => array( 'GET' ),
    114                 'callback' => '__return_null',
    115                 'args'     => array(
     115                'methods'             => array( 'GET' ),
     116                'callback'            => '__return_null',
     117                'permission_callback' => '__return_true',
     118                'args'                => array(
    116119                    'foo' => array(),
    117120                ),
     
    132135            '/test',
    133136            array(
    134                 'methods'  => array( 'GET' ),
    135                 'callback' => '__return_null',
    136                 'args'     => array(
     137                'methods'             => array( 'GET' ),
     138                'callback'            => '__return_null',
     139                'permission_callback' => '__return_true',
     140                'args'                => array(
    137141                    'foo' => array(
    138142                        'default' => 'bar',
     
    151155            '/test',
    152156            array(
    153                 'methods'  => array( 'GET' ),
    154                 'callback' => '__return_true',
     157                'methods'             => array( 'GET' ),
     158                'callback'            => '__return_true',
     159                'permission_callback' => '__return_true',
    155160            )
    156161        );
     
    172177            array(
    173178                array(
    174                     'methods'  => array( 'HEAD' ),
    175                     'callback' => '__return_true',
     179                    'methods'             => array( 'HEAD' ),
     180                    'callback'            => '__return_true',
     181                    'permission_callback' => '__return_true',
    176182                ),
    177183                array(
     
    194200            array(
    195201                array(
    196                     'methods'  => WP_REST_Server::READABLE,
    197                     'callback' => '__return_false',
    198                     'args'     => array(
     202                    'methods'             => WP_REST_Server::READABLE,
     203                    'callback'            => '__return_false',
     204                    'permission_callback' => '__return_true',
     205                    'args'                => array(
    199206                        'data' => array(),
    200207                    ),
     
    267274            '/test',
    268275            array(
    269                 'methods'      => 'GET',
    270                 'callback'     => '__return_null',
    271                 'should_exist' => false,
     276                'methods'             => 'GET',
     277                'callback'            => '__return_null',
     278                'permission_callback' => '__return_true',
     279                'should_exist'        => false,
    272280            )
    273281        );
     
    294302            '/test',
    295303            array(
    296                 'methods'      => 'GET',
    297                 'callback'     => '__return_null',
    298                 'should_exist' => false,
     304                'methods'             => 'GET',
     305                'callback'            => '__return_null',
     306                'permission_callback' => '__return_true',
     307                'should_exist'        => false,
    299308            )
    300309        );
     
    304313            '/test',
    305314            array(
    306                 'methods'      => 'POST',
    307                 'callback'     => '__return_null',
    308                 'should_exist' => false,
     315                'methods'             => 'POST',
     316                'callback'            => '__return_null',
     317                'permission_callback' => '__return_true',
     318                'should_exist'        => false,
    309319            )
    310320        );
     
    343353            '/test',
    344354            array(
    345                 'methods'      => 'POST',
    346                 'callback'     => '__return_null',
    347                 'should_exist' => false,
     355                'methods'             => 'POST',
     356                'callback'            => '__return_null',
     357                'permission_callback' => '__return_true',
     358                'should_exist'        => false,
    348359            )
    349360        );
     
    366377            array(
    367378                array(
    368                     'methods'  => array( 'GET' ),
    369                     'callback' => '__return_null',
     379                    'methods'             => array( 'GET' ),
     380                    'callback'            => '__return_null',
     381                    'permission_callback' => '__return_true',
    370382                ),
    371383                array(
     
    13281340            '/test',
    13291341            array(
    1330                 'methods'  => array( 'GET' ),
    1331                 'callback' => '__return_null',
    1332                 'args'     => array(
     1342                'methods'             => array( 'GET' ),
     1343                'callback'            => '__return_null',
     1344                'permission_callback' => '__return_true',
     1345                'args'                => array(
    13331346                    'someinteger' => array(
    13341347                        'validate_callback' => array( $this, '_validate_as_integer_123' ),
     
    13631376            '/test',
    13641377            array(
    1365                 'methods'  => array( 'GET' ),
    1366                 'callback' => function () {
     1378                'methods'             => array( 'GET' ),
     1379                'callback'            => function () {
    13671380                    return new WP_REST_Response();
    13681381                },
     1382                'permission_callback' => '__return_true',
    13691383            )
    13701384        );
     
    13841398            '/test',
    13851399            array(
    1386                 'methods'  => array( 'GET' ),
    1387                 'callback' => function () {
     1400                'methods'             => array( 'GET' ),
     1401                'callback'            => function () {
    13881402                    return new WP_REST_Response( 'data', 204 );
    13891403                },
     1404                'permission_callback' => '__return_true',
    13901405            )
    13911406        );
     
    14741489            '/test',
    14751490            array(
    1476                 'methods'  => array( 'GET' ),
    1477                 'callback' => function() {
     1491                'methods'             => array( 'GET' ),
     1492                'callback'            => function() {
    14781493                    return new WP_REST_Response( 'data', 204 );
    14791494                },
     1495                'permission_callback' => '__return_true',
    14801496            )
    14811497        );
     
    14841500            '/test',
    14851501            array(
    1486                 'methods'  => array( 'GET' ),
    1487                 'callback' => function() {
     1502                'methods'             => array( 'GET' ),
     1503                'callback'            => function() {
    14881504                    return new WP_REST_Response( 'data', 204 );
    14891505                },
     1506                'permission_callback' => '__return_true',
    14901507            )
    14911508        );
Note: See TracChangeset for help on using the changeset viewer.