Skip to content

Commit 0c0719e

Browse files
committed
Fix bug in the permission check for ability in REST API run controller
1 parent e23c381 commit 0c0719e

File tree

1 file changed

+53
-49
lines changed

1 file changed

+53
-49
lines changed

src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ public function register_routes(): void {
6262
// This was the same issue that we ended up seeing with the Feature API.
6363
array(
6464
'methods' => WP_REST_Server::ALLMETHODS,
65-
'callback' => array( $this, 'run_ability_with_method_check' ),
66-
'permission_callback' => array( $this, 'run_ability_permissions_check' ),
65+
'callback' => array( $this, 'execute_ability' ),
66+
'permission_callback' => array( $this, 'check_ability_permissions' ),
6767
'args' => $this->get_run_args(),
6868
),
6969
'schema' => array( $this, 'get_run_schema' ),
@@ -72,16 +72,15 @@ public function register_routes(): void {
7272
}
7373

7474
/**
75-
* Executes an ability with HTTP method validation.
75+
* Executes an ability.
7676
*
7777
* @since 6.9.0
7878
*
7979
* @param WP_REST_Request<array<string, mixed>> $request Full details about the request.
8080
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
8181
*/
82-
public function run_ability_with_method_check( $request ) {
82+
public function execute_ability( $request ) {
8383
$ability = wp_get_ability( $request->get_param( 'name' ) );
84-
8584
if ( ! $ability ) {
8685
return new WP_Error(
8786
'rest_ability_not_found',
@@ -90,60 +89,50 @@ public function run_ability_with_method_check( $request ) {
9089
);
9190
}
9291

93-
// Check if the HTTP method matches the ability annotations.
94-
$annotations = $ability->get_meta_item( 'annotations' );
95-
$expected_method = 'POST';
96-
if ( ! empty( $annotations['readonly'] ) ) {
97-
$expected_method = 'GET';
98-
} elseif ( ! empty( $annotations['destructive'] ) && ! empty( $annotations['idempotent'] ) ) {
99-
$expected_method = 'DELETE';
100-
}
101-
102-
if ( $expected_method !== $request->get_method() ) {
103-
$error_message = __( 'Abilities that perform updates require POST method.' );
104-
if ( 'GET' === $expected_method ) {
105-
$error_message = __( 'Read-only abilities require GET method.' );
106-
} elseif ( 'DELETE' === $expected_method ) {
107-
$error_message = __( 'Abilities that perform destructive actions require DELETE method.' );
92+
$input = $this->get_input_from_request( $request );
93+
$result = $ability->execute( $input );
94+
if ( is_wp_error( $result ) ) {
95+
if ( 'ability_invalid_input' === $result->get_error_code() ) {
96+
$result->add_data( array( 'status' => 400 ) );
10897
}
109-
return new WP_Error(
110-
'rest_ability_invalid_method',
111-
$error_message,
112-
array( 'status' => 405 )
113-
);
98+
return $result;
11499
}
115100

116-
return $this->run_ability( $request );
101+
return rest_ensure_response( $result );
117102
}
118103

119104
/**
120-
* Executes an ability.
105+
* Validates if the HTTP method matches the expected method for the ability based on its annotations.
121106
*
122107
* @since 6.9.0
123108
*
124-
* @param WP_REST_Request<array<string, mixed>> $request Full details about the request.
125-
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
109+
* @param string $request_method The HTTP method of the request.
110+
* @param array<string, (null|bool)> $annotations The ability annotations.
111+
* @return true|WP_Error True on success, or WP_Error object on failure.
126112
*/
127-
public function run_ability( $request ) {
128-
$ability = wp_get_ability( $request->get_param( 'name' ) );
129-
if ( ! $ability ) {
130-
return new WP_Error(
131-
'rest_ability_not_found',
132-
__( 'Ability not found.' ),
133-
array( 'status' => 404 )
134-
);
113+
public function validate_request_method( string $request_method, array $annotations ) {
114+
$expected_method = 'POST';
115+
if ( ! empty( $annotations['readonly'] ) ) {
116+
$expected_method = 'GET';
117+
} elseif ( ! empty( $annotations['destructive'] ) && ! empty( $annotations['idempotent'] ) ) {
118+
$expected_method = 'DELETE';
135119
}
136120

137-
$input = $this->get_input_from_request( $request );
138-
$result = $ability->execute( $input );
139-
if ( is_wp_error( $result ) ) {
140-
if ( 'ability_invalid_input' === $result->get_error_code() ) {
141-
$result->add_data( array( 'status' => 400 ) );
142-
}
143-
return $result;
121+
if ( $expected_method === $request_method ) {
122+
return true;
144123
}
145124

146-
return rest_ensure_response( $result );
125+
$error_message = __( 'Abilities that perform updates require POST method.' );
126+
if ( 'GET' === $expected_method ) {
127+
$error_message = __( 'Read-only abilities require GET method.' );
128+
} elseif ( 'DELETE' === $expected_method ) {
129+
$error_message = __( 'Abilities that perform destructive actions require DELETE method.' );
130+
}
131+
return new WP_Error(
132+
'rest_ability_invalid_method',
133+
$error_message,
134+
array( 'status' => 405 )
135+
);
147136
}
148137

149138
/**
@@ -154,7 +143,7 @@ public function run_ability( $request ) {
154143
* @param WP_REST_Request<array<string, mixed>> $request Full details about the request.
155144
* @return true|WP_Error True if the request has execution permission, WP_Error object otherwise.
156145
*/
157-
public function run_ability_permissions_check( $request ) {
146+
public function check_ability_permissions( $request ) {
158147
$ability = wp_get_ability( $request->get_param( 'name' ) );
159148
if ( ! $ability || ! $ability->get_meta_item( 'show_in_rest' ) ) {
160149
return new WP_Error(
@@ -164,8 +153,23 @@ public function run_ability_permissions_check( $request ) {
164153
);
165154
}
166155

167-
$input = $this->get_input_from_request( $request );
168-
if ( ! $ability->check_permissions( $input ) ) {
156+
$is_valid = $this->validate_request_method(
157+
$request->get_method(),
158+
$ability->get_meta_item( 'annotations' )
159+
);
160+
if ( is_wp_error( $is_valid ) ) {
161+
return $is_valid;
162+
}
163+
164+
$input = $this->get_input_from_request( $request );
165+
$result = $ability->check_permissions( $input );
166+
if ( is_wp_error( $result ) ) {
167+
if ( 'ability_invalid_input' === $result->get_error_code() ) {
168+
$result->add_data( array( 'status' => 400 ) );
169+
}
170+
return $result;
171+
}
172+
if ( ! $result ) {
169173
return new WP_Error(
170174
'rest_ability_cannot_execute',
171175
__( 'Sorry, you are not allowed to execute this ability.' ),
@@ -185,7 +189,7 @@ public function run_ability_permissions_check( $request ) {
185189
* @return mixed|null The input parameters.
186190
*/
187191
private function get_input_from_request( $request ) {
188-
if ( in_array( $request->get_method(), array( 'GET', 'DELETE' ) ) ) {
192+
if ( in_array( $request->get_method(), array( 'GET', 'DELETE' ), true ) ) {
189193
// For GET and DELETE requests, look for 'input' query parameter.
190194
$query_params = $request->get_query_params();
191195
return $query_params['input'] ?? null;

0 commit comments

Comments
 (0)