PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 All 506 releases
← All changes | json-endpoints/class.wpcom-json-api-update-media-v1-1-endpoint.php +73 -10 13.4.5 → 16.3-a.7 View file →
@@ -4,8 +4,12 @@
4 4 *
5 5 * Endpoint: v1.1/sites/%s/media/%d
6 6 */
7 7
8 +if ( ! defined( 'ABSPATH' ) ) {
9 + exit( 0 );
10 +}
11 +
8 12 new WPCOM_JSON_API_Update_Media_v1_1_Endpoint(
9 13 array(
10 14 'description' => 'Edit basic information about a media item.',
11 15 'group' => 'media',
@@ -74,11 +78,56 @@
74 78
75 79 // phpcs:disable PEAR.NamingConventions.ValidClassName.Invalid
76 80 /**
77 81 * Update media item info v1.1 class.
82 + *
83 + * @phan-constructor-used-for-side-effects
78 84 */
79 85 class WPCOM_JSON_API_Update_Media_v1_1_Endpoint extends WPCOM_JSON_API_Endpoint {
80 86 /**
87 + * Whether the current user may edit the given media item.
88 + *
89 + * `upload_files` is a primitive capability and ignores any object passed to it,
90 + * so it only tells us the caller may upload something, never that they may edit
91 + * this particular item. A missing item is passed through so the caller receives
92 + * the endpoint's own 404 rather than a 403. A userless request gets no exemption:
93 + * `edit_post` fails closed for user 0 like any other caller.
94 + *
95 + * Non-attachments are refused outright. `get_post()` resolves any post type, so
96 + * without this test a media endpoint edits ordinary posts, pages and revisions.
97 + * The post-type test must stay below the missing-post passthrough: that branch
98 + * returns true, so testing there would skip `edit_post` for ordinary posts.
99 + *
100 + * A non-attachment yields 403, not the 404 a missing item gets. This is a boolean
101 + * gate, and `get_media_item*()` resolves any post type, so a passthrough would
102 + * return 200 rather than 404. Revisit if clients conflate it with an auth failure.
103 + *
104 + * Do not move this into a trait: this file instantiates the endpoint above the
105 + * class declaration, and `use Trait;` disables PHP early binding, which makes the
106 + * file fatal with "Class not found".
107 + *
108 + * @param int $media_id Media post ID.
109 + * @return bool
110 + */
111 + protected function current_user_can_edit_media_item( $media_id ) {
112 + if ( ! current_user_can( 'upload_files' ) ) {
113 + return false;
114 + }
115 +
116 + $post = get_post( $media_id );
117 +
118 + if ( ! $post ) {
119 + return true;
120 + }
121 +
122 + if ( 'attachment' !== $post->post_type ) {
123 + return false;
124 + }
125 +
126 + return current_user_can( 'edit_post', $media_id );
127 + }
128 +
129 + /**
81 130 * Update media item info API v1.1 callback.
82 131 *
83 132 * @param string $path API path.
84 133 * @param int $blog_id Blog ID.
@@ -91,10 +140,10 @@
91 140 if ( is_wp_error( $blog_id ) ) {
92 141 return $blog_id;
93 142 }
94 143
95 - if ( ! current_user_can( 'upload_files', $media_id ) ) {
96 - return new WP_Error( 'unauthorized', 'User cannot view media', 403 );
144 + if ( ! $this->current_user_can_edit_media_item( $media_id ) ) {
145 + return new WP_Error( 'unauthorized', 'User cannot edit media', 403 );
97 146 }
98 147
99 148 $item = $this->get_media_item_v1_1( $media_id );
100 149
@@ -117,9 +166,23 @@
117 166 $insert['post_content'] = $input['description'];
118 167 }
119 168
120 169 if ( isset( $input['parent_id'] ) ) {
121 - $insert['post_parent'] = $input['parent_id'];
170 + $parent_id = (int) $input['parent_id'];
171 +
172 + /*
173 + * Attaching media to a post is an edit of that post, so it takes `edit_post` on
174 + * the target, as core's WP_REST_Attachments_Controller does for the same field.
175 + * Without this a caller attaches their own media to any post on the site.
176 + *
177 + * Zero is exempt: it detaches the item rather than naming a target, and
178 + * `edit_post` fails closed on 0, which would make detaching impossible.
179 + */
180 + if ( $parent_id && ! current_user_can( 'edit_post', $parent_id ) ) {
181 + return new WP_Error( 'unauthorized', 'User cannot edit the parent post', 403 );
182 + }
183 +
184 + $insert['post_parent'] = $parent_id;
122 185 }
123 186
124 187 if ( isset( $input['alt'] ) ) {
125 188 $alt = wp_strip_all_tags( $input['alt'], true );
@@ -187,14 +250,14 @@
187 250
188 251 return \Videopress_Attachment_Metadata::persist_metadata(
189 252 $media_id,
190 253 $item->videopress_guid,
191 - isset( $input['title'] ) ? $input['title'] : null,
192 - isset( $input['caption'] ) ? $input['caption'] : null,
193 - isset( $input['description'] ) ? $input['description'] : null,
194 - isset( $input['rating'] ) ? $input['rating'] : null,
195 - isset( $input['display_embed'] ) ? $input['display_embed'] : null,
196 - isset( $input['allow_download'] ) ? $input['allow_download'] : null,
197 - isset( $input['privacy_setting'] ) ? $input['privacy_setting'] : null
254 + $input['title'] ?? null,
255 + $input['caption'] ?? null,
256 + $input['description'] ?? null,
257 + $input['rating'] ?? null,
258 + $input['display_embed'] ?? null,
259 + $input['allow_download'] ?? null,
260 + $input['privacy_setting'] ?? null
198 261 );
199 262 }
200 263 }