diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
index 63fb4e9..f81fee3 100644
--- a/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
+++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
@@ -570,12 +570,22 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
 			return $user;
 		}
 
-		if ( ! current_user_can( 'edit_user', $user->ID ) ) {
-			return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this user.' ), array( 'status' => rest_authorization_required_code() ) );
+		if ( ! empty( $request['roles'] ) ) {
+			if ( ! current_user_can( 'promote_user', $user->ID ) ) {
+				return new WP_Error( 'rest_cannot_edit_roles', __( 'Sorry, you are not allowed to edit roles of this user.' ), array( 'status' => rest_authorization_required_code() ) );
+			}
+
+			$request_params = array_keys( $request->get_params() );
+			sort( $request_params );
+			// If only 'id' and 'roles' are specified (we are only trying to
+			// edit roles), then only the 'promote_user' cap is required.
+			if ( $request_params === array( 'id', 'roles' ) ) {
+				return true;
+			}
 		}
 
-		if ( ! empty( $request['roles'] ) && ! current_user_can( 'edit_users' ) ) {
-			return new WP_Error( 'rest_cannot_edit_roles', __( 'Sorry, you are not allowed to edit roles of this user.' ), array( 'status' => rest_authorization_required_code() ) );
+		if ( ! current_user_can( 'edit_user', $user->ID ) ) {
+			return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this user.' ), array( 'status' => rest_authorization_required_code() ) );
 		}
 
 		return true;
diff --git a/tests/phpunit/tests/rest-api/rest-users-controller.php b/tests/phpunit/tests/rest-api/rest-users-controller.php
index 3024693..6af355d 100644
--- a/tests/phpunit/tests/rest-api/rest-users-controller.php
+++ b/tests/phpunit/tests/rest-api/rest-users-controller.php
@@ -1569,6 +1569,68 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase {
 		$this->assertErrorResponse( 'rest_user_invalid_id', $response, 404 );
 	}
 
+	/**
+	 * @ticket 40263
+	 */
+	public function test_update_item_only_roles_as_editor() {
+		$user_id = $this->factory->user->create( array(
+			'role' => 'author',
+		) );
+
+		wp_set_current_user( self::$editor );
+		$request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/users/%d', $user_id ) );
+		$request->set_param( 'roles', array( 'editor' ) );
+		$response = $this->server->dispatch( $request );
+		$this->assertErrorResponse( 'rest_cannot_edit_roles', $response, 403 );
+	}
+
+	/**
+	 * @ticket 40263
+	 */
+	public function test_update_item_only_roles_as_site_administrator() {
+		$user_id = $this->factory->user->create( array(
+			'role' => 'author',
+		) );
+
+		wp_set_current_user( self::$user );
+		$request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/users/%d', $user_id ) );
+		$request->set_param( 'roles', array( 'editor' ) );
+		$response = $this->server->dispatch( $request );
+		$this->assertEquals( 200, $response->get_status() );
+
+		$new_data = $response->get_data();
+		$this->assertEquals( 'editor', $new_data['roles'][0] );
+	}
+
+	/**
+	 * @ticket 40263
+	 */
+	public function test_update_item_including_roles_and_other_params() {
+		$user_id = $this->factory->user->create( array(
+			'role' => 'author',
+		) );
+
+		wp_set_current_user( self::$user );
+		$request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/users/%d', $user_id ) );
+		$request->set_param( 'roles', array( 'editor' ) );
+		$request->set_param( 'name', 'Short-Lived User' );
+		$response = $this->server->dispatch( $request );
+
+		if ( is_multisite() ) {
+			// Site administrators can promote users, as verified by the
+			// previous test, but they cannot perform other user-editing
+			// operations.  This also tests the branch of logic that verifies
+			// that no parameters other than 'id' and 'roles' are specified for
+			// a roles update.
+			$this->assertErrorResponse( 'rest_cannot_edit', $response, 403 );
+		} else {
+			$this->assertEquals( 200, $response->get_status() );
+
+			$new_data = $response->get_data();
+			$this->assertEquals( 'editor', $new_data['roles'][0] );
+		}
+	}
+
 	public function test_update_item_invalid_password() {
 		$this->allow_user_to_manage_multisite();
 		wp_set_current_user( self::$user );
