From c8f974f2f5c5f3de670703d49547f0976dc343ab Mon Sep 17 00:00:00 2001 From: Timothy Jacobs Date: Sun, 31 Jan 2021 19:02:30 +0000 Subject: [PATCH] App Passwords: Introduce fine grained capabilities. Previously, all permission checks for using app passwords were implemented using `edit_user`. This commit introduces a series of more fine grained meta capabilities that should be used instead: `create_app_password`, `list_app_passwords`, `read_app_password`, `edit_app_password`, `delete_app_password` and `delete_app_passwords`. These capabilities all map to `edit_user` by default, but may now be customized by developers. Props johnbillion, TimothyBlynJacobs. Fixes #51703. git-svn-id: https://develop.svn.wordpress.org/trunk@50114 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/capabilities.php | 8 ++ ...-rest-application-passwords-controller.php | 99 +++++++++++++++++-- .../rest-application-passwords-controller.php | 12 +-- tests/phpunit/tests/user/capabilities.php | 8 +- 4 files changed, 114 insertions(+), 13 deletions(-) diff --git a/src/wp-includes/capabilities.php b/src/wp-includes/capabilities.php index 22eff2b1e3..26c09a1944 100644 --- a/src/wp-includes/capabilities.php +++ b/src/wp-includes/capabilities.php @@ -592,6 +592,14 @@ function map_meta_cap( $cap, $user_id, ...$args ) { case 'manage_privacy_options': $caps[] = is_multisite() ? 'manage_network' : 'manage_options'; break; + case 'create_app_password': + case 'list_app_passwords': + case 'read_app_password': + case 'edit_app_password': + case 'delete_app_passwords': + case 'delete_app_password': + $caps = map_meta_cap( 'edit_user', $user_id, $args[0] ); + break; default: // Handle meta capabilities for custom post types. global $post_type_meta_caps; diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php index c3474df09a..84fa144800 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php @@ -110,7 +110,21 @@ class WP_REST_Application_Passwords_Controller extends WP_REST_Controller { * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { - return $this->do_permissions_check( $request ); + $user = $this->get_user( $request ); + + if ( is_wp_error( $user ) ) { + 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; } /** @@ -149,7 +163,21 @@ class WP_REST_Application_Passwords_Controller extends WP_REST_Controller { * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. */ public function get_item_permissions_check( $request ) { - return $this->do_permissions_check( $request ); + $user = $this->get_user( $request ); + + if ( is_wp_error( $user ) ) { + 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; } /** @@ -179,7 +207,21 @@ class WP_REST_Application_Passwords_Controller extends WP_REST_Controller { * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. */ public function create_item_permissions_check( $request ) { - return $this->do_permissions_check( $request ); + $user = $this->get_user( $request ); + + if ( is_wp_error( $user ) ) { + 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; } /** @@ -248,7 +290,21 @@ class WP_REST_Application_Passwords_Controller extends WP_REST_Controller { * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. */ public function update_item_permissions_check( $request ) { - return $this->do_permissions_check( $request ); + $user = $this->get_user( $request ); + + if ( is_wp_error( $user ) ) { + 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; } /** @@ -308,7 +364,21 @@ class WP_REST_Application_Passwords_Controller extends WP_REST_Controller { * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. */ public function delete_items_permissions_check( $request ) { - return $this->do_permissions_check( $request ); + $user = $this->get_user( $request ); + + if ( is_wp_error( $user ) ) { + 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; } /** @@ -349,7 +419,21 @@ class WP_REST_Application_Passwords_Controller extends WP_REST_Controller { * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. */ public function delete_item_permissions_check( $request ) { - return $this->do_permissions_check( $request ); + $user = $this->get_user( $request ); + + if ( is_wp_error( $user ) ) { + 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; } /** @@ -457,11 +541,14 @@ class WP_REST_Application_Passwords_Controller extends WP_REST_Controller { * Performs a permissions check for the request. * * @since 5.6.0 + * @deprecated 5.7.0 Use `edit_user` directly or one of the specific meta capabilities introduced in 5.7.0. * * @param WP_REST_Request $request * @return true|WP_Error */ protected function do_permissions_check( $request ) { + _deprecated_function( __METHOD__, '5.7.0' ); + $user = $this->get_user( $request ); if ( is_wp_error( $user ) ) { diff --git a/tests/phpunit/tests/rest-api/rest-application-passwords-controller.php b/tests/phpunit/tests/rest-api/rest-application-passwords-controller.php index a7f257da9b..b55c8e082d 100644 --- a/tests/phpunit/tests/rest-api/rest-application-passwords-controller.php +++ b/tests/phpunit/tests/rest-api/rest-application-passwords-controller.php @@ -190,7 +190,7 @@ class WP_Test_REST_Application_Passwords_Controller extends WP_Test_REST_Control wp_set_current_user( self::$subscriber_id ); $response = rest_do_request( sprintf( '/wp/v2/users/%d/application-passwords', self::$admin ) ); - $this->assertErrorResponse( 'rest_cannot_manage_application_passwords', $response, 403 ); + $this->assertErrorResponse( 'rest_cannot_list_application_passwords', $response, 403 ); } /** @@ -272,7 +272,7 @@ class WP_Test_REST_Application_Passwords_Controller extends WP_Test_REST_Control $uuid = $item['uuid']; $response = rest_do_request( sprintf( '/wp/v2/users/%d/application-passwords/%s', self::$admin, $uuid ) ); - $this->assertErrorResponse( 'rest_cannot_manage_application_passwords', $response, 403 ); + $this->assertErrorResponse( 'rest_cannot_read_application_password', $response, 403 ); } /** @@ -394,7 +394,7 @@ class WP_Test_REST_Application_Passwords_Controller extends WP_Test_REST_Control $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/users/%d/application-passwords', self::$admin ) ); $request->set_body_params( array( 'name' => 'App' ) ); $response = rest_do_request( $request ); - $this->assertErrorResponse( 'rest_cannot_manage_application_passwords', $response, 403 ); + $this->assertErrorResponse( 'rest_cannot_create_application_passwords', $response, 403 ); } /** @@ -500,7 +500,7 @@ class WP_Test_REST_Application_Passwords_Controller extends WP_Test_REST_Control $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/users/%d/application-passwords/%s', self::$admin, $uuid ) ); $request->set_body_params( array( 'name' => 'New App' ) ); $response = rest_do_request( $request ); - $this->assertErrorResponse( 'rest_cannot_manage_application_passwords', $response, 403 ); + $this->assertErrorResponse( 'rest_cannot_edit_application_password', $response, 403 ); } /** @@ -643,7 +643,7 @@ class WP_Test_REST_Application_Passwords_Controller extends WP_Test_REST_Control $uuid = $item['uuid']; $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/users/%d/application-passwords/%s', self::$admin, $uuid ) ); $response = rest_do_request( $request ); - $this->assertErrorResponse( 'rest_cannot_manage_application_passwords', $response, 403 ); + $this->assertErrorResponse( 'rest_cannot_delete_application_password', $response, 403 ); } /** @@ -747,7 +747,7 @@ class WP_Test_REST_Application_Passwords_Controller extends WP_Test_REST_Control $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/users/%d/application-passwords', self::$admin ) ); $response = rest_do_request( $request ); - $this->assertErrorResponse( 'rest_cannot_manage_application_passwords', $response, 403 ); + $this->assertErrorResponse( 'rest_cannot_delete_application_passwords', $response, 403 ); } /** diff --git a/tests/phpunit/tests/user/capabilities.php b/tests/phpunit/tests/user/capabilities.php index 55394e7c52..1675a34047 100644 --- a/tests/phpunit/tests/user/capabilities.php +++ b/tests/phpunit/tests/user/capabilities.php @@ -522,7 +522,13 @@ class Tests_User_Capabilities extends WP_UnitTestCase { $expected['delete_user'], $expected['edit_user_meta'], $expected['delete_user_meta'], - $expected['add_user_meta'] + $expected['add_user_meta'], + $expected['create_app_password'], + $expected['list_app_passwords'], + $expected['read_app_password'], + $expected['edit_app_password'], + $expected['delete_app_passwords'], + $expected['delete_app_password'] ); $expected = array_keys( $expected );