Opened 8 years ago
Last modified 7 days ago
#41604 reopened defect (bug)
REST API: Attempting to create or update a non-existent setting doesn't return an error response
| Reported by: |
|
Owned by: |
|
|---|---|---|---|
| Milestone: | 7.0 | Priority: | normal |
| Severity: | normal | Version: | |
| Component: | Options, Meta APIs | Keywords: | needs-patch needs-unit-tests |
| Focuses: | rest-api | Cc: |
Description
Attempting to create a new setting via a POST request to /wp/v2/settings returns a 200 response, despite the request having been invalid. The same goes for attempting to update a non-existent setting with a PUT request.
I would have expected maybe a 400 from such requests.
The internal logic in WP_REST_Settings_Controller routes the request through the update_item() method, treats it as a setting update, and silently ignores the problem.
Attachments (1)
Change History (44)
This ticket was mentioned in PR #8914 on WordPress/wordpress-develop by @sheldorofazeroth.
6 months ago
#2
- Keywords has-patch added; needs-patch removed
Trac: https://core.trac.wordpress.org/ticket/41604
Problem
The /wp/v2/settings endpoint currently accepts POST requests with empty bodies or invalid parameters and incorrectly returns the site settings instead of proper error responses.
Current behavior (incorrect):
POST /wp/v2/settings with empty body → Returns settings (should be 400)
POST /wp/v2/settings with {"invalid_param": "value"} → Returns settings (should be 400)
POST /wp/v2/settings with {"title": "New", "invalid_param": "value"} → Returns settings (should be 400)
Root Cause
The WordPress REST API args validation system only validates known parameters defined in the schema. When no settings are registered with show_in_rest => true, or when unknown parameters are provided, the validation framework doesn't reject the request, allowing the update_item() callback to execute and return settings data.
Solution
Added parameter validation in the update_item() method of WP_REST_Settings_Controller to:
Reject empty request bodies - POST/PUT/PATCH requests must contain parameters
Reject invalid parameters - Only allow parameters that correspond to registered settings
Return appropriate HTTP 400 errors with descriptive messages
Expected Behavior After Fix
- Proper error responses:
- POST /wp/v2/settings with {} → 400: "Request body cannot be empty."
- POST /wp/v2/settings with {"invalid_param": "value"} → 400: "Invalid parameter(s) provided."
- POST /wp/v2/settings with {"title": "New", "invalid_param": "value"} → 400: "Invalid parameter(s) provided."
- Valid requests still work:
- POST /wp/v2/settings with {"title": "New Title"} → Updates setting and returns updated settings
#3
in reply to:
↑ description
@
6 months ago
Replying to johnbillion:
Attempting to create a new setting via a POST request to
/wp/v2/settingsreturns a 200 response, despite the request having been invalid. The same goes for attempting to update a non-existent setting with a PUT request.
I would have expected maybe a 400 from such requests.
The internal logic in
WP_REST_Settings_Controllerroutes the request through theupdate_item()method, treats it as a setting update, and silently ignores the problem.
This same issue persists when an empty body is sent in the update request. The PR I raised addresses all the cases in which the settings API doesn't return the expected response. For more details on the issue and the steps to reproduce it, please go through the description of the pull request.
#4
@
6 months ago
- Keywords needs-unit-tests added
- Milestone changed from Awaiting Review to Future Release
This is looking good, but we'll need tests for this change too. Is that something you can look at @sheldorofazeroth? A good starting point would be the existing tests in tests/phpunit/tests/rest-api.
#5
@
6 months ago
- Keywords has-unit-tests added; needs-unit-tests removed
- Milestone changed from Future Release to 6.9
#6
@
6 months ago
- Owner set to johnbillion
- Resolution set to fixed
- Status changed from new to closed
In 60301:
#7
@
6 months ago
- Resolution fixed deleted
- Status changed from closed to reopened
The Gutenberg e2e tests discovered a regression after r60301.
The requests can contain global query parameters, such as _locale, which fail to pass ! empty( array_diff_key( $params, $options ) ) check:
An actual request made by the block editor to update settings: http://trunk.test/wp-json/wp/v2/settings?_locale=user.
This ticket was mentioned in Slack in #core-editor by mamaduka. View the logs.
6 months ago
#9
@
6 months ago
Failing workflow run: https://github.com/WordPress/gutenberg/actions/runs/15599732038
This ticket was mentioned in PR #8967 on WordPress/wordpress-develop by @johnbillion.
6 months ago
#10
Trac ticket: Core-41604
#11
@
6 months ago
- Keywords needs-patch added; has-patch removed
PR here that adds a failing test: https://github.com/WordPress/wordpress-develop/pull/8967
#12
@
6 months ago
@Mamaduka @sheldorofazeroth The _locale URL query parameter appears to be a special case handled by determine_locale(). Are there other parameters or cases you think could break with this change?
#13
@
6 months ago
@johnbillion @Mamaduka I think it would be best if there is a small change to the code. Instead of using
$request->get_params(); I can use $request->get_body_params(). This will only consider the POST request body and not the URL parameters. Let me know your thoughts on this. I'll make the change once I hear back from you.
#14
@
6 months ago
I think that makes sense. Let's give it a try. You can use my tests from https://github.com/WordPress/wordpress-develop/pull/8967 and add any more that you see fit.
This ticket was mentioned in PR #8975 on WordPress/wordpress-develop by @sheldorofazeroth.
6 months ago
#15
- Keywords has-patch added; needs-patch removed
Trac ticket: https://core.trac.wordpress.org/ticket/41604
This PR fixes the issue pointed out by @Mamaduka i.e., the API can contain global query parameters, such as _locale, which fail to pass. This is fixed by replacing the $request->get_params(); by $request->get_body_params()
#17
@
6 months ago
I think $request->get_body_params() only works if the request is sent in form format. If the request is sent in JSON format (application/json), the parameters cannot be retrieved correctly.
I think the endpoint needs to guarantee that it will work no matter which format the request is sent in.
Theoretically, the following approach is possible, but it might not be suitable:
$params = array_merge(
$request->get_body_params(),
$request->get_json_params()
);
#18
@
6 months ago
@wildworks Due to this issue, I used $request->get_params(); but as it also picked the URL parameters, therefore I changed it to $request->get_body_params().
I agree with your point. I think the below code can fix this:
$params = $request->get_params(); $url_params = $request->get_url_params(); $query_params = $request->get_query_params(); $params = array_diff_key( $params, $url_params, $query_params );
@wildworks @johnbillion let me know what you think? I'll update my PR accordingly.
#19
@
6 months ago
@sheldorofazeroth Feel free to try things out in your PR, you don't need to ask each time :)
Don't forget there's also the tests from https://github.com/WordPress/wordpress-develop/pull/8967 that should go in, plus any others you think are appropriate to add.
This ticket was mentioned in Slack in #core-editor by wildworks. View the logs.
6 months ago
#23
in reply to:
↑ 20
@
6 months ago
@sheldorofazeroth could you check the following points?
- The Settings API doesn't accept URL parameters, so I believe
$request->get_url_params()can be removed. - Let's add unit tests. See https://core.trac.wordpress.org/ticket/41604?replyto=20#comment:19
#24
in reply to:
↑ 20
;
follow-up:
↓ 25
@
6 months ago
@wildworks if $request->get_url_params() then the case about gutenberg pointed out by @Mamaduka will not be covered. Here is the issue @Mamaduka pointed out https://core.trac.wordpress.org/ticket/41604?#comment:7. @wildworks Let me know what you think.
#25
in reply to:
↑ 24
@
6 months ago
Replying to sheldorofazeroth:
Let's review the definitions of words and the roles of methods. For example, suppose we receive the following request:
/wp/v2/posts/123/?param=value
This is my understanding, but please correct me if I'm wrong.
- URL Parameter: The
123in the URL corresponds to this. This can be obtained using theget_url_paramsmethod. - Query Parameter: The
param=valuein the URL corresponds to this. This can be obtained using theget_query_paramsmethod.
Since the Settings endpoint does not accept the URL parameter, the get_url_params() method should not be necessary.
#30
@
6 months ago
I tested the latest patch (PR 8975) and it works fine.
- Create a new post.
- Insert a Site logo, Site Tagline, and Site Logo block.
- Make changes to the blocks.
- Save the post.
- Before: 400 error occurred.
- After: The post is saved successfully and the settings are updated correctly.
@johnbillion @Mamaduka I think this patch can be shipped, what are your thoughts on this? Or is there a different approach?
#31
@
6 months ago
Can't think of a different approach at the moment. If the unit tests and e2e tests are passing, then the latest patch appears to be a good option to land.
P.S. I assumed that the schema handled the individual parameter validation, but that doesn't seem to be the case here. cc @TimothyBlynJacobs
#33
@
3 weeks ago
- Resolution fixed deleted
- Status changed from closed to reopened
This looks like a breaking change to me.
I was just testing 6.9 RC 3 with the Web Stories plugin and noticed that I can no longer update settings. The plugin updates settings with a POST request to wp-json/wp//settings/?some_setting_field=<newvalue> and an empty body.
This works fine in 6.8 and below. But with 6.9 I now get the Request body cannot be empty. HTTP 400 error.
Not saying that updating settings via GET instead of POST is correct, but I'm sure it's not the only plugin doing this.
Reopening for visibility.
#34
@
2 weeks ago
- Keywords needs-patch needs-unit-tests added; has-patch has-unit-tests removed
@swissspidy This is being treated as an "empty" request because the new logic checks for validity by fetching all parameters that are not present in the URL (array_diff_key( $request->get_params(), $request->get_query_params() )).
Can we handle this as a valid request while also detecting an empty body?
#35
@
2 weeks ago
I'm going to revert this for 6.9 as we don't have a patch for handling settings value updates via URL query parameters instead of body data.
This appears to be an oddity of the WP REST API that wasn't considered. WP_REST_Request::get_params() reads parameter values from both body data and URL query parameters, meaning the latter can be used in place of the former. So while updating data via a POST but passing data in via URL query parameters does seem strange, it's fully supported. This change needs to account for that usage.
#37
@
2 weeks ago
- Keywords dev-feedback added; needs-patch needs-unit-tests removed
Requesting approval for backport to the 6.9 branch.
#39
@
2 weeks ago
Requesting approval for backport to the 6.9 branch.
This seems like the best option for now.
Let's leave this ticket open, change the milestone to 7.0, and revisit the ideal approach after we commit to the 6.9 branch.
The test in 41604.test.patch demonstrates the issue.