From ac59c45c6a881c4750c1092e27e3c8125cd9c547 Mon Sep 17 00:00:00 2001 From: Sukhendu Sekhar Guria Date: Mon, 8 Jun 2026 13:06:58 +0530 Subject: [PATCH] Users: Add self-protected user capability checks --- src/wp-includes/capabilities.php | 41 ++++++++++++++ tests/phpunit/tests/user/capabilities.php | 67 +++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/src/wp-includes/capabilities.php b/src/wp-includes/capabilities.php index 028e61ec414a8..234b1f21ff1ed 100644 --- a/src/wp-includes/capabilities.php +++ b/src/wp-includes/capabilities.php @@ -50,11 +50,30 @@ function map_meta_cap( $cap, $user_id, ...$args ) { // In multisite the user must be a super admin to remove themselves. if ( isset( $args[0] ) && $user_id === (int) $args[0] && ! is_super_admin( $user_id ) ) { $caps[] = 'do_not_allow'; + } elseif ( + isset( $args[0] ) + && user_can( (int) $args[0], 'self_protect' ) + && ! user_can( $user_id, 'manage_self_protected_users' ) + && ! user_can( $user_id, 'self_protect' ) + ) { + $caps[] = 'do_not_allow'; } else { $caps[] = 'remove_users'; } break; case 'promote_user': + if ( + isset( $args[0] ) + && user_can( (int) $args[0], 'self_protect' ) + && ! user_can( $user_id, 'manage_self_protected_users' ) + && ! user_can( $user_id, 'self_protect' ) + ) { + $caps[] = 'do_not_allow'; + break; + } + + $caps[] = 'promote_users'; + break; case 'add_users': $caps[] = 'promote_users'; break; @@ -71,6 +90,17 @@ function map_meta_cap( $cap, $user_id, ...$args ) { break; } + if ( + 'edit_user' === $cap + && isset( $args[0] ) + && user_can( (int) $args[0], 'self_protect' ) + && ! user_can( $user_id, 'manage_self_protected_users' ) + && ! user_can( $user_id, 'self_protect' ) + ) { + $caps[] = 'do_not_allow'; + break; + } + // In multisite the user must have manage_network_users caps. If editing a super admin, the user must be a super admin. if ( is_multisite() && ( ( ! is_super_admin( $user_id ) && 'edit_user' === $cap && is_super_admin( $args[0] ) ) || ! user_can( $user_id, 'manage_network_users' ) ) ) { $caps[] = 'do_not_allow'; @@ -672,6 +702,17 @@ function map_meta_cap( $cap, $user_id, ...$args ) { break; case 'delete_user': case 'delete_users': + if ( + 'delete_user' === $cap + && isset( $args[0] ) + && user_can( (int) $args[0], 'self_protect' ) + && ! user_can( $user_id, 'manage_self_protected_users' ) + && ! user_can( $user_id, 'self_protect' ) + ) { + $caps[] = 'do_not_allow'; + break; + } + // If multisite only super admins can delete users. if ( is_multisite() && ! is_super_admin( $user_id ) ) { $caps[] = 'do_not_allow'; diff --git a/tests/phpunit/tests/user/capabilities.php b/tests/phpunit/tests/user/capabilities.php index b92b0db231ecb..bdbd4b1a60553 100644 --- a/tests/phpunit/tests/user/capabilities.php +++ b/tests/phpunit/tests/user/capabilities.php @@ -1911,6 +1911,73 @@ public function test_only_admins_can_delete_users_on_single_site() { $this->assertFalse( user_can( self::$users['subscriber']->ID, 'delete_user', self::$users['subscriber']->ID ) ); } + /** + * @ticket 14460 + * + * @group ms-excluded + */ + public function test_user_cannot_manage_self_protected_user_without_override_cap() { + $admin = self::$users['administrator']; + $target = self::$users['subscriber']; + $caps = array( 'edit_user', 'delete_user', 'promote_user', 'remove_user' ); + + $target->add_cap( 'self_protect' ); + + try { + foreach ( $caps as $cap ) { + $this->assertFalse( user_can( $admin->ID, $cap, $target->ID ), "User should not have the {$cap} capability for a self-protected user." ); + } + + $admin->add_cap( 'self_protect' ); + + foreach ( $caps as $cap ) { + $this->assertTrue( user_can( $admin->ID, $cap, $target->ID ), "User should have the {$cap} capability for a self-protected user when they are also self-protected." ); + } + + $admin->remove_cap( 'self_protect' ); + $admin->add_cap( 'manage_self_protected_users' ); + + foreach ( $caps as $cap ) { + $this->assertTrue( user_can( $admin->ID, $cap, $target->ID ), "User should have the {$cap} capability for a self-protected user when they have the override capability." ); + } + } finally { + $admin->remove_cap( 'manage_self_protected_users' ); + $admin->remove_cap( 'self_protect' ); + $target->remove_cap( 'self_protect' ); + } + } + + /** + * @ticket 14460 + * + * @group ms-required + */ + public function test_multisite_admin_with_manage_network_users_cannot_edit_self_protected_user() { + $admin = self::$users['administrator']; + $target = self::$users['subscriber']; + + $admin->add_cap( 'manage_network_users' ); + $target->add_cap( 'self_protect' ); + + try { + $this->assertFalse( user_can( $admin->ID, 'edit_user', $target->ID ) ); + + $admin->add_cap( 'self_protect' ); + + $this->assertTrue( user_can( $admin->ID, 'edit_user', $target->ID ) ); + + $admin->remove_cap( 'self_protect' ); + $admin->add_cap( 'manage_self_protected_users' ); + + $this->assertTrue( user_can( $admin->ID, 'edit_user', $target->ID ) ); + } finally { + $admin->remove_cap( 'manage_self_protected_users' ); + $admin->remove_cap( 'self_protect' ); + $admin->remove_cap( 'manage_network_users' ); + $target->remove_cap( 'self_protect' ); + } + } + public function test_only_admins_and_super_admins_can_promote_users() { if ( is_multisite() ) { $this->assertTrue( user_can( self::$super_admin->ID, 'promote_user', self::$users['subscriber']->ID ) );