Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.08% covered (success)
98.08%
51 / 52
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Uploader_Rest_Endpoints
98.08% covered (success)
98.08%
51 / 52
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%
27 / 27
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                    ),
64                ),
65            )
66        );
67    }
68
69    /**
70     * Checks whether the user has permission to perform the upload.
71     *
72     * The site-wide `upload_files` capability is not sufficient on its own: the
73     * endpoint operates on a caller-supplied attachment id, so we must also
74     * confirm the current user is allowed to edit that specific attachment.
75     * Without the per-object check any author could target another user's
76     * private/draft video attachment by id (IDOR).
77     *
78     * @param \WP_REST_Request $request The request object.
79     * @return boolean
80     */
81    public static function permissions_callback( $request ) {
82        if ( ! current_user_can( 'upload_files' ) ) {
83            return false;
84        }
85
86        return current_user_can( 'edit_post', (int) $request['attachment_id'] );
87    }
88
89    /**
90     * Validates the attachment ID argument.
91     *
92     * This remains available to existing callers, but must not be registered as
93     * the REST argument validator because those run before route authorization.
94     *
95     * @param integer|string $value The attachment ID passed as an argument to the endpoint.
96     * @return boolean|WP_Error
97     */
98    public static function validate_attachment_id( $value ) {
99        return Uploader::is_valid_attachment_id( $value );
100    }
101
102    /**
103     * Endpoint callback for the GET method. Checks the upload status
104     *
105     * @param \WP_REST_Request $request The request object.
106     * @return array|WP_Error
107     */
108    public static function check_status( $request ) {
109        $attachment_id = $request->get_param( 'attachment_id' );
110        try {
111            $uploader = new Uploader( $attachment_id );
112            $status   = $uploader->check_status();
113            return rest_ensure_response( $status );
114        } catch ( Upload_Exception $e ) {
115            return new WP_Error(
116                'rest_invalid_param',
117                $e->getMessage(),
118                array( 'status' => 400 )
119            );
120        }
121    }
122
123    /**
124     * Endpoint callback for the POST method. Uploads the video
125     *
126     * @param \WP_REST_Request $request The request object.
127     * @return array|WP_Error
128     */
129    public static function do_upload( $request ) {
130        $attachment_id = $request->get_param( 'attachment_id' );
131        try {
132            $uploader = new Uploader( $attachment_id );
133            $status   = $uploader->upload();
134            return rest_ensure_response( $status );
135        } catch ( Upload_Exception $e ) {
136            return new WP_Error(
137                'rest_invalid_param',
138                $e->getMessage(),
139                array( 'status' => 400 )
140            );
141        }
142    }
143}