From 666257a85abec5d897d824bdd7d1d1a96e8e74ae Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Tue, 11 Aug 2026 23:50:07 +0100 Subject: [PATCH 1/4] Prevent the default role from being set to a privileged role when user registration is open. --- src/wp-admin/options-general.php | 7 +- src/wp-includes/default-filters.php | 1 + src/wp-includes/functions.php | 24 +++ .../tests/functions/filterDefaultRole.php | 177 ++++++++++++++++++ 4 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 tests/phpunit/tests/functions/filterDefaultRole.php diff --git a/src/wp-admin/options-general.php b/src/wp-admin/options-general.php index 6a9a6d2f3812d..b9fd3e18e98c8 100644 --- a/src/wp-admin/options-general.php +++ b/src/wp-admin/options-general.php @@ -305,15 +305,20 @@ class="" set_users_can_register( false ); + + $this->assertSame( + $role, + filter_default_role( $role ), + 'The default role was changed while user registration was closed.' + ); + } + + /** + * Ensures privileged roles are replaced with the subscriber role when user registration is open. + * + * @dataProvider data_excluded_roles + */ + public function test_excluded_role_is_replaced_when_registration_is_open( string $role ) { + $this->set_users_can_register( true ); + + $this->assertSame( + 'subscriber', + filter_default_role( $role ), + 'The privileged default role was not replaced with the subscriber role.' + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public static function data_excluded_roles() { + return array( + 'administrator' => array( 'administrator' ), + 'editor' => array( 'editor' ), + ); + } + + /** + * Ensures roles that are not excluded are left alone when user registration is open. + * + * @dataProvider data_allowed_roles + */ + public function test_allowed_role_is_unchanged_when_registration_is_open( string $role ) { + $this->set_users_can_register( true ); + + $this->assertSame( + $role, + filter_default_role( $role ), + 'A role which is not excluded was changed.' + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public static function data_allowed_roles() { + return array( + 'author' => array( 'author' ), + 'contributor' => array( 'contributor' ), + 'subscriber' => array( 'subscriber' ), + 'unknown role' => array( 'this-role-does-not-exist' ), + 'empty string' => array( '' ), + ); + } + + /** + * Ensures a role can be added to the list of excluded roles. + */ + public function test_excluded_roles_can_be_added_to() { + $this->set_users_can_register( true ); + + add_filter( + 'default_role_excluded_roles', + static function ( $excluded ) { + $excluded[] = 'author'; + return $excluded; + } + ); + + $this->assertSame( + 'subscriber', + filter_default_role( 'author' ), + 'A role added to the excluded roles was not replaced with the subscriber role.' + ); + } + + /** + * Ensures a role can be removed from the list of excluded roles. + */ + public function test_excluded_roles_can_be_removed() { + $this->set_users_can_register( true ); + + add_filter( 'default_role_excluded_roles', '__return_empty_array' ); + + $this->assertSame( + 'administrator', + filter_default_role( 'administrator' ), + 'A role removed from the excluded roles was replaced.' + ); + } + + /** + * Ensures a new user is not assigned a privileged role when user registration is open. + */ + public function test_new_user_is_not_assigned_a_privileged_role() { + $this->set_users_can_register( true ); + update_option( 'default_role', 'administrator' ); + + $user_id = wp_insert_user( + array( + 'user_login' => 'test_default_role', + 'user_pass' => 'password', + 'user_email' => 'test_default_role@example.org', + ) + ); + + $this->assertNotWPError( $user_id, 'The user was not created.' ); + $this->assertSame( + array( 'subscriber' ), + get_userdata( $user_id )->roles, + 'The new user was assigned a privileged role.' + ); + } + + /** + * Ensures a new user is assigned the stored role when user registration is closed. + */ + public function test_new_user_is_assigned_the_stored_role_when_registration_is_closed() { + $this->set_users_can_register( false ); + update_option( 'default_role', 'editor' ); + + $user_id = wp_insert_user( + array( + 'user_login' => 'test_default_role', + 'user_pass' => 'password', + 'user_email' => 'test_default_role@example.org', + ) + ); + + $this->assertNotWPError( $user_id, 'The user was not created.' ); + $this->assertSame( + array( 'editor' ), + get_userdata( $user_id )->roles, + 'The new user was not assigned the stored default role.' + ); + } +} From e705bedf0e578aae963650e5375b8dbfe129feaf Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sun, 16 Aug 2026 16:28:35 -0700 Subject: [PATCH 2/4] Docs. --- src/wp-admin/options-general.php | 2 +- src/wp-includes/functions.php | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/wp-admin/options-general.php b/src/wp-admin/options-general.php index b9fd3e18e98c8..b21055b867c66 100644 --- a/src/wp-admin/options-general.php +++ b/src/wp-admin/options-general.php @@ -305,7 +305,7 @@ class="" Date: Thu, 27 Aug 2026 20:47:20 +0200 Subject: [PATCH 3/4] Expand the multisite tests. --- .../tests/functions/filterDefaultRole.php | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/phpunit/tests/functions/filterDefaultRole.php b/tests/phpunit/tests/functions/filterDefaultRole.php index 5ec6b6e0693c8..181cc52f35690 100644 --- a/tests/phpunit/tests/functions/filterDefaultRole.php +++ b/tests/phpunit/tests/functions/filterDefaultRole.php @@ -152,6 +152,42 @@ public function test_new_user_is_not_assigned_a_privileged_role() { ); } + /** + * Ensures the network registration setting governs the default role on Multisite, even when + * the `users_can_register` option of the current site is closed. + * + * @ticket 46744 + * @group ms-required + */ + public function test_network_registration_governs_when_site_option_is_closed() { + update_site_option( 'registration', 'user' ); + update_option( 'users_can_register', 0 ); + + $this->assertSame( + 'subscriber', + filter_default_role( 'administrator' ), + 'The privileged default role was not replaced while network registration was open.' + ); + } + + /** + * Ensures the network registration setting governs the default role on Multisite, even when + * the `users_can_register` option of the current site is open. + * + * @ticket 46744 + * @group ms-required + */ + public function test_network_registration_governs_when_site_option_is_open() { + update_site_option( 'registration', 'none' ); + update_option( 'users_can_register', 1 ); + + $this->assertSame( + 'administrator', + filter_default_role( 'administrator' ), + 'The default role was changed while network registration was closed.' + ); + } + /** * Ensures a new user is assigned the stored role when user registration is closed. */ From c6af56f55eae0e88d61ff60999e2d8e5cc5a764f Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 27 Aug 2026 20:47:29 +0200 Subject: [PATCH 4/4] Add a re-entry guard. --- src/wp-includes/functions.php | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index bb237f12568e3..9ca4fb994dd72 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -9408,25 +9408,31 @@ function wp_verify_fast_hash( * @return string The filtered default role for new user registrations. */ function filter_default_role( $default_role ) { - $users_can_register = get_option( 'users_can_register' ); + static $filtering = false; - if ( ! $users_can_register ) { + if ( $filtering ) { return $default_role; } - /** - * Filters the roles that are excluded from being available as the default role for new user registrations. - * - * @since 7.2.0 - * - * @param string[] $roles Roles that are excluded from being available. - */ - $excluded = apply_filters( 'default_role_excluded_roles', array( 'administrator', 'editor' ) ); + $filtering = true; - // Don't allow a privileged default role if users can register. - if ( in_array( $default_role, $excluded, true ) ) { - $default_role = 'subscriber'; + if ( get_option( 'users_can_register' ) ) { + /** + * Filters the roles that are excluded from being available as the default role for new user registrations. + * + * @since 7.2.0 + * + * @param string[] $roles Roles that are excluded from being available. + */ + $excluded = apply_filters( 'default_role_excluded_roles', array( 'administrator', 'editor' ) ); + + // Don't allow a privileged default role if users can register. + if ( in_array( $default_role, $excluded, true ) ) { + $default_role = 'subscriber'; + } } + $filtering = false; + return $default_role; }