Skip to content

Commit feea51d

Browse files
authored
VideoPress: Add capability check to upload token AJAX handlers (#47683)
1 parent 1c81fb6 commit feea51d

3 files changed

Lines changed: 178 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: security
3+
4+
Ensure only users who can upload media can request VideoPress upload tokens via AJAX.

projects/packages/videopress/src/class-ajax.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ private function request_jwt_from_wpcom( $guid ) {
173173
* @return void
174174
*/
175175
public function wp_ajax_videopress_get_upload_jwt() {
176+
if ( ! current_user_can( 'upload_files' ) ) {
177+
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
178+
wp_send_json_error( array( 'message' => __( 'You do not have permission to upload files.', 'jetpack-videopress-pkg' ) ), null, JSON_UNESCAPED_SLASHES );
179+
return;
180+
}
181+
176182
$video_blog_id = $this->get_videopress_blog_id();
177183
$args = array(
178184
'method' => 'POST',
@@ -206,6 +212,12 @@ public function wp_ajax_videopress_get_upload_jwt() {
206212
* @return void
207213
*/
208214
public function wp_ajax_videopress_get_upload_token() {
215+
if ( ! current_user_can( 'upload_files' ) ) {
216+
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
217+
wp_send_json_error( array( 'message' => __( 'You do not have permission to upload files.', 'jetpack-videopress-pkg' ) ), null, JSON_UNESCAPED_SLASHES );
218+
return;
219+
}
220+
209221
$video_blog_id = $this->get_videopress_blog_id();
210222

211223
$args = array(
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<?php
2+
/**
3+
* Tests for Automattic\Jetpack\VideoPress\AJAX
4+
*
5+
* @package automattic/jetpack-videopress
6+
*/
7+
8+
namespace Automattic\Jetpack\VideoPress;
9+
10+
use Automattic\Jetpack\Constants;
11+
use WorDBless\BaseTestCase;
12+
13+
/**
14+
* AJAX capability check test suite.
15+
*/
16+
class AJAX_Test extends BaseTestCase {
17+
18+
/**
19+
* The AJAX instance.
20+
*
21+
* @var AJAX
22+
*/
23+
private $ajax;
24+
25+
/**
26+
* Set up before each test.
27+
*/
28+
protected function set_up() {
29+
Constants::set_constant( 'JETPACK__WPCOM_JSON_API_BASE', 'https://public-api.wordpress.com' );
30+
// Mock connection.
31+
\Jetpack_Options::update_option( 'blog_token', 'asdasd.123123' );
32+
\Jetpack_Options::update_option( 'id', 1234 );
33+
34+
$this->ajax = AJAX::init();
35+
}
36+
37+
/**
38+
* Clean up after each test.
39+
*/
40+
protected function tear_down() {
41+
wp_set_current_user( 0 );
42+
Constants::clear_constants();
43+
}
44+
45+
/**
46+
* Test that subscribers cannot get upload JWTs.
47+
*/
48+
public function test_get_upload_jwt_rejected_for_subscriber() {
49+
$this->set_current_user_role( 'subscriber' );
50+
51+
$response = $this->call_ajax_method( 'wp_ajax_videopress_get_upload_jwt' );
52+
53+
$this->assertFalse( $response['success'] );
54+
$this->assertSame( 'You do not have permission to upload files.', $response['data']['message'] );
55+
}
56+
57+
/**
58+
* Test that contributors cannot get upload JWTs.
59+
*/
60+
public function test_get_upload_jwt_rejected_for_contributor() {
61+
$this->set_current_user_role( 'contributor' );
62+
63+
$response = $this->call_ajax_method( 'wp_ajax_videopress_get_upload_jwt' );
64+
65+
$this->assertFalse( $response['success'] );
66+
$this->assertSame( 'You do not have permission to upload files.', $response['data']['message'] );
67+
}
68+
69+
/**
70+
* Test that authors can get upload JWTs.
71+
*/
72+
public function test_get_upload_jwt_allowed_for_author() {
73+
$this->set_current_user_role( 'author' );
74+
75+
$valid_response = array( $this, 'return_valid_upload_response' );
76+
add_filter( 'pre_http_request', $valid_response );
77+
$response = $this->call_ajax_method( 'wp_ajax_videopress_get_upload_jwt' );
78+
remove_filter( 'pre_http_request', $valid_response );
79+
80+
$this->assertTrue( $response['success'] );
81+
}
82+
83+
/**
84+
* Test that subscribers cannot get upload tokens.
85+
*/
86+
public function test_get_upload_token_rejected_for_subscriber() {
87+
$this->set_current_user_role( 'subscriber' );
88+
89+
$response = $this->call_ajax_method( 'wp_ajax_videopress_get_upload_token' );
90+
91+
$this->assertFalse( $response['success'] );
92+
$this->assertSame( 'You do not have permission to upload files.', $response['data']['message'] );
93+
}
94+
95+
/**
96+
* Test that authors can get upload tokens.
97+
*/
98+
public function test_get_upload_token_allowed_for_author() {
99+
$this->set_current_user_role( 'author' );
100+
101+
$valid_response = array( $this, 'return_valid_upload_response' );
102+
add_filter( 'pre_http_request', $valid_response );
103+
$response = $this->call_ajax_method( 'wp_ajax_videopress_get_upload_token' );
104+
remove_filter( 'pre_http_request', $valid_response );
105+
106+
$this->assertTrue( $response['success'] );
107+
}
108+
109+
/**
110+
* Helper to call an AJAX method and return the decoded JSON response.
111+
*
112+
* @param string $method The AJAX method name.
113+
* @return array The decoded JSON response.
114+
*/
115+
private function call_ajax_method( $method ) {
116+
add_filter( 'wp_doing_ajax', '__return_true' );
117+
118+
// Override WorDBless's wp_die handler to avoid a current_filter() bug on PHP < 8.
119+
$noop_die_handler = static function () {
120+
return '__return_empty_string';
121+
};
122+
add_filter( 'wp_die_ajax_handler', $noop_die_handler, 20 );
123+
124+
ob_start();
125+
$this->ajax->$method();
126+
$output = ob_get_clean();
127+
128+
remove_filter( 'wp_die_ajax_handler', $noop_die_handler, 20 );
129+
remove_filter( 'wp_doing_ajax', '__return_true' );
130+
131+
$response = json_decode( $output, true );
132+
$this->assertNotNull( $response, "AJAX method '$method' did not return valid JSON. Output: " . substr( $output, 0, 200 ) );
133+
134+
return $response;
135+
}
136+
137+
/**
138+
* Create a user with the given role and set as current user.
139+
*
140+
* @param string $role The user role.
141+
*/
142+
private function set_current_user_role( $role ) {
143+
$user_id = wp_insert_user(
144+
array(
145+
'user_login' => $role . '_user',
146+
'user_pass' => 'pass',
147+
'user_email' => $role . '@test.com',
148+
'role' => $role,
149+
)
150+
);
151+
wp_set_current_user( $user_id );
152+
}
153+
154+
/**
155+
* Returns a mock HTTP response with a valid upload token.
156+
*
157+
* @return array
158+
*/
159+
public function return_valid_upload_response() {
160+
return array( 'body' => wp_json_encode( array( 'upload_token' => 'test-token' ), JSON_UNESCAPED_SLASHES ) );
161+
}
162+
}

0 commit comments

Comments
 (0)