-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Improve validation and permission checks for WP_HTTP_Polling_Sync_Server #11296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
74d3090
842e056
5281152
628a2ed
546d5f2
9dd286b
dd187cb
3fd71b0
62d058f
532aec1
fc8eb60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,30 @@ class WP_HTTP_Polling_Sync_Server { | |
| */ | ||
| const COMPACTION_THRESHOLD = 50; | ||
|
|
||
| /** | ||
| * Maximum total size (in bytes) of the request body. | ||
| * | ||
| * @since 7.0.0 | ||
| * @var int | ||
| */ | ||
| const MAX_BODY_SIZE = 16 * MB_IN_BYTES; | ||
|
|
||
| /** | ||
| * Maximum number of rooms allowed per request. | ||
| * | ||
| * @since 7.0.0 | ||
| * @var int | ||
| */ | ||
| const MAX_ROOMS_PER_REQUEST = 50; | ||
|
|
||
| /** | ||
| * Maximum length of a single update data string. | ||
| * | ||
| * @since 7.0.0 | ||
| * @var int | ||
| */ | ||
| const MAX_UPDATE_DATA_SIZE = MB_IN_BYTES; | ||
|
|
||
| /** | ||
| * Sync update type: compaction. | ||
| * | ||
|
|
@@ -96,8 +120,9 @@ public function register_routes(): void { | |
| $typed_update_args = array( | ||
| 'properties' => array( | ||
| 'data' => array( | ||
| 'type' => 'string', | ||
| 'required' => true, | ||
| 'type' => 'string', | ||
| 'required' => true, | ||
| 'maxLength' => self::MAX_UPDATE_DATA_SIZE, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dd32 noted in slack that large content updates can be made via the code editor. Is there a way that can be handled, even if it's just to replace the content with the code edited version if it's too large.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code editor targets You can follow the diffing logic here: |
||
| ), | ||
| 'type' => array( | ||
| 'type' => 'string', | ||
|
|
@@ -149,12 +174,14 @@ public function register_routes(): void { | |
| 'methods' => array( WP_REST_Server::CREATABLE ), | ||
| 'callback' => array( $this, 'handle_request' ), | ||
| 'permission_callback' => array( $this, 'check_permissions' ), | ||
| 'validate_callback' => array( $this, 'validate_request' ), | ||
peterwilsoncc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 'args' => array( | ||
| 'rooms' => array( | ||
| 'items' => array( | ||
| 'properties' => $room_args, | ||
| 'type' => 'object', | ||
| ), | ||
| 'maxItems' => self::MAX_ROOMS_PER_REQUEST, | ||
| 'required' => true, | ||
| 'type' => 'array', | ||
| ), | ||
|
|
@@ -223,6 +250,30 @@ public function check_permissions( WP_REST_Request $request ) { | |
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Validates that the request body does not exceed the maximum allowed size. | ||
| * | ||
| * Runs as the route-level validate_callback, after per-arg schema | ||
| * validation has already passed. | ||
| * | ||
| * @since 7.0.0 | ||
| * | ||
| * @param WP_REST_Request $request The REST request. | ||
| * @return true|WP_Error True if valid, WP_Error if the body is too large. | ||
| */ | ||
| public function validate_request( WP_REST_Request $request ) { | ||
| $body = $request->get_body(); | ||
| if ( is_string( $body ) && strlen( $body ) > self::MAX_BODY_SIZE ) { | ||
| return new WP_Error( | ||
| 'rest_sync_body_too_large', | ||
| __( 'Request body is too large.' ), | ||
| array( 'status' => 413 ) | ||
| ); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Handles request: stores sync updates and awareness data, and returns | ||
| * updates the client is missing. | ||
|
|
@@ -278,24 +329,47 @@ public function handle_request( WP_REST_Request $request ) { | |
| * | ||
| * @param string $entity_kind The entity kind, e.g. 'postType', 'taxonomy', 'root'. | ||
| * @param string $entity_name The entity name, e.g. 'post', 'category', 'site'. | ||
| * @param string|null $object_id The object ID / entity key for single entities, null for collections. | ||
| * @param string|null $object_id The numeric object ID / entity key for single entities, null for collections. | ||
| * @return bool True if user has permission, otherwise false. | ||
| */ | ||
| private function can_user_sync_entity_type( string $entity_kind, string $entity_name, ?string $object_id ): bool { | ||
| // Handle single post type entities with a defined object ID. | ||
| if ( 'postType' === $entity_kind && is_numeric( $object_id ) ) { | ||
| return current_user_can( 'edit_post', (int) $object_id ); | ||
| if ( is_string( $object_id ) ) { | ||
| if ( ! ctype_digit( $object_id ) ) { | ||
| return false; | ||
| } | ||
| $object_id = (int) $object_id; | ||
| } | ||
|
|
||
| // Handle single taxonomy term entities with a defined object ID. | ||
| if ( 'taxonomy' === $entity_kind && is_numeric( $object_id ) ) { | ||
| $taxonomy = get_taxonomy( $entity_name ); | ||
| return isset( $taxonomy->cap->assign_terms ) && current_user_can( $taxonomy->cap->assign_terms ); | ||
| if ( null !== $object_id && $object_id <= 0 ) { | ||
| // Object ID must be numeric if provided. | ||
| return false; | ||
| } | ||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Handle single comment entities with a defined object ID. | ||
| if ( 'root' === $entity_kind && 'comment' === $entity_name && is_numeric( $object_id ) ) { | ||
| return current_user_can( 'edit_comment', (int) $object_id ); | ||
| // Validate permissions for the provided object ID. | ||
| if ( is_int( $object_id ) ) { | ||
| // Handle single post type entities with a defined object ID. | ||
| if ( 'postType' === $entity_kind ) { | ||
| if ( get_post_type( $object_id ) !== $entity_name ) { | ||
| // Post is not of the specified post type. | ||
| return false; | ||
| } | ||
| return current_user_can( 'edit_post', $object_id ); | ||
| } | ||
|
|
||
| // Handle single taxonomy term entities with a defined object ID. | ||
| if ( 'taxonomy' === $entity_kind ) { | ||
| $term_exists = term_exists( $object_id, $entity_name ); | ||
| if ( ! is_array( $term_exists ) || ! isset( $term_exists['term_id'] ) ) { | ||
| // Either term doesn't exist OR term is not in specified taxonomy. | ||
| return false; | ||
| } | ||
|
|
||
| return current_user_can( 'edit_term', $object_id ); | ||
| } | ||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Handle single comment entities with a defined object ID. | ||
| if ( 'root' === $entity_kind && 'comment' === $entity_name ) { | ||
| return current_user_can( 'edit_comment', $object_id ); | ||
| } | ||
| } | ||
|
|
||
| // All the remaining checks are for collections. If an object ID is provided, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MAX_UPDATE_DATA_SIZEis documented as a size "in bytes", but it's currently enforced via the REST schema'smaxLength(character count). If this is intended to limit the encoded string length, consider updating the docblock wording to avoid implying decoded byte size enforcement.