Make WordPress Core


Ignore:
Timestamp:
09/17/2024 09:50:38 PM (8 months ago)
Author:
TimothyBlynJacobs
Message:

REST API: Automatically populate targetHints for the Allow header.

The REST API uses the "Allow" header to communicate what methods a user is authorized to perform on a resource. This works great when operating on a single item route, but can break down when needing to determine authorization over a collection of items.

This commit uses the "targetHints" property of JSON Hyper Schema to provide access to the "allow" header for "self" links. This alleviates needing to make a separate network request for each item in a collection.

Props mamaduka, noisysocks, peterwilsoncc, spacedmonkey, swissspidy, timothyblynjacobs, tyxla, youknowriad.
Fixes #61739.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/rest-api/rest-server.php

    r57987 r59032  
    1010class Tests_REST_Server extends WP_Test_REST_TestCase {
    1111    protected static $icon_id;
     12    protected static $admin_id;
     13    protected static $post_id;
    1214
    1315    /**
     
    2224
    2325    public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
    24         $filename      = DIR_TESTDATA . '/images/test-image-large.jpg';
    25         self::$icon_id = $factory->attachment->create_upload_object( $filename );
     26        $filename       = DIR_TESTDATA . '/images/test-image-large.jpg';
     27        self::$icon_id  = $factory->attachment->create_upload_object( $filename );
     28        self::$admin_id = $factory->user->create(
     29            array(
     30                'role' => 'administrator',
     31            )
     32        );
     33        self::$post_id  = $factory->post->create();
    2634    }
    2735
    2836    public static function tear_down_after_class() {
    2937        wp_delete_attachment( self::$icon_id, true );
     38        self::delete_user( self::$admin_id );
     39        wp_delete_post( self::$post_id );
    3040
    3141        parent::tear_down_after_class();
     
    24322442    }
    24332443
     2444    /**
     2445     * @ticket 61739
     2446     */
     2447    public function test_validates_request_when_building_target_hints() {
     2448        register_rest_route(
     2449            'test-ns/v1',
     2450            '/test/(?P<id>\d+)',
     2451            array(
     2452                array(
     2453                    'methods'             => \WP_REST_Server::READABLE,
     2454                    'callback'            => static function () {
     2455                        return new \WP_REST_Response();
     2456                    },
     2457                    'permission_callback' => '__return_true',
     2458                    'args'                => array(
     2459                        'id' => array(
     2460                            'type' => 'integer',
     2461                        ),
     2462                    ),
     2463                ),
     2464            )
     2465        );
     2466
     2467        $response = new WP_REST_Response();
     2468        $response->add_link( 'self', rest_url( 'test-ns/v1/test/garbage' ) );
     2469
     2470        $links = rest_get_server()::get_response_links( $response );
     2471
     2472        $this->assertArrayHasKey( 'self', $links );
     2473        $this->assertArrayNotHasKey( 'targetHints', $links['self'][0] );
     2474    }
     2475
     2476    /**
     2477     * @ticket 61739
     2478     */
     2479    public function test_sanitizes_request_when_building_target_hints() {
     2480        $validated_param = null;
     2481        register_rest_route(
     2482            'test-ns/v1',
     2483            '/test/(?P<id>\d+)',
     2484            array(
     2485                array(
     2486                    'methods'             => \WP_REST_Server::READABLE,
     2487                    'callback'            => static function () {
     2488                        return new \WP_REST_Response();
     2489                    },
     2490                    'permission_callback' => function ( WP_REST_Request $request ) use ( &$validated_param ) {
     2491                        $validated_param = $request['id'];
     2492
     2493                        return true;
     2494                    },
     2495                    'args'                => array(
     2496                        'id' => array(
     2497                            'type' => 'integer',
     2498                        ),
     2499                    ),
     2500                ),
     2501            )
     2502        );
     2503
     2504        $response = new WP_REST_Response();
     2505        $response->add_link( 'self', rest_url( 'test-ns/v1/test/5' ) );
     2506
     2507        $links = rest_get_server()::get_response_links( $response );
     2508
     2509        $this->assertArrayHasKey( 'self', $links );
     2510        $this->assertArrayHasKey( 'targetHints', $links['self'][0] );
     2511        $this->assertIsInt( $validated_param );
     2512    }
     2513
     2514    /**
     2515     * @ticket 61739
     2516     */
     2517    public function test_populates_target_hints_for_administrator() {
     2518        wp_set_current_user( self::$admin_id );
     2519        $response = rest_do_request( '/wp/v2/posts' );
     2520        $post     = $response->get_data()[0];
     2521
     2522        $link = $post['_links']['self'][0];
     2523        $this->assertArrayHasKey( 'targetHints', $link );
     2524        $this->assertArrayHasKey( 'allow', $link['targetHints'] );
     2525        $this->assertSame( array( 'GET', 'POST', 'PUT', 'PATCH', 'DELETE' ), $link['targetHints']['allow'] );
     2526    }
     2527
     2528    /**
     2529     * @ticket 61739
     2530     */
     2531    public function test_populates_target_hints_for_logged_out_user() {
     2532        $response = rest_do_request( '/wp/v2/posts' );
     2533        $post     = $response->get_data()[0];
     2534
     2535        $link = $post['_links']['self'][0];
     2536        $this->assertArrayHasKey( 'targetHints', $link );
     2537        $this->assertArrayHasKey( 'allow', $link['targetHints'] );
     2538        $this->assertSame( array( 'GET' ), $link['targetHints']['allow'] );
     2539    }
     2540
     2541    /**
     2542     * @ticket 61739
     2543     */
     2544    public function test_does_not_error_on_invalid_urls() {
     2545        $response = new WP_REST_Response();
     2546        $response->add_link( 'self', 'this is not a real URL' );
     2547
     2548        $links = rest_get_server()::get_response_links( $response );
     2549        $this->assertArrayNotHasKey( 'targetHints', $links['self'][0] );
     2550    }
     2551
     2552    /**
     2553     * @ticket 61739
     2554     */
     2555    public function test_does_not_error_on_bad_rest_api_routes() {
     2556        $response = new WP_REST_Response();
     2557        $response->add_link( 'self', rest_url( '/this/is/not/a/real/route' ) );
     2558
     2559        $links = rest_get_server()::get_response_links( $response );
     2560        $this->assertArrayNotHasKey( 'targetHints', $links['self'][0] );
     2561    }
     2562
     2563    /**
     2564     * @ticket 61739
     2565     */
     2566    public function test_prefers_developer_defined_target_hints() {
     2567        $response = new WP_REST_Response();
     2568        $response->add_link(
     2569            'self',
     2570            '/wp/v2/posts/' . self::$post_id,
     2571            array(
     2572                'targetHints' => array(
     2573                    'allow' => array( 'GET', 'PUT' ),
     2574                ),
     2575            )
     2576        );
     2577
     2578        $links = rest_get_server()::get_response_links( $response );
     2579        $link  = $links['self'][0];
     2580        $this->assertArrayHasKey( 'targetHints', $link );
     2581        $this->assertArrayHasKey( 'allow', $link['targetHints'] );
     2582        $this->assertSame( array( 'GET', 'PUT' ), $link['targetHints']['allow'] );
     2583    }
     2584
    24342585    public function _validate_as_integer_123( $value, $request, $key ) {
    24352586        if ( ! is_int( $value ) ) {
Note: See TracChangeset for help on using the changeset viewer.