Make WordPress Core

Changeset 53408


Ignore:
Timestamp:
05/17/2022 06:59:24 PM (4 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.