Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.21% covered (success)
98.21%
55 / 56
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Uploader_Rest_Endpoints
98.21% covered (success)
98.21%
55 / 56
83.33% covered (warning)
83.33%
5 / 6
9
0.00% covered (danger)
0.00%
0 / 1
 init
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 register_rest_endpoints
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
1 / 1
1
 permissions_callback
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 validate_attachment_id
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 check_status
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 do_upload
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * VideoPress Uploader
4 *
5 * @package automattic/jetpack-videopress
6 */
7
8namespace Automattic\Jetpack\VideoPress;
9
10use WP_Error;
11
12/**
13 * VideoPress Uploader class
14 *
15 * Handles the upload from the Media Library to VideoPress servers
16 */
17class Uploader_Rest_Endpoints {
18
19    /**
20     * Initializes the endpoints
21     *
22     * @return void
23     */
24    public static function init() {
25        add_action( 'rest_api_init', array( __CLASS__, 'register_rest_endpoints' ) );
26    }
27
28    /**
29     * Register the REST API routes.
30     *
31     * @return void
32     */
33    public static function register_rest_endpoints() {
34        /*
35         * Keep route validation structural. WordPress validates arguments before
36         * running permission callbacks, so inspecting the attachment or its file
37         * here would reveal which IDs contain readable videos to unauthorized
38         * callers. Uploader validates the attachment after authorization instead.
39         */
40        $id_arg = array(
41            'description' => __( 'The ID of the attachment you want to upload to VideoPress', 'jetpack-videopress-pkg' ),
42            'type'        => 'integer',
43            'required'    => true,
44        );
45        register_rest_route(
46            'videopress/v1',
47            'upload/(?P<attachment_id>\d+)',
48            array(
49                array(
50                    'methods'             => \WP_REST_Server::READABLE,
51                    'callback'            => __CLASS__ . '::check_status',
52                    'permission_callback' => __CLASS__ . '::permissions_callback',
53                    'args'                => array(
54                        'attachment_id' => $id_arg,
55                    ),
56                ),
57                array(
58                    'methods'             => \WP_REST_Server::EDITABLE,
59                    'callback'            => __CLASS__ . '::do_upload',
60                    'permission_callback' => __CLASS__ . '::permissions_callback',
61                    'args'                => array(
62                        'attachment_id' => $id_arg,
63                        'source'        => array(
64                            'type' => 'string',
65                            'enum' => array( 'dashboard_promote', 'block_editor' ),
66                        ),
67                    ),
68                ),
69            )
70        );
71    }
72
73    /**
74     * Checks whether the user has permission to perform the upload.
75     *
76     * The site-wide `upload_files` capability is not sufficient on its own: the
77     * endpoint operates on a caller-supplied attachment id, so we must also
78     * confirm the current user is allowed to edit that specific attachment.
79     * Without the per-object check any author could target another user's
80     * private/draft video attachment by id (IDOR).
81     *
82     * @param \WP_REST_Request $request The request object.
83     * @return boolean
84     */
85    public static function permissions_callback( $request ) {
86        if ( ! current_user_can( 'upload_files' ) ) {
87            return false;
88        }
89
90        return current_user_can( 'edit_post', (int) $request['attachment_id'] );
91    }
92
93    /**
94     * Validates the attachment ID argument.
95     *
96     * This remains available to existing callers, but must not be registered as
97     * the REST argument validator because those run before route authorization.
98     *
99     * @param integer|string $value The attachment ID passed as an argument to the endpoint.
100     * @return boolean|WP_Error
101     */
102    public static function validate_attachment_id( $value ) {
103        return Uploader::is_valid_attachment_id( $value );
104    }
105
106    /**
107     * Endpoint callback for the GET method. Checks the upload status
108     *
109     * @param \WP_REST_Request $request The request object.
110     * @return array|WP_Error
111     */
112    public static function check_status( $request ) {
113        $attachment_id = $request->get_param( 'attachment_id' );
114        try {
115            $uploader = new Uploader( $attachment_id );
116            $status   = $uploader->check_status();
117            return rest_ensure_response( $status );
118        } catch ( Upload_Exception $e ) {
119            return new WP_Error(
120                'rest_invalid_param',
121                $e->getMessage(),
122                array( 'status' => 400 )
123            );
124        }
125    }
126
127    /**
128     * Endpoint callback for the POST method. Uploads the video
129     *
130     * @param \WP_REST_Request $request The request object.
131     * @return array|WP_Error
132     */
133    public static function do_upload( $request ) {
134        $attachment_id = $request->get_param( 'attachment_id' );
135        try {
136            $uploader = new Uploader( $attachment_id, $request->get_param( 'source' ) );
137            $status   = $uploader->upload();
138            return rest_ensure_response( $status );
139        } catch ( Upload_Exception $e ) {
140            return new WP_Error(
141                'rest_invalid_param',
142                $e->getMessage(),
143                array( 'status' => 400 )
144            );
145        }
146    }
147}