diff --git a/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php index 1a52e3e0fb..6d2f3e650f 100644 --- a/src/wp-admin/includes/ajax-actions.php +++ b/src/wp-admin/includes/ajax-actions.php @@ -4344,7 +4344,7 @@ function wp_ajax_wp_privacy_export_personal_data() { wp_send_json_error( __( 'Invalid request ID.' ) ); } - if ( ! current_user_can( 'manage_options' ) ) { + if ( ! current_user_can( 'export_others_personal_data' ) ) { wp_send_json_error( __( 'Invalid request.' ) ); } @@ -4522,7 +4522,8 @@ function wp_ajax_wp_privacy_erase_personal_data() { wp_send_json_error( __( 'Invalid request ID.' ) ); } - if ( ! current_user_can( 'delete_users' ) ) { + // Both capabilities are required to avoid confusion, see `_wp_personal_data_removal_page()`. + if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) { wp_send_json_error( __( 'Invalid request.' ) ); } diff --git a/src/wp-admin/includes/user.php b/src/wp-admin/includes/user.php index e242322e35..939ad167bd 100644 --- a/src/wp-admin/includes/user.php +++ b/src/wp-admin/includes/user.php @@ -785,8 +785,8 @@ function _wp_personal_data_cleanup_requests() { * @access private */ function _wp_personal_data_export_page() { - if ( ! current_user_can( 'manage_options' ) ) { - wp_die( esc_html__( 'Sorry, you are not allowed to manage privacy on this site.' ) ); + if ( ! current_user_can( 'export_others_personal_data' ) ) { + wp_die( __( 'Sorry, you are not allowed to export personal data on this site.' ) ); } _wp_personal_data_handle_actions(); @@ -850,8 +850,14 @@ function _wp_personal_data_export_page() { * @access private */ function _wp_personal_data_removal_page() { - if ( ! current_user_can( 'delete_users' ) ) { - wp_die( esc_html__( 'Sorry, you are not allowed to manage privacy on this site.' ) ); + /* + * Require both caps in order to make it explicitly clear that delegating + * erasure from network admins to single-site admins will give them the + * ability to affect global users, rather than being limited to the site + * that they administer. + */ + if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) { + wp_die( __( 'Sorry, you are not allowed to erase data on this site.' ) ); } _wp_personal_data_handle_actions(); @@ -917,8 +923,8 @@ function _wp_personal_data_removal_page() { * @access private */ function _wp_privacy_hook_requests_page() { - add_submenu_page( 'tools.php', __( 'Export Personal Data' ), __( 'Export Personal Data' ), 'manage_options', 'export_personal_data', '_wp_personal_data_export_page' ); - add_submenu_page( 'tools.php', __( 'Remove Personal Data' ), __( 'Remove Personal Data' ), 'manage_options', 'remove_personal_data', '_wp_personal_data_removal_page' ); + add_submenu_page( 'tools.php', __( 'Export Personal Data' ), __( 'Export Personal Data' ), 'export_others_personal_data', 'export_personal_data', '_wp_personal_data_export_page' ); + add_submenu_page( 'tools.php', __( 'Remove Personal Data' ), __( 'Remove Personal Data' ), 'erase_others_personal_data', 'remove_personal_data', '_wp_personal_data_removal_page' ); } // TODO: move the following classes in new files. diff --git a/src/wp-includes/capabilities.php b/src/wp-includes/capabilities.php index a81761db15..efc3dd2e4c 100644 --- a/src/wp-includes/capabilities.php +++ b/src/wp-includes/capabilities.php @@ -555,6 +555,10 @@ function map_meta_cap( $cap, $user_id ) { $caps[] = 'update_core'; } break; + case 'export_others_personal_data': + case 'erase_others_personal_data': + $caps[] = is_multisite() ? 'manage_network' : 'manage_options'; + break; default: // Handle meta capabilities for custom post types. global $post_type_meta_caps; diff --git a/tests/phpunit/tests/user/capabilities.php b/tests/phpunit/tests/user/capabilities.php index 85c5d995d3..7b086d1384 100644 --- a/tests/phpunit/tests/user/capabilities.php +++ b/tests/phpunit/tests/user/capabilities.php @@ -237,6 +237,8 @@ class Tests_User_Capabilities extends WP_UnitTestCase { 'update_languages' => array( 'administrator' ), 'deactivate_plugins' => array( 'administrator' ), 'upgrade_php' => array( 'administrator' ), + 'export_others_personal_data' => array( 'administrator' ), + 'erase_others_personal_data' => array( 'administrator' ), 'edit_categories' => array( 'administrator', 'editor' ), 'delete_categories' => array( 'administrator', 'editor' ), @@ -269,6 +271,8 @@ class Tests_User_Capabilities extends WP_UnitTestCase { 'update_languages' => array(), 'deactivate_plugins' => array(), 'upgrade_php' => array(), + 'export_others_personal_data' => array( '' ), + 'erase_others_personal_data' => array( '' ), 'customize' => array( 'administrator' ), 'delete_site' => array( 'administrator' ),