1

current_user_can('custom_capability') always returns true for super admins even if the capability is explicitly denied as stated here. It does work with a normal admin account.

I have added a custom capability for users with a specific email address, which is set when new users are registered and on plugin activation.

if (!empty(get_super_admins())) {
    foreach (get_users(['login__in' => get_super_admins()]) as $user) {
        $user->add_cap('custom_capability', false);
    }
}

foreach (get_users(['search' => '*@specific-domain.com']) as $user) {
    $user->add_cap('custom_capability');
}

Checking a super admin profile without that specific email address correctly states Denied: custom_capability under the additional capabilities section of the profile.

The output of wp_get_current_user() if logged in with the same super admin account as above:

...

  ["caps"]=>
  array(2) {
    ["custom_capability"]=>
    bool(false)
    ["administrator"]=>
    bool(true)
  }

...

Nonetheless current_user_can('custom_capability') returns true.

Did anybody else ever have this issue? Am I overlooking something and this is intended behavior?

1 Answer 1

2

The documentation unfortunately not very clear on what "explicitly deny" actually entails. While setting a capability to false will normally explicitly deny the capability, that check is performed after super admins have been waved through. So even though the documentation says "unless explicitly denied", denying it this way doesn't work for super admins. Only the other roles.

The only way to explicitly deny a capability for a super admin is to use the map_meta_cap filter to return do_not_allow when the custom capability is checked.

The map_meta_cap() function, which the filter applies to, is quite complicated, but its basic purpose is to do things like translate current_user_can( 'edit_post', 2 ) into either the edit_posts or edit_others_posts capability based on who the user is. Because map_meta_cap() is run before the super admin check, we can use this filter to explicitly return do_not_allow when checks for current_user_can( 'custom_capability' ) are made for a super admin that doesn't have the capability. Here's some code to do that:

add_filter(
    'map_meta_cap',
    function( $caps, $cap, $user_id ) {
        /**
         * Only filter checks for custom_capability.
         */
        if ( 'custom_capability' === $cap ) {
            $user      = get_userdata( $user_id );
            $user_caps = $user->get_role_caps();

            /**
             * If the user does not have the capability, or it's denied, then
             * add do_not_allow.
             */
            if ( empty( $user_caps[$cap] ) ) {
                $caps[] = 'do_not_allow';
            }
        }

        return $caps;
    },
    10,
    3
);
1
  • 1
    Awesome. Never thought the solution would come this fast. You might want to move the semicolon out of the 'do_not_allow ' string, though - other than that, this worked perfectly.
    – kwellmann
    Commented Apr 22, 2020 at 15:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.