Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

## Unreleased

## 2.7.4 (2026-10-02)

* Require the network site-management capability before changing a user's
Multisite membership or role from the Sites profile section
* Require user-editing permission before changing another account's status,
and prevent status changes through the user's own profile
* Require role-promotion permission in the Permissions section for every site
on an editable network, and reject crafted self-role changes
* Match WordPress Core's existence check for primary sites without requiring
membership
* Apply Core's network, removal, and promotion checks to Sites bulk actions
* Preserve registered custom role slugs in Sites bulk actions

## 2.7.3 (2026-09-19)

* Add an opt-in modern profile layout controlled by the
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "wp-user-profiles",
"version": "2.7.3",
"version": "2.7.4",
"author": "John James Jacoby",
"private": true,
"license": "GPL-2.0-or-later",
Expand Down
10 changes: 9 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Tags: user, profile, edit, metabox
Requires PHP: 7.4
Requires at least: 6.4
Tested up to: 7.1
Stable tag: 2.7.3
Stable tag: 2.7.4

A sophisticated way to edit users in WordPress.

Expand Down Expand Up @@ -86,6 +86,14 @@ The classic layout remains the default unless the site owner enables this filter

== Changelog ==

= [2.7.4] - 2026-10-02 =
* Require network site-management permission to change Multisite membership or roles from a user profile
* Prevent profile saves from changing account status without user-editing permission
* Require role-promotion permission on editable networks and reject crafted self-role changes
* Validate primary site existence without requiring membership
* Apply network, removal, and promotion permissions to Sites bulk actions
* Preserve registered custom role slugs in Sites bulk actions

= [2.7.3] - 2026-09-19 =
* Add an opt-in modern profile layout controlled by the `wp_user_profiles_use_modern_styles` filter
* Prevent profile screens from overriding unrelated administration page titles
Expand Down
138 changes: 138 additions & 0 deletions tests/PermissionsSecurityTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php
/**
* Permissions section role-change regression tests.
*
* @package WP_User_Profiles
*/

use PHPUnit\Framework\TestCase;

require_once __DIR__ . '/fixtures/site-membership-stubs.php';

/**
* Test role changes at the section's own authorization boundary.
*/
class PermissionsSecurityTest extends TestCase {
/**
* Reset test doubles before each case.
*/
protected function setUp(): void {
wpup_test_reset();
}

/**
* Build a section with self-profile access.
*
* @return WP_User_Profile_Permissions_Section
*/
private function section(): WP_User_Profile_Permissions_Section {
$section = new WP_User_Profile_Permissions_Section();
$section->id = 'permissions';
$section->cap = 'edit_profile';
$section->errors = new WP_Error();
return $section;
}

/**
* Build a user with database fields for the shared save routine.
*
* @return WP_User
*/
private function user(): WP_User {
$user = new WP_User( 9 );
$user->data = (object) array( 'ID' => 9 );
return $user;
}

/**
* Self-profile access does not authorize current-site role changes.
*/
public function test_current_site_role_requires_promotion_permission(): void {
$GLOBALS['wpup_test']['is_multisite'] = true;
$GLOBALS['wpup_test']['capabilities']['edit_profile'] = true;
$_POST['role'] = array( 1 => 'administrator' );

$user = $this->user();
$this->section()->save( $user );

$this->assertSame( array(), $user->role_history );
}

/**
* Single-site role changes require promotion permission too.
*/
public function test_single_site_role_requires_promotion_permission(): void {
$GLOBALS['wpup_test']['capabilities']['edit_profile'] = true;
$_POST['role'] = array( 1 => 'administrator' );

$user = $this->user();
$this->section()->save( $user );

$this->assertSame( array(), $user->role_history );
}

/**
* Authorized current-site role changes still work.
*/
public function test_current_site_role_changes_with_promotion_permission(): void {
$GLOBALS['wpup_test']['is_multisite'] = true;
$GLOBALS['wpup_test']['capabilities']['edit_profile'] = true;
$GLOBALS['wpup_test']['capabilities']['promote_user'] = true;
$_POST['role'] = array( 1 => 'editor' );

$user = $this->user();
$this->section()->save( $user );

$this->assertSame( array( array( 'set', 'editor' ) ), $user->role_history );
$this->assertSame( 1, $GLOBALS['wpup_test']['current_blog_id'] );
}

/**
* A crafted self-profile request cannot change its own role.
*/
public function test_self_role_change_is_blocked(): void {
$GLOBALS['wpup_test']['capabilities']['edit_profile'] = true;
$GLOBALS['wpup_test']['capabilities']['promote_user'] = true;
$_POST['role'] = array( 1 => 'subscriber' );

$user = new WP_User( 1 );
$user->data = (object) array( 'ID' => 1 );
$this->section()->save( $user );

$this->assertSame( array(), $user->role_history );
}

/**
* A network administrator cannot change roles on another network.
*/
public function test_role_change_requires_network_access(): void {
$GLOBALS['wpup_test']['is_multisite'] = true;
$GLOBALS['wpup_test']['capabilities']['edit_profile'] = true;
$GLOBALS['wpup_test']['capabilities']['promote_user'] = true;
$GLOBALS['wpup_test']['can_edit_network'] = false;
$_POST['role'] = array( 2 => 'administrator' );

$user = $this->user();
$this->section()->save( $user );

$this->assertSame( array(), $user->role_history );
$this->assertSame( 1, $GLOBALS['wpup_test']['current_blog_id'] );
}

/**
* A role change cannot target a site that does not exist.
*/
public function test_role_change_requires_existing_site(): void {
$GLOBALS['wpup_test']['is_multisite'] = true;
$GLOBALS['wpup_test']['capabilities']['edit_profile'] = true;
$GLOBALS['wpup_test']['capabilities']['promote_user'] = true;
$GLOBALS['wpup_test']['sites'][2] = false;
$_POST['role'] = array( 2 => 'administrator' );

$user = $this->user();
$this->section()->save( $user );

$this->assertSame( array(), $user->role_history );
$this->assertSame( 1, $GLOBALS['wpup_test']['current_blog_id'] );
}
}
Loading
Loading