Make WordPress Core

Changeset 50490


Ignore:
Timestamp:
03/04/2021 12:12:55 AM (4 years ago)
Author:
peterwilsoncc
Message:

Roles/Caps: Return same result from current_user_can and user_can().

Ensure current_user_can() and user_can() return the same results for logged out users. For core capabilities this changes user_can( 0, 'exist' ) to return true rather than false in line with current_user_can( 'exist' ) for logged out users.

Convert current_user_can() and current_user_can_for_blog() to wrapper functions ultimately calling user_can().

Add anonymous user to primitive capability checks as appropriate. Convert Tests_User_Capabilities::test_other_caps_for_all_roles() to use a data provider and add tests to check whether user exists in the database (WP_User::exists()) as that intentionally differs from the exist capability.

Props jjj, johnbillion, peterwilsoncc, SergeyBiryukov, TimothyBlynJacobs.
Fixes #52076.

Location:
trunk
Files:
3 edited

Legend:

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

    r50138 r50490  
    680680 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
    681681 *              by adding it to the function signature.
     682 * @since 5.8.0 Converted to wrapper for the user_can() function.
    682683 *
    683684 * @see WP_User::has_cap()
     
    690691 */
    691692function current_user_can( $capability, ...$args ) {
    692     $current_user = wp_get_current_user();
    693 
    694     if ( empty( $current_user ) ) {
    695         return false;
    696     }
    697 
    698     return $current_user->has_cap( $capability, ...$args );
     693    return user_can( wp_get_current_user(), $capability, ...$args );
    699694}
    700695
     
    715710 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
    716711 *              by adding it to the function signature.
     712 * @since 5.8.0 Wraps current_user_can() after switching to blog.
    717713 *
    718714 * @param int    $blog_id    Site ID.
     
    724720    $switched = is_multisite() ? switch_to_blog( $blog_id ) : false;
    725721
    726     $current_user = wp_get_current_user();
    727 
    728     if ( empty( $current_user ) ) {
    729         if ( $switched ) {
    730             restore_current_blog();
    731         }
    732         return false;
    733     }
    734 
    735     $can = $current_user->has_cap( $capability, ...$args );
     722    $can = current_user_can( $capability, ...$args );
    736723
    737724    if ( $switched ) {
     
    806793    }
    807794
    808     if ( ! $user || ! $user->exists() ) {
    809         return false;
     795    if ( empty( $user ) ) {
     796        // User is logged out, create anonymous user object.
     797        $user = new WP_User( 0 );
     798        $user->init( new stdClass );
    810799    }
    811800
  • trunk/src/wp-includes/class-wp-user.php

    r49790 r50490  
    168168     */
    169169    public function init( $data, $site_id = '' ) {
     170        if ( ! isset( $data->ID ) ) {
     171            $data->ID = 0;
     172        }
    170173        $this->data = $data;
    171174        $this->ID   = (int) $data->ID;
  • trunk/tests/phpunit/tests/user/capabilities.php

    r50131 r50490  
    1313     */
    1414    protected static $users = array(
     15        'anonymous'     => null,
    1516        'administrator' => null,
    1617        'editor'        => null,
     
    3233    public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
    3334        self::$users       = array(
     35            'anonymous'     => new WP_User( 0 ),
    3436            'administrator' => $factory->user->create_and_get( array( 'role' => 'administrator' ) ),
    3537            'editor'        => $factory->user->create_and_get( array( 'role' => 'editor' ) ),
     
    343345    }
    344346
     347    /**
     348     * Data provider for testing a single site install's roles.
     349     *
     350     * @return array[] {
     351     *     Arguments for test.
     352     *
     353     *     @type string $role The role to test for.
     354     * }
     355     */
     356    function data_single_site_roles_to_check() {
     357        return array(
     358            array( 'anonymous' ),
     359            array( 'administrator' ),
     360            array( 'editor' ),
     361            array( 'author' ),
     362            array( 'contributor' ),
     363            array( 'subscriber' ),
     364        );
     365    }
     366
    345367    protected function getAllCapsAndRoles() {
    346368        return $this->getPrimitiveCapsAndRoles() + $this->getMetaCapsAndRoles();
     
    392414
    393415        foreach ( self::$users as $role => $user ) {
    394 
    395             // Make sure the user is valid.
    396             $this->assertTrue( $user->exists(), "User with {$role} role does not exist" );
     416            if ( 'anonymous' === $role ) {
     417                // The anonymous role does not exist.
     418                $this->assertFalse( $user->exists(), "User with {$role} role should not exist" );
     419            } else {
     420                // Make sure the user is valid.
     421                $this->assertTrue( $user->exists(), "User with {$role} role does not exist" );
     422            }
    397423
    398424            $user_caps = $user->allcaps;
     
    564590    /**
    565591     * Test miscellaneous capabilities of all user roles.
    566      */
    567     function test_other_caps_for_all_roles() {
    568         foreach ( self::$users as $role => $user ) {
    569             // Make sure the user is valid.
    570             $this->assertTrue( $user->exists(), "User with {$role} role does not exist" );
    571 
    572             // Make sure the role name is correct.
    573             $this->assertSame( array( $role ), $user->roles, "User should only have the {$role} role" );
    574 
    575             $this->assertFalse( $user->has_cap( 'start_a_fire' ), "User with the {$role} role should not have a custom capability" );
    576             $this->assertFalse( user_can( $user, 'start_a_fire' ), "User with the {$role} role should not have a custom capability" );
    577 
    578             $this->assertFalse( $user->has_cap( 'do_not_allow' ), "User with the {$role} role should not have the do_not_allow capability" );
    579             $this->assertFalse( user_can( $user, 'do_not_allow' ), "User with the {$role} role should not have the do_not_allow capability" );
    580 
    581             $this->assertTrue( $user->has_cap( 'exist' ), "User with the {$role} role should have the exist capability" );
    582             $this->assertTrue( user_can( $user, 'exist' ), "User with the {$role} role should have the exist capability" );
    583         }
     592     *
     593     * @dataProvider data_single_site_roles_to_check
     594     */
     595    function test_other_caps_for_all_roles( $role ) {
     596        $user   = self::$users[ $role ];
     597        $old_id = wp_get_current_user()->ID;
     598        wp_set_current_user( $user->ID );
     599
     600        // Make sure the role name is correct.
     601        $expected_roles = array( $role );
     602        if ( 'anonymous' === $role ) {
     603            //  Anonymous role does not exist, user roles should be empty.
     604            $expected_roles = array();
     605        }
     606        $this->assertSame( $expected_roles, $user->roles, "User should only have the {$role} role" );
     607
     608        $this->assertFalse( $user->has_cap( 'start_a_fire' ), "User with the {$role} role should not have a custom capability (test via WP_User->has_cap() method)." );
     609        $this->assertFalse( user_can( $user, 'start_a_fire' ), "User with the {$role} role should not have a custom capability (test by user object)." );
     610        $this->assertFalse( user_can( $user->ID, 'start_a_fire' ), "User with the {$role} role should not have a custom capability (test by user ID)." );
     611        $this->assertFalse( current_user_can( 'start_a_fire' ), "User with the {$role} role should not have a custom capability (test by current user)." );
     612
     613        $this->assertFalse( $user->has_cap( 'do_not_allow' ), "User with the {$role} role should not have the do_not_allow capability (test via WP_User->has_cap() method)." );
     614        $this->assertFalse( user_can( $user, 'do_not_allow' ), "User with the {$role} role should not have the do_not_allow capability (test by user object)." );
     615        $this->assertFalse( user_can( $user->ID, 'do_not_allow' ), "User with the {$role} role should not have the do_not_allow capability (test by user ID)." );
     616        $this->assertFalse( current_user_can( 'do_not_allow' ), "User with the {$role} role should not have the do_not_allow capability (test by current user)." );
     617
     618        $this->assertTrue( $user->has_cap( 'exist' ), "User with the {$role} role should have the exist capability (test via WP_User->has_cap() method)." );
     619        $this->assertTrue( user_can( $user, 'exist' ), "User with the {$role} role should have the exist capability (test by user object)." );
     620        $this->assertTrue( user_can( $user->ID, 'exist' ), "User with the {$role} role should have the exist capability (test by user ID)." );
     621        $this->assertTrue( current_user_can( 'exist' ), "User with the {$role} role should have the exist capability (test by current user)." );
     622
     623        wp_set_current_user( $old_id );
     624    }
     625
     626    /**
     627     * Test user exists/does not exist as expected.
     628     *
     629     * @dataProvider data_single_site_roles_to_check
     630     */
     631    function test_user_exists_in_database( $role ) {
     632        $user     = self::$users[ $role ];
     633        $expected = true;
     634
     635        if ( 'anonymous' === $role ) {
     636            $expected = false;
     637        }
     638
     639        $this->assertSame( $expected, $user->exists() );
    584640    }
    585641
     
    595651            $user->remove_cap( 'do_not_allow' );
    596652            $this->assertFalse( $has_cap, "User with the {$role} role should not have the do_not_allow capability" );
     653
     654            # Test adding the cap via a filter
     655            add_filter( 'user_has_cap', array( $this, 'grant_do_not_allow' ), 10, 4 );
     656            $has_cap = $user->has_cap( 'do_not_allow' );
     657            remove_filter( 'user_has_cap', array( $this, 'grant_do_not_allow' ), 10, 4 );
     658            $this->assertFalse( $has_cap, "User with the {$role} role should not have the do_not_allow capability" );
     659
     660            if ( 'anonymous' === $role ) {
     661                // The anonymous role does not exist.
     662                continue;
     663            }
    597664
    598665            # Test adding the cap to the user's role
     
    602669            $role_obj->remove_cap( 'do_not_allow' );
    603670            $this->assertFalse( $has_cap, "User with the {$role} role should not have the do_not_allow capability" );
    604 
    605             # Test adding the cap via a filter
    606             add_filter( 'user_has_cap', array( $this, 'grant_do_not_allow' ), 10, 4 );
    607             $has_cap = $user->has_cap( 'do_not_allow' );
    608             remove_filter( 'user_has_cap', array( $this, 'grant_do_not_allow' ), 10, 4 );
    609             $this->assertFalse( $has_cap, "User with the {$role} role should not have the do_not_allow capability" );
    610 
    611671        }
    612672    }
Note: See TracChangeset for help on using the changeset viewer.