Repository navigation
Honor REST access controls for protected block content - #118
Conversation
ingeniumed
left a comment
There was a problem hiding this comment.
I have two changes I think this PR should make, plus one access-control boundary worth clarifying:
- Denied, password-protected, and missing posts currently return 400 rest_invalid_param. The access check should run in the route’s permission callback so it can return an appropriate 401/403 or 404. Tests should confirm that these error responses contain no protected content.
- The new call to a post type’s REST controller should handle an exception during controller construction or
get_item_permissions_check(). It should fail closed with a generic error and never continue to parse the post. A throwing test controller would verify this. - Does this endpoint intentionally return raw post_content when a custom controller permits the item but removes content from its REST response? Response redaction does not change the stored post content that this plugin parses. The password check handles WordPress’s known case, and I have not found a custom controller here that does the same thing. It would help to state
the intended behavior so the PR does not imply that checking item permissions also honors response redaction.
A REST test through a public post referencing an inaccessible synced pattern would also verify that the pattern’s content cannot appear in an otherwise readable parent response.
| * @param int $post_id The queried post ID. | ||
| */ | ||
| return apply_filters( 'vip_block_data_api__rest_validate_post_id', $is_valid, $post_id ); | ||
| $is_allowed = apply_filters( 'vip_block_data_api__rest_validate_post_id', $is_readable, $post_id ); |
There was a problem hiding this comment.
This is a breaking change for those customers that depended on this behaviour.
This should be noted in a breaking changes section.
There was a problem hiding this comment.
@maxschmeling I think we should keep this behavior the same as before if possible. The vip_block_data_api__rest_validate_post_id filter existed to give developers an option to expose a post that our default logic blocked. If a developer wants to use special logic to expose a post in their own code using special rules (e.g. meta capabilities, auth token) I don't think we should block it.
There was a problem hiding this comment.
Isn't this is the code path to implementing preview functionality via this API?
There was a problem hiding this comment.
Max rolled this back in 99cbf95, so the filter can still override default as I think it should work.
| return apply_filters( 'vip_block_data_api__rest_validate_post_id', $is_valid, $post_id ); | ||
| $is_allowed = apply_filters( 'vip_block_data_api__rest_validate_post_id', $is_readable, $post_id ); | ||
|
|
||
| return $is_readable && $is_allowed; |
There was a problem hiding this comment.
Denied, password-protected, and missing posts all become 400 rest_invalid_param because access is checked during ID validation. Could this check move to permission_callback() so the route returns 401/403 for denied access and 404 for a missing post? The tests should also confirm that error responses contain no protected content.
There was a problem hiding this comment.
I don't mind the check in the ID parameter. If a post shouldn't exist in the public API yet, different status responses to indicate an invalid post ID leaks some information, e.g. "Post XXX exists but is password protected, or otherwise inaccessible" is more information that "Post XXX isn't valid".
There was a problem hiding this comment.
As mentioned above, I think the best way to be consistent and avoid leaking information is to validate everything based on the ID OR permission_callback, but probably not both. I think reporting a missing ID and a draft/password protected ID the same way in this validation is fine. I left the check here for now, let me know if you disagree.
| // Use parent status if inheriting. | ||
| if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) { | ||
| return self::is_post_readable( $post->post_parent ); | ||
| $rest_controller = $post_type->get_rest_controller(); |
There was a problem hiding this comment.
A custom controller can throw during construction or in get_item_permissions_check(). Since this route now calls that controller directly, it should handle either failure without parsing the post or exposing exception details. A throwing test controller would verify a generic error response.
There was a problem hiding this comment.
Added a try/catch and tests in 30793b1, good idea.
| } | ||
| // Use the registered controller rather than copying the base posts controller's | ||
| // permission logic. Post types such as wp_block add stricter checks in subclasses. | ||
| $request = new WP_REST_Request( 'GET' ); |
There was a problem hiding this comment.
One boundary worth clarifying: if a custom controller permits an item but removes its content from the REST response, should GET /vip-block-data-api/v1/posts/{id}/blocks still return blocks parsed from stored post_content? The permission check does not run response preparation
There was a problem hiding this comment.
This is tricky. If we parse content from the direct REST response (possible fix for this issue), we even more deeply rely on the internal structure of the REST controller which can be filtered and changed a lot of ways. Using the REST security model is simple get_item_permissions_check(), but I'm worried about becoming too dependent on other REST internals like this.
I think for now I'd prefer returning blocks parsed from post_content for consistency if a user has read access to the item according to the controller, even if the response is filtered or changed.
There was a problem hiding this comment.
@ingeniumed I added a comment to the README about the security modal and how we use post_content directly in fae2906, so at least it should be easy to find this information if content isn't working as expected.
|
Max's changes and my commits should have addressed the comments above, please have another review when you have a chance. Thanks! |
Description
Prevents the Block Data API REST endpoint from returning raw block content that WordPress does not permit the current user to read.
post_content.vip_block_data_api__rest_validate_post_idas an explicit override point for integrations that provide their own authorization logic.Steps to Test
wp-env.WP_MULTISITE=1.composer phpcs.wp_block, while public posts and authorized editor requests continue to work.Verification