diff --git a/wp-includes/rest-api/class-wp-rest-user-utilities.php b/wp-includes/rest-api/class-wp-rest-user-utilities.php new file mode 100644 index 000000000000..a3f6ccef055c --- /dev/null +++ b/wp-includes/rest-api/class-wp-rest-user-utilities.php @@ -0,0 +1,151 @@ + 404 ) + ); + + if ( (int) $user_id <= 0 ) { + return $error; + } + + $user = get_userdata( (int) $user_id ); + + if ( empty( $user ) || ! $user->exists() ) { + return $error; + } + + if ( ! self::check_multisite_membership( $user, $allow_super_admin_bypass ) ) { + return $error; + } + + return $user; + } + + /** + * Resolves the current user for "me" endpoint requests. + * + * Returns the current authenticated user, or a WP_Error if the + * request is not authenticated. + * + * @since 6.8.0 + * + * @return WP_User|WP_Error WP_User on success, WP_Error if not logged in. + */ + public static function resolve_current_user() { + if ( ! is_user_logged_in() ) { + return new WP_Error( + 'rest_not_logged_in', + __( 'You are not currently logged in.' ), + array( 'status' => 401 ) + ); + } + + return wp_get_current_user(); + } + + /** + * Checks whether a user is a member of the current blog on multisite. + * + * On single-site installations, always returns true. On multisite, + * optionally allows super administrators to bypass the membership check. + * + * @since 6.8.0 + * + * @param WP_User $user The user to check. + * @param bool $allow_super_admin_bypass Optional. Whether super admins + * bypass the membership check. + * Default false. + * @return bool True if the user passes the membership check. + */ + public static function check_multisite_membership( $user, $allow_super_admin_bypass = false ) { + if ( ! is_multisite() ) { + return true; + } + + if ( $allow_super_admin_bypass && user_can( $user->ID, 'manage_sites' ) ) { + return true; + } + + return is_user_member_of_blog( $user->ID ); + } + + /** + * Checks whether the current user has a given capability, returning + * a WP_Error if the check fails. + * + * Convenience wrapper around current_user_can() that builds the + * appropriate WP_Error response for REST API permission callbacks. + * + * @since 6.8.0 + * + * @param string $capability The capability to check. + * @param string $error_code WP_Error code to use on failure. + * @param string $message Human-readable error message. + * @param mixed ...$args Optional. Additional arguments passed to current_user_can(). + * @return true|WP_Error True if the user has the capability, WP_Error otherwise. + */ + public static function check_permission( $capability, $error_code, $message, ...$args ) { + if ( current_user_can( $capability, ...$args ) ) { + return true; + } + + return new WP_Error( + $error_code, + $message, + array( 'status' => rest_authorization_required_code() ) + ); + } + + /** + * Checks whether the current user has a specific capability. + * + * A thin wrapper around current_user_can() for use in REST API + * permission callbacks that return boolean values rather than + * WP_Error objects. + * + * @since 6.8.0 + * + * @param string $capability The capability to check. + * @param mixed ...$args Optional. Additional arguments passed to current_user_can(). + * @return bool True if the current user has the capability. + */ + public static function has_capability( $capability, ...$args ) { + return current_user_can( $capability, ...$args ); + } +} diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php index 767917d6f6fd..5f68eab616c2 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php @@ -116,15 +116,12 @@ public function get_items_permissions_check( $request ) { return $user; } - if ( ! current_user_can( 'list_app_passwords', $user->ID ) ) { - return new WP_Error( - 'rest_cannot_list_application_passwords', - __( 'Sorry, you are not allowed to list application passwords for this user.' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - return true; + return WP_REST_User_Utilities::check_permission( + 'list_app_passwords', + 'rest_cannot_list_application_passwords', + __( 'Sorry, you are not allowed to list application passwords for this user.' ), + $user->ID + ); } /** @@ -169,15 +166,13 @@ public function get_item_permissions_check( $request ) { return $user; } - if ( ! current_user_can( 'read_app_password', $user->ID, $request['uuid'] ) ) { - return new WP_Error( - 'rest_cannot_read_application_password', - __( 'Sorry, you are not allowed to read this application password.' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - return true; + return WP_REST_User_Utilities::check_permission( + 'read_app_password', + 'rest_cannot_read_application_password', + __( 'Sorry, you are not allowed to read this application password.' ), + $user->ID, + $request['uuid'] + ); } /** @@ -213,15 +208,12 @@ public function create_item_permissions_check( $request ) { return $user; } - if ( ! current_user_can( 'create_app_password', $user->ID ) ) { - return new WP_Error( - 'rest_cannot_create_application_passwords', - __( 'Sorry, you are not allowed to create application passwords for this user.' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - return true; + return WP_REST_User_Utilities::check_permission( + 'create_app_password', + 'rest_cannot_create_application_passwords', + __( 'Sorry, you are not allowed to create application passwords for this user.' ), + $user->ID + ); } /** @@ -296,15 +288,13 @@ public function update_item_permissions_check( $request ) { return $user; } - if ( ! current_user_can( 'edit_app_password', $user->ID, $request['uuid'] ) ) { - return new WP_Error( - 'rest_cannot_edit_application_password', - __( 'Sorry, you are not allowed to edit this application password.' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - return true; + return WP_REST_User_Utilities::check_permission( + 'edit_app_password', + 'rest_cannot_edit_application_password', + __( 'Sorry, you are not allowed to edit this application password.' ), + $user->ID, + $request['uuid'] + ); } /** @@ -370,15 +360,12 @@ public function delete_items_permissions_check( $request ) { return $user; } - if ( ! current_user_can( 'delete_app_passwords', $user->ID ) ) { - return new WP_Error( - 'rest_cannot_delete_application_passwords', - __( 'Sorry, you are not allowed to delete application passwords for this user.' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - return true; + return WP_REST_User_Utilities::check_permission( + 'delete_app_passwords', + 'rest_cannot_delete_application_passwords', + __( 'Sorry, you are not allowed to delete application passwords for this user.' ), + $user->ID + ); } /** @@ -425,15 +412,13 @@ public function delete_item_permissions_check( $request ) { return $user; } - if ( ! current_user_can( 'delete_app_password', $user->ID, $request['uuid'] ) ) { - return new WP_Error( - 'rest_cannot_delete_application_password', - __( 'Sorry, you are not allowed to delete this application password.' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - return true; + return WP_REST_User_Utilities::check_permission( + 'delete_app_password', + 'rest_cannot_delete_application_password', + __( 'Sorry, you are not allowed to delete this application password.' ), + $user->ID, + $request['uuid'] + ); } /** @@ -687,40 +672,29 @@ protected function get_user( $request ) { ); } - $error = new WP_Error( - 'rest_user_invalid_id', - __( 'Invalid user ID.' ), - array( 'status' => 404 ) - ); - $id = $request['user_id']; if ( 'me' === $id ) { - if ( ! is_user_logged_in() ) { + $user = WP_REST_User_Utilities::resolve_current_user(); + + if ( is_wp_error( $user ) ) { + return $user; + } + + // Verify multisite blog membership for the current user. + if ( ! WP_REST_User_Utilities::check_multisite_membership( $user, true ) ) { return new WP_Error( - 'rest_not_logged_in', - __( 'You are not currently logged in.' ), - array( 'status' => 401 ) + 'rest_user_invalid_id', + __( 'Invalid user ID.' ), + array( 'status' => 404 ) ); } - - $user = wp_get_current_user(); } else { - $id = (int) $id; - - if ( $id <= 0 ) { - return $error; - } - - $user = get_userdata( $id ); - } - - if ( empty( $user ) || ! $user->exists() ) { - return $error; + $user = WP_REST_User_Utilities::validate_user_id( $id, true ); } - if ( is_multisite() && ! user_can( $user->ID, 'manage_sites' ) && ! is_user_member_of_blog( $user->ID ) ) { - return $error; + if ( is_wp_error( $user ) ) { + return $user; } if ( ! wp_is_application_passwords_available_for_user( $user ) ) { diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php index 142836c7c892..f7af118a3af8 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php @@ -65,7 +65,7 @@ public function register_routes() { * @return bool True if the request has read access for the item, otherwise false. */ public function get_item_permissions_check( $request ) { - return current_user_can( 'manage_options' ); + return WP_REST_User_Utilities::has_capability( 'manage_options' ); } /** diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php index 9b25cf7974cb..6e153673397c 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php @@ -442,26 +442,7 @@ static function ( $column ) use ( $search_columns_mapping ) { * @return WP_User|WP_Error True if ID is valid, WP_Error otherwise. */ protected function get_user( $id ) { - $error = new WP_Error( - 'rest_user_invalid_id', - __( 'Invalid user ID.' ), - array( 'status' => 404 ) - ); - - if ( (int) $id <= 0 ) { - return $error; - } - - $user = get_userdata( (int) $id ); - if ( empty( $user ) || ! $user->exists() ) { - return $error; - } - - if ( is_multisite() && ! is_user_member_of_blog( $user->ID ) ) { - return $error; - } - - return $user; + return WP_REST_User_Utilities::validate_user_id( $id, false ); } /** @@ -532,17 +513,12 @@ public function get_item( $request ) { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_current_item( $request ) { - $current_user_id = get_current_user_id(); + $user = WP_REST_User_Utilities::resolve_current_user(); - if ( empty( $current_user_id ) ) { - return new WP_Error( - 'rest_not_logged_in', - __( 'You are not currently logged in.' ), - array( 'status' => 401 ) - ); + if ( is_wp_error( $user ) ) { + return $user; } - $user = wp_get_current_user(); $response = $this->prepare_item_for_response( $user, $request ); $response = rest_ensure_response( $response ); @@ -558,16 +534,11 @@ public function get_current_item( $request ) { * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. */ public function create_item_permissions_check( $request ) { - - if ( ! current_user_can( 'create_users' ) ) { - return new WP_Error( - 'rest_cannot_create_user', - __( 'Sorry, you are not allowed to create new users.' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - return true; + return WP_REST_User_Utilities::check_permission( + 'create_users', + 'rest_cannot_create_user', + __( 'Sorry, you are not allowed to create new users.' ) + ); } /** @@ -896,15 +867,12 @@ public function delete_item_permissions_check( $request ) { return $user; } - if ( ! current_user_can( 'delete_user', $user->ID ) ) { - return new WP_Error( - 'rest_user_cannot_delete', - __( 'Sorry, you are not allowed to delete this user.' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - return true; + return WP_REST_User_Utilities::check_permission( + 'delete_user', + 'rest_user_cannot_delete', + __( 'Sorry, you are not allowed to delete this user.' ), + $user->ID + ); } /** diff --git a/wp-settings.php b/wp-settings.php index 60c220100f53..5917625fd605 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -304,6 +304,7 @@ require ABSPATH . WPINC . '/rest-api/class-wp-rest-response.php'; require ABSPATH . WPINC . '/rest-api/class-wp-rest-request.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-controller.php'; +require ABSPATH . WPINC . '/rest-api/class-wp-rest-user-utilities.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-posts-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-attachments-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-global-styles-controller.php';