Make WordPress Core

Changeset 53408


Ignore:
Timestamp:
05/17/2022 06:59:24 PM (2 years ago)
Author:
SergeyBiryukov
Message:

Users: Fail gracefully when checking mapped capabilities without providing the required object ID.

This avoids an Undefined array key 0 PHP warning for current_user_can() capability checks that require a specific object to check against but an object ID was not passed.

A _doing_it_wrong() notice is also added, so that developers and site administrators are aware that the capability mapping is failing in the absence of the required object ID.

The list of mapped capabilities that require an object ID:

  • delete_post / delete_page
  • edit_post / edit_page
  • read_post / read_page
  • publish_post
  • edit_(post|comment|term|user)_meta / delete_*_meta / add_*_meta
  • edit_comment
  • edit_term / delete_term / assign_term

Follow-up to [34091], [34113], [47178].

Props jeherve, peterwilsoncc, henry.wright, johnbillion, mattheweppelsheimer, hellofromTonya, JeffPaul, azouamauriac, Ninos Ego, TobiasBg, wpsmith, GaryJ, nacin, johnstonphilip, azaozz, SergeyBiryukov.
Fixes #44591.

Location:
trunk
Files:
4 edited

Legend:

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

    r53249 r53408  
    7474        case 'delete_post':
    7575        case 'delete_page':
     76            if ( ! isset( $args[0] ) ) {
     77                if ( 'delete_post' === $cap ) {
     78                    /* translators: %s: Capability name. */
     79                    $message = __( 'When checking for the %s capability, you must always check it against a specific post.' );
     80                } else {
     81                    /* translators: %s: Capability name. */
     82                    $message = __( 'When checking for the %s capability, you must always check it against a specific page.' );
     83                }
     84
     85                _doing_it_wrong(
     86                    __FUNCTION__,
     87                    sprintf( $message, '<code>' . $cap . '</code>' ),
     88                    '6.1.0'
     89                );
     90
     91                $caps[] = 'do_not_allow';
     92                break;
     93            }
     94
    7695            $post = get_post( $args[0] );
    7796            if ( ! $post ) {
     
    93112            if ( ! $post_type ) {
    94113                /* translators: 1: Post type, 2: Capability name. */
    95                 _doing_it_wrong( __FUNCTION__, sprintf( __( 'The post type %1$s is not registered, so it may not be reliable to check the capability "%2$s" against a post of that type.' ), $post->post_type, $cap ), '4.4.0' );
     114                $message = __( 'The post type %1$s is not registered, so it may not be reliable to check the capability %2$s against a post of that type.' );
     115
     116                _doing_it_wrong(
     117                    __FUNCTION__,
     118                    sprintf(
     119                        $message,
     120                        '<code>' . $post->post_type . '</code>',
     121                        '<code>' . $cap . '</code>'
     122                    ),
     123                    '4.4.0'
     124                );
     125
    96126                $caps[] = 'edit_others_posts';
    97127                break;
     
    147177        case 'edit_post':
    148178        case 'edit_page':
     179            if ( ! isset( $args[0] ) ) {
     180                if ( 'edit_post' === $cap ) {
     181                    /* translators: %s: Capability name. */
     182                    $message = __( 'When checking for the %s capability, you must always check it against a specific post.' );
     183                } else {
     184                    /* translators: %s: Capability name. */
     185                    $message = __( 'When checking for the %s capability, you must always check it against a specific page.' );
     186                }
     187
     188                _doing_it_wrong(
     189                    __FUNCTION__,
     190                    sprintf( $message, '<code>' . $cap . '</code>' ),
     191                    '6.1.0'
     192                );
     193
     194                $caps[] = 'do_not_allow';
     195                break;
     196            }
     197
    149198            $post = get_post( $args[0] );
    150199            if ( ! $post ) {
     
    164213            if ( ! $post_type ) {
    165214                /* translators: 1: Post type, 2: Capability name. */
    166                 _doing_it_wrong( __FUNCTION__, sprintf( __( 'The post type %1$s is not registered, so it may not be reliable to check the capability "%2$s" against a post of that type.' ), $post->post_type, $cap ), '4.4.0' );
     215                $message = __( 'The post type %1$s is not registered, so it may not be reliable to check the capability %2$s against a post of that type.' );
     216
     217                _doing_it_wrong(
     218                    __FUNCTION__,
     219                    sprintf(
     220                        $message,
     221                        '<code>' . $post->post_type . '</code>',
     222                        '<code>' . $cap . '</code>'
     223                    ),
     224                    '4.4.0'
     225                );
     226
    167227                $caps[] = 'edit_others_posts';
    168228                break;
     
    216276        case 'read_post':
    217277        case 'read_page':
     278            if ( ! isset( $args[0] ) ) {
     279                if ( 'read_post' === $cap ) {
     280                    /* translators: %s: Capability name. */
     281                    $message = __( 'When checking for the %s capability, you must always check it against a specific post.' );
     282                } else {
     283                    /* translators: %s: Capability name. */
     284                    $message = __( 'When checking for the %s capability, you must always check it against a specific page.' );
     285                }
     286
     287                _doing_it_wrong(
     288                    __FUNCTION__,
     289                    sprintf( $message, '<code>' . $cap . '</code>' ),
     290                    '6.1.0'
     291                );
     292
     293                $caps[] = 'do_not_allow';
     294                break;
     295            }
     296
    218297            $post = get_post( $args[0] );
    219298            if ( ! $post ) {
     
    233312            if ( ! $post_type ) {
    234313                /* translators: 1: Post type, 2: Capability name. */
    235                 _doing_it_wrong( __FUNCTION__, sprintf( __( 'The post type %1$s is not registered, so it may not be reliable to check the capability "%2$s" against a post of that type.' ), $post->post_type, $cap ), '4.4.0' );
     314                $message = __( 'The post type %1$s is not registered, so it may not be reliable to check the capability %2$s against a post of that type.' );
     315
     316                _doing_it_wrong(
     317                    __FUNCTION__,
     318                    sprintf(
     319                        $message,
     320                        '<code>' . $post->post_type . '</code>',
     321                        '<code>' . $cap . '</code>'
     322                    ),
     323                    '4.4.0'
     324                );
     325
    236326                $caps[] = 'edit_others_posts';
    237327                break;
     
    250340            if ( ! $status_obj ) {
    251341                /* translators: 1: Post status, 2: Capability name. */
    252                 _doing_it_wrong( __FUNCTION__, sprintf( __( 'The post status %1$s is not registered, so it may not be reliable to check the capability "%2$s" against a post with that status.' ), get_post_status( $post ), $cap ), '5.4.0' );
     342                $message = __( 'The post status %1$s is not registered, so it may not be reliable to check the capability %2$s against a post with that status.' );
     343
     344                _doing_it_wrong(
     345                    __FUNCTION__,
     346                    sprintf(
     347                        $message,
     348                        '<code>' . get_post_status( $post ) . '</code>',
     349                        '<code>' . $cap . '</code>'
     350                    ),
     351                    '5.4.0'
     352                );
     353
    253354                $caps[] = 'edit_others_posts';
    254355                break;
     
    269370            break;
    270371        case 'publish_post':
     372            if ( ! isset( $args[0] ) ) {
     373                /* translators: %s: Capability name. */
     374                $message = __( 'When checking for the %s capability, you must always check it against a specific post.' );
     375
     376                _doing_it_wrong(
     377                    __FUNCTION__,
     378                    sprintf( $message, '<code>' . $cap . '</code>' ),
     379                    '6.1.0'
     380                );
     381
     382                $caps[] = 'do_not_allow';
     383                break;
     384            }
     385
    271386            $post = get_post( $args[0] );
    272387            if ( ! $post ) {
     
    278393            if ( ! $post_type ) {
    279394                /* translators: 1: Post type, 2: Capability name. */
    280                 _doing_it_wrong( __FUNCTION__, sprintf( __( 'The post type %1$s is not registered, so it may not be reliable to check the capability "%2$s" against a post of that type.' ), $post->post_type, $cap ), '4.4.0' );
     395                $message = __( 'The post type %1$s is not registered, so it may not be reliable to check the capability %2$s against a post of that type.' );
     396
     397                _doing_it_wrong(
     398                    __FUNCTION__,
     399                    sprintf(
     400                        $message,
     401                        '<code>' . $post->post_type . '</code>',
     402                        '<code>' . $cap . '</code>'
     403                    ),
     404                    '4.4.0'
     405                );
     406
    281407                $caps[] = 'edit_others_posts';
    282408                break;
     
    298424        case 'add_user_meta':
    299425            $object_type = explode( '_', $cap )[1];
    300             $object_id   = (int) $args[0];
     426
     427            if ( ! isset( $args[0] ) ) {
     428                if ( 'post' === $object_type ) {
     429                    /* translators: %s: Capability name. */
     430                    $message = __( 'When checking for the %s capability, you must always check it against a specific post.' );
     431                } elseif ( 'comment' === $object_type ) {
     432                    /* translators: %s: Capability name. */
     433                    $message = __( 'When checking for the %s capability, you must always check it against a specific comment.' );
     434                } elseif ( 'term' === $object_type ) {
     435                    /* translators: %s: Capability name. */
     436                    $message = __( 'When checking for the %s capability, you must always check it against a specific term.' );
     437                } else {
     438                    /* translators: %s: Capability name. */
     439                    $message = __( 'When checking for the %s capability, you must always check it against a specific user.' );
     440                }
     441
     442                _doing_it_wrong(
     443                    __FUNCTION__,
     444                    sprintf( $message, '<code>' . $cap . '</code>' ),
     445                    '6.1.0'
     446                );
     447
     448                $caps[] = 'do_not_allow';
     449                break;
     450            }
     451
     452            $object_id = (int) $args[0];
    301453
    302454            $object_subtype = get_object_subtype( $object_type, $object_id );
     
    393545            break;
    394546        case 'edit_comment':
     547            if ( ! isset( $args[0] ) ) {
     548                /* translators: %s: Capability name. */
     549                $message = __( 'When checking for the %s capability, you must always check it against a specific comment.' );
     550
     551                _doing_it_wrong(
     552                    __FUNCTION__,
     553                    sprintf( $message, '<code>' . $cap . '</code>' ),
     554                    '6.1.0'
     555                );
     556
     557                $caps[] = 'do_not_allow';
     558                break;
     559            }
     560
    395561            $comment = get_comment( $args[0] );
    396562            if ( ! $comment ) {
     
    533699        case 'delete_term':
    534700        case 'assign_term':
     701            if ( ! isset( $args[0] ) ) {
     702                /* translators: %s: Capability name. */
     703                $message = __( 'When checking for the %s capability, you must always check it against a specific term.' );
     704
     705                _doing_it_wrong(
     706                    __FUNCTION__,
     707                    sprintf( $message, '<code>' . $cap . '</code>' ),
     708                    '6.1.0'
     709                );
     710
     711                $caps[] = 'do_not_allow';
     712                break;
     713            }
     714
    535715            $term_id = (int) $args[0];
    536716            $term    = get_term( $term_id );
  • trunk/tests/phpunit/tests/user.php

    r52650 r53408  
    19751975        );
    19761976
    1977         // _doing_wrong() should be called because the filter callback
     1977        // _doing_it_wrong() should be called because the filter callback
    19781978        // adds a item with a 'name' that is the same as one generated by core.
    19791979        $this->setExpectedIncorrectUsage( 'wp_user_personal_data_exporter' );
  • trunk/tests/phpunit/tests/user/capabilities.php

    r52824 r53408  
    15961596        $editor = self::$users['editor'];
    15971597
     1598        $this->setExpectedIncorrectUsage( 'map_meta_cap' );
    15981599        foreach ( $caps as $cap ) {
    15991600            // `null` represents a non-existent term ID.
  • trunk/tests/phpunit/tests/user/mapMetaCap.php

    r52010 r53408  
    44 * @group user
    55 * @group capabilities
     6 * @covers ::map_meta_cap
    67 */
    78class Tests_User_MapMetaCap extends WP_UnitTestCase {
     
    411412        $this->assertSame( array( 'manage_options' ), $caps );
    412413    }
     414
     415    /**
     416     * @dataProvider data_meta_caps_throw_doing_it_wrong_without_required_argument_provided
     417     * @ticket 44591
     418     *
     419     * @param string $cap The meta capability requiring an argument.
     420     */
     421    public function test_meta_caps_throw_doing_it_wrong_without_required_argument_provided( $cap ) {
     422        $admin_user = self::$user_id;
     423        $this->setExpectedIncorrectUsage( 'map_meta_cap' );
     424        $this->assertContains( 'do_not_allow', map_meta_cap( $cap, $admin_user ) );
     425    }
     426
     427    /**
     428     * Data provider.
     429     *
     430     * @return array[] Test parameters {
     431     *     @type string $cap The meta capability requiring an argument.
     432     * }
     433     */
     434    public function data_meta_caps_throw_doing_it_wrong_without_required_argument_provided() {
     435        return array(
     436            array( 'delete_post' ),
     437            array( 'delete_page' ),
     438            array( 'edit_post' ),
     439            array( 'edit_page' ),
     440            array( 'read_post' ),
     441            array( 'read_page' ),
     442            array( 'publish_post' ),
     443            array( 'edit_post_meta' ),
     444            array( 'delete_post_meta' ),
     445            array( 'add_post_meta' ),
     446            array( 'edit_comment_meta' ),
     447            array( 'delete_comment_meta' ),
     448            array( 'add_comment_meta' ),
     449            array( 'edit_term_meta' ),
     450            array( 'delete_term_meta' ),
     451            array( 'add_term_meta' ),
     452            array( 'edit_user_meta' ),
     453            array( 'delete_user_meta' ),
     454            array( 'add_user_meta' ),
     455            array( 'edit_comment' ),
     456            array( 'edit_term' ),
     457            array( 'delete_term' ),
     458            array( 'assign_term' ),
     459        );
     460    }
    413461}
Note: See TracChangeset for help on using the changeset viewer.