PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-backup / src / rest / class-rest-controller.php
jetpack / jetpack_vendor / automattic / jetpack-backup / src / rest Last commit date
class-activity-log-bridge.php 1 week ago class-capabilities-bridge.php 1 week ago class-download-bridge.php 1 week ago class-file-browser-bridge.php 1 week ago class-rest-controller.php 1 week ago class-restore-bridge.php 1 week ago
class-rest-controller.php
249 lines
1 <?php
2 /**
3 * REST controller for the modernized Backup dashboard.
4 *
5 * @package automattic/jetpack-backup-plugin
6 */
7
8 namespace Automattic\Jetpack\Backup\V0005\REST;
9
10 use Automattic\Jetpack\Backup\V0005\Jetpack_Backup;
11 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
12 use Jetpack_Options;
13 use WP_Error;
14 use WP_REST_Request;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit( 0 );
18 }
19
20 /**
21 * Registers REST routes that back the modernized dashboard.
22 *
23 * Each bridge class declares its routes via `register_routes()` and uses
24 * the shared `permission_check()` helper for the `manage_options` gate.
25 * Routes only register when the modernization filter is on, so the
26 * legacy plugin is byte-identical when the flag is off.
27 */
28 class Rest_Controller {
29
30 /**
31 * Every category WPCOM's rewind endpoints recognize in a `types` map.
32 *
33 * The first six are the whole-site checklist the Restore and Download
34 * screens render.
35 *
36 * `paths` covers the granular *download* shape only — `types: { paths:
37 * true }`, paired with `include_path_list` / `exclude_path_list`.
38 * Nothing sends it yet (C3), but leaving it out would make the file
39 * browser's granular download fail closed with a confusing 400 the day
40 * it is wired up.
41 *
42 * It does not carry over to granular *restore*, whatever that ends up
43 * spelling: the v1 route took `types: 'paths'` as a bare string, which
44 * is not a map at all and would never reach this allowlist. Granular
45 * restore has to establish its own shape against the v2 route before
46 * anything here can claim to cover it.
47 *
48 * This list has to grow if VaultPress adds a category. WPCOM's own
49 * route deliberately does not allowlist, so that it stays open to new
50 * types; we can afford to be stricter because we also own the UI that
51 * produces the values, and here a value that names nothing is the
52 * dangerous case rather than merely a useless one.
53 *
54 * @var string[]
55 */
56 private const CATEGORIES = array(
57 'themes',
58 'plugins',
59 'roots',
60 'contents',
61 'sqls',
62 'uploads',
63 'paths',
64 );
65
66 /**
67 * Hook entry point. Registers all bridge routes if the modernization
68 * filter is enabled.
69 *
70 * @return void
71 */
72 public static function register_routes() {
73 if ( ! Jetpack_Backup::is_modernized() ) {
74 return;
75 }
76
77 Capabilities_Bridge::register_routes();
78 Activity_Log_Bridge::register_routes();
79 File_Browser_Bridge::register_routes();
80 Download_Bridge::register_routes();
81 Restore_Bridge::register_routes();
82 }
83
84 /**
85 * Permission check shared by every modernized-dashboard route.
86 *
87 * Mirrors the activity-log package's pattern: `manage_options` is
88 * necessary but not sufficient — every bridge eventually proxies a
89 * WPCOM endpoint that's user-gated, so a site admin who isn't
90 * personally WPCOM-linked needs a clearer error than the opaque
91 * "Only Administrators can query…" WPCOM returns.
92 *
93 * @return bool|WP_Error True when the current user can call the bridges, WP_Error otherwise.
94 */
95 public static function permission_check() {
96 if ( ! current_user_can( 'manage_options' ) ) {
97 return false;
98 }
99
100 if ( ! ( new Connection_Manager() )->is_user_connected() ) {
101 return new WP_Error(
102 'user_not_connected',
103 __( 'Your WordPress.com account is not connected to this site.', 'jetpack-backup-pkg' ),
104 array( 'status' => 403 )
105 );
106 }
107
108 return true;
109 }
110
111 /**
112 * Returns the site's WPCOM blog id, or a `not_connected` WP_Error
113 * when the site hasn't been registered yet. Shared across the bridges
114 * so the `sprintf( '/sites/%d/…', $blog_id )` upstream path is never
115 * built with an empty id.
116 *
117 * @return int|WP_Error Blog id, or WP_Error when not connected.
118 */
119 public static function get_blog_id_or_error() {
120 $blog_id = (int) Jetpack_Options::get_option( 'id' );
121 if ( ! $blog_id ) {
122 return new WP_Error(
123 'not_connected',
124 __( 'This site is not connected to Jetpack.', 'jetpack-backup-pkg' ),
125 array( 'status' => 412 )
126 );
127 }
128 return $blog_id;
129 }
130
131 /**
132 * Rebuild a restore/download `types` parameter as a named map.
133 *
134 * The PHP counterpart of the client's `requireTypes`, and the reason
135 * it exists here rather than being trusted from the request: WordPress
136 * validates `'type' => 'object'` with `rest_is_object()`, which is
137 * `is_array()`. A JSON list therefore passes validation and arrives as
138 * a PHP list, whose numeric keys WPCOM reads as category names. The
139 * route schema rejects the realistic version of that, but only because
140 * the members fail a boolean check — shape itself is never asserted —
141 * so the guarantee is made here, where the payload is actually built.
142 *
143 * Only known categories with a truthy value survive, and every
144 * surviving value is normalized to `true`. Values are read with
145 * `rest_sanitize_boolean()` so a form-encoded `"false"` or `"0"` means
146 * skip rather than select.
147 *
148 * Unknown keys are dropped rather than forwarded, which is what makes
149 * `request_names_no_types()` a total guard: without it a payload naming
150 * only categories WPCOM does not recognize would satisfy the guard and
151 * be sent on, and what WPCOM does with a `types` that matches nothing
152 * is not characterized. Dropping them means such a payload names
153 * nothing, and is refused. The realistic way to get there is not an
154 * attacker — an admin who can craft the request can already omit
155 * `types` for a whole-site operation — but a future client-side typo:
156 * renaming a checklist key `sqls` to `sql` would otherwise go through
157 * silently.
158 *
159 * @param mixed $types Raw `types` parameter from the request.
160 * @return array<string, true> Named types, empty when none are selected.
161 */
162 public static function named_types( $types ) {
163 if ( ! is_array( $types ) && ! is_object( $types ) ) {
164 return array();
165 }
166
167 $named = array();
168 foreach ( (array) $types as $key => $value ) {
169 if ( in_array( $key, self::CATEGORIES, true ) && rest_sanitize_boolean( $value ) ) {
170 $named[ $key ] = true;
171 }
172 }
173 return $named;
174 }
175
176 /**
177 * Whether the request supplied a `types` parameter that names no category.
178 *
179 * The distinction this draws is the whole point of the helper, and it
180 * is the opposite of what it looks like. An **absent** `types` is a
181 * valid, deliberate request for every category — WPCOM's contract is
182 * "omit it for everything" — so the mutations leave the key out for a
183 * whole-site restore or a full archive. A **supplied** `types` that
184 * survives into nothing is the other thing entirely: the caller tried
185 * to name categories and named none, and forwarding that as an
186 * omission would quietly upgrade "restore nothing" into "restore
187 * everything", against a live site.
188 *
189 * It takes the request rather than the value because the value cannot
190 * answer the question. `{"types": null}` is supplied and names nothing,
191 * but arrives as the same `null` an omitted key does — and the schema
192 * never sees it, since `WP_REST_Request::has_valid_params()` skips
193 * `validate_callback` for a null param. `has_param()` is the only thing
194 * that knows the key was on the wire.
195 *
196 * Nothing upstream catches it on both routes. The v2 restore route
197 * rejects a `types` naming nothing, but `/rewind/downloads` does not,
198 * so the guarantee has to be made here for the pair to behave alike.
199 *
200 * @param WP_REST_Request $request The REST request.
201 * @return bool True when the caller supplied a `types` that names no category.
202 */
203 public static function request_names_no_types( WP_REST_Request $request ) {
204 if ( ! $request->has_param( 'types' ) ) {
205 return false;
206 }
207
208 return ! self::named_types( $request->get_param( 'types' ) );
209 }
210
211 /**
212 * Convert a transport-level failure into a bridge error.
213 *
214 * `Client::wpcom_json_api_request_as_*` answers with a `WP_Error` when
215 * the request never reached WPCOM at all — DNS, TLS, or the cURL
216 * timeout behind JETPACK-2173's "cURL error 28". Returning that error
217 * unchanged hands cURL's own text to the browser, where the dashboard
218 * renders the message verbatim in a notice; it also carries no
219 * `status`, so core answers 500 for what is really a reachability
220 * problem rather than a server fault.
221 *
222 * The raw text is preserved under `transport` rather than discarded.
223 * It is the only part a support agent can act on, and it is the same
224 * reason the non-200 branches forward WPCOM's status instead of
225 * flattening it.
226 *
227 * Always 502: telling a timeout from a refused connection would mean
228 * matching on cURL's English message text, and no caller reads the
229 * difference.
230 *
231 * @param WP_Error $error Transport error from the HTTP client.
232 * @param string $code Bridge error code for the operation that failed.
233 * @return WP_Error
234 */
235 public static function transport_error( WP_Error $error, $code ) {
236 return new WP_Error(
237 $code,
238 __( 'Could not reach WordPress.com. Check your connection and try again.', 'jetpack-backup-pkg' ),
239 array(
240 'status' => 502,
241 'transport' => array(
242 'code' => $error->get_error_code(),
243 'message' => $error->get_error_message(),
244 ),
245 )
246 );
247 }
248 }
249