PluginProbe
Patchstack – WordPress & Plugins Security / 2.1.24
Patchstack – WordPress & Plugins Security v2.1.24
2.3.7 trunk 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.17 2.1.18 2.1.19 2.1.2 2.1.20 2.1.21 2.1.22 2.1.23 2.1.24 2.1.25 2.1.3 2.1.4 2.1.5 2.1.6 All 49 releases
← All changes | includes/api.php +82 -196 trunk2.1.24 View file →
@@ -15,13 +15,8 @@
15 15 */
16 16 public $blog_id;
17 17
18 18 /**
19 - * @var string Error message from the API.
20 - */
21 - public $message;
22 -
23 - /**
24 19 * Add the actions required for the API.
25 20 *
26 21 * @param Patchstack $core
27 22 * @return void
@@ -28,11 +23,10 @@
28 23 */
29 24 public function __construct( $core ) {
30 25 parent::__construct( $core );
31 26 $this->blog_id = get_current_blog_id();
32 - add_action( 'patchstack_update_license_status', [ $this, 'update_license_status' ] );
33 - add_action( 'patchstack_send_ping', [ $this, 'ping' ] );
34 - add_action( 'patchstack_send_header_request', [ $this, 'send_header_request' ] );
27 + add_action( 'patchstack_update_license_status', array( $this, 'update_license_status' ) );
28 + add_action( 'patchstack_send_ping', array( $this, 'ping' ) );
35 29 }
36 30
37 31 /**
38 32 * Get the API token.
@@ -56,18 +50,17 @@
56 50 if ( $response && $response->result == 'success' ) {
57 51 $this->update_blog_option(
58 52 $this->blog_id,
59 53 'patchstack_api_token',
60 - [
54 + array(
61 55 'token' => $response->message,
62 56 'expiresin' => $response->expiresin,
63 - ]
57 + )
64 58 );
65 59 return $response->message;
66 60 }
67 61
68 62 // If we reach this, it means we were not able to get the access token.
69 - $this->message = $response;
70 63 $this->update_blog_option( $this->blog_id, 'patchstack_api_token', '' );
71 64 return null;
72 65 }
73 66
@@ -79,13 +72,13 @@
79 72 * @return string|array|object
80 73 */
81 74 public function fetch_access_token( $clientid = '', $secretkey = '' ) {
82 75 // Skeleton for the response data.
83 - $response_data = (object) [
76 + $response_data = (object) array(
84 77 'result' => '',
85 78 'message' => '',
86 79 'expiresin' => '',
87 - ];
80 + );
88 81
89 82 // Determine if the license id/key is set.
90 83 $client_id = $this->get_blog_option( $this->blog_id, 'patchstack_clientid', $clientid );
91 84
@@ -98,9 +91,9 @@
98 91
99 92 // Make sure these values are set.
100 93 if ( empty( $client_id ) || empty( $client_secret ) ) {
101 94 $response_data->result = 'failed';
102 - $response_data->message = esc_attr__( 'API keys missing! Unable to obtain an access token.', 'patchstack' );
95 + $response_data->message = __( 'API keys missing! Unable to obtain an access token.', 'patchstack' );
103 96 return $response_data;
104 97 }
105 98
106 99 // Send a request to our server to obtain the access token.
@@ -105,38 +98,28 @@
105 98
106 99 // Send a request to our server to obtain the access token.
107 100 $response = wp_remote_post(
108 101 $this->plugin->auth_url . '/oauth/token',
109 - [
102 + array(
110 103 'method' => 'POST',
111 104 'timeout' => 60,
112 105 'redirection' => 5,
113 106 'httpversion' => '1.0',
114 107 'blocking' => true,
115 - 'headers' => [],
116 - 'body' => [
108 + 'headers' => array(),
109 + 'body' => array(
117 110 'client_id' => $client_id,
118 111 'client_secret' => $client_secret,
119 112 'grant_type' => 'client_credentials',
120 - ],
121 - 'cookies' => [],
122 - ]
113 + ),
114 + 'cookies' => array(),
115 + )
123 116 );
124 117
125 118 // Stop if we received an error from the API.
126 - if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) == 401 ) {
127 - $this->message = wp_remote_retrieve_body( $response );
128 -
129 - if ( wp_remote_retrieve_response_code( $response ) == 401 ) {
130 - $this->update_blog_option( $this->blog_id, 'patchstack_clientid', '' );
131 - $this->update_blog_option( $this->blog_id, 'patchstack_secretkey', '' );
132 - $this->update_blog_option( $this->blog_id, 'patchstack_secretkey_nonce', '' );
133 - $this->update_blog_option( $this->blog_id, 'patchstack_api_token', '' );
134 - }
135 -
119 + if ( is_wp_error( $response ) ) {
136 120 $response_data->result = 'failed';
137 - $response_data->message = esc_attr__( 'Unexpected error! Unable to obtain an access token. Error code: ', 'patchstack' ) . wp_remote_retrieve_response_code( $response );
138 - $response_data->body = $this->message;
121 + $response_data->message = __( 'Unexpected error! Unable to obtain an access token.', 'patchstack' ) . $response->get_error_message();
139 122 return $response_data;
140 123 }
141 124
142 125 // Parse the result.
@@ -149,9 +132,8 @@
149 132 // We need to know when the token expires.
150 133 // Defer to 'expires' if it is provided instead.
151 134 if ( isset( $result->expires_in ) ) {
152 135 if ( ! is_numeric( $result->expires_in ) ) {
153 - $response_data->result = 'failed';
154 136 $response_data->message = 'expires_in value must be an integer';
155 137 return $response_data;
156 138 }
157 139 $response_data->expiresin = $result->expires_in != 0 ? time() + $result->expires_in : 0;
@@ -159,76 +141,14 @@
159 141
160 142 return $response_data;
161 143 } elseif ( isset( $result->error ) ) {
162 144 $response_data->result = $result->error;
163 - $response_data->message = esc_attr__( 'Unexpected error! Unable to obtain an access token.', 'patchstack' ) . $result->message;
145 + $response_data->message = __( 'Unexpected error! Unable to obtain an access token.', 'patchstack' ) . $result->message;
164 146 return $response_data;
165 147 }
166 148 }
167 149
168 150 /**
169 - * Send a request to the API with optionally POST data.
170 - *
171 - * @param string $url
172 - * @param string $method
173 - * @param array $data
174 - * @return void|array If successful array, otherwise void.
175 - */
176 - public function send_request( $url, $method, $data = [] ) {
177 - // Attempt to get the access token.
178 - $token = $this->get_access_token();
179 - if ( empty( $token ) ) {
180 - return;
181 - }
182 -
183 - // Pass the multisite value to all requests, only for POST requests.
184 - if ( $method == 'POST' ) {
185 - $data['is_multisite'] = $this->is_multi_site ? 1 : 0;
186 - }
187 -
188 - // Send the remote request using the WordPress built-in method.
189 - $response = wp_remote_request(
190 - $this->plugin->api_url . $url,
191 - [
192 - 'method' => $method,
193 - 'timeout' => 60,
194 - 'redirection' => 5,
195 - 'httpversion' => '1.0',
196 - 'blocking' => true,
197 - 'headers' => [
198 - 'Authorization' => 'Bearer ' . $token,
199 - 'LicenseID' => $this->get_blog_option( $this->blog_id, 'patchstack_clientid', 0 ),
200 - 'Source-Host' => get_site_url(),
201 - ],
202 - 'body' => $data,
203 - 'cookies' => [],
204 - ]
205 - );
206 -
207 - // Check error or status code.
208 - if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) != 200 ) {
209 -
210 - // See if we received a site API connection termination.
211 - $body = json_decode( wp_remote_retrieve_body( $response ), true );
212 - if ( isset( $body['cancel'] ) ) {
213 - $this->update_blog_option( $this->blog_id, 'patchstack_clientid', '' );
214 - $this->update_blog_option( $this->blog_id, 'patchstack_secretkey', '' );
215 - $this->update_blog_option( $this->blog_id, 'patchstack_secretkey_nonce', '' );
216 - $this->update_blog_option( $this->blog_id, 'patchstack_api_token', '' );
217 - }
218 -
219 - return wp_remote_retrieve_response_code( $response );
220 - }
221 -
222 - // A 200 OK means we successfully communicated with the API for this sync
223 - // action (license verify, log/software upload, rule pull, ping, etc.), so
224 - // record it as the last successful sync time.
225 - $this->update_blog_option( $this->blog_id, 'patchstack_last_sync', time() );
226 -
227 - return json_decode( wp_remote_retrieve_body( $response ), true );
228 - }
229 -
230 - /**
231 151 * Checks if the API token has expired.
232 152 *
233 153 * @param integer $expiresin API token expiry.
234 154 * @return boolean If the token has expired.
@@ -233,12 +153,8 @@
233 153 * @param integer $expiresin API token expiry.
234 154 * @return boolean If the token has expired.
235 155 */
236 156 public function has_expired( $expiresin ) {
237 - // A stored expiry of 0 means the token never expires.
238 - if ( $expiresin === 0 ) {
239 - return false;
240 - }
241 157 return ( $expiresin < ( time() + 30 ) );
242 158 }
243 159
244 160 /**
@@ -243,97 +159,93 @@
243 159
244 160 /**
245 161 * Retrieve the status of a license.
246 162 *
247 - * @param boolean $fetchPolicy Whether or not to fetch the policy settings.
248 163 * @return void|array
249 164 */
250 - public function update_license_status($fetchPolicy = false) {
165 + public function update_license_status() {
251 166 // Get current license status.
252 - $response = $this->send_request( '/api/license/verify' . ($fetchPolicy ? '?fetchPolicy=true' : ''), 'GET' );
167 + $response = $this->send_request( '/api/license/verify', 'GET' );
253 168
254 - // Invalid license, or no longer active.
255 - if ( ! is_array( $response ) && $response == 422 ) {
256 - $this->update_blog_option( $this->blog_id, 'patchstack_clientid', '' );
257 - $this->update_blog_option( $this->blog_id, 'patchstack_secretkey', '' );
258 - $this->update_blog_option( $this->blog_id, 'patchstack_secretkey_nonce', '' );
259 - $this->update_blog_option( $this->blog_id, 'patchstack_api_token', '' );
260 - return;
261 - }
262 -
263 169 // Update the representing options.
264 - // Expiry date.
265 170 if ( isset( $response['expires_at'] ) ) {
266 171 $this->update_blog_option( $this->blog_id, 'patchstack_license_expiry', $response['expires_at'] );
267 172 }
268 173
269 - // Free vs Paid license.
270 174 if ( isset( $response['free'] ) ) {
271 175 $this->update_blog_option( $this->blog_id, 'patchstack_license_free', $response['free'] == false ? 0 : 1 );
272 176
273 177 if ( $response['free'] == true ) {
274 178 $this->update_blog_option( $this->blog_id, 'patchstack_show_settings', 0 );
275 - $this->update_blog_option( $this->blog_id, 'patchstack_firewall_rules_v3', '[]' );
276 179 } else {
277 180 $this->send_header_request();
278 181 }
279 182 }
280 183
281 - // Active subscription.
282 - if ( isset( $response['active'] ) ) {
283 - $this->update_blog_option( $this->blog_id, 'patchstack_license_activated', $response['active'] == true ? 1 : 0 );
184 + if ( isset( $response['active'] ) && $response['active'] == true ) {
185 + $this->update_blog_option( $this->blog_id, 'patchstack_license_activated', true );
284 186 }
285 187
286 - // Subscription class.
287 188 if ( isset( $response['class'] ) ) {
288 189 $this->update_blog_option( $this->blog_id, 'patchstack_subscription_class', $response['class'] );
289 190 $this->update_blog_option( $this->blog_id, 'patchstack_last_license_check', time() );
290 191 }
291 192
292 - // Managed site status.
293 - if ( isset( $response['managed'], $response['managed_string'] ) ) {
294 - $this->update_blog_option( $this->blog_id, 'patchstack_managed', $response['managed'] ? 1 : 0 );
295 - $this->update_blog_option( $this->blog_id, 'patchstack_managed_text', $response['managed_string'] );
296 - }
193 + return $response;
194 + }
297 195
298 - // Site ID.
299 - if ( isset( $response['site_id'] ) ) {
300 - $this->update_blog_option( $this->blog_id, 'patchstack_site_id', $response['site_id'] );
196 + /**
197 + * Send a request to the API with optionally POST data.
198 + *
199 + * @param string $url
200 + * @param string $request
201 + * @param array $data
202 + * @return void|array If successful array, otherwise void.
203 + */
204 + public function send_request( $url, $request, $data = array() ) {
205 + // Attempt to get the access token.
206 + $token = $this->get_access_token();
207 + if ( empty( $token ) ) {
208 + return;
301 209 }
302 210
303 - // Policy settings.
304 - if ( isset( $response['policy'] ) && is_array( $response['policy'] ) && count( $response['policy'] ) > 0 ) {
305 - foreach ( $response['policy'] as $key => $value ) {
306 - // Make sure the option exists.
307 - if ( ! array_key_exists( $key, $this->plugin->admin_options->options ) ) {
308 - continue;
309 - }
211 + // Send the remote request using the WordPress built-in method.
212 + $response = wp_remote_request(
213 + $this->plugin->api_url . $url,
214 + array(
215 + 'method' => $request,
216 + 'timeout' => 60,
217 + 'redirection' => 5,
218 + 'httpversion' => '1.0',
219 + 'blocking' => true,
220 + 'headers' => array(
221 + 'Authorization' => 'Bearer ' . $token,
222 + 'LicenseID' => $this->get_blog_option( $this->blog_id, 'patchstack_clientid', 0 ),
223 + 'Source-Host' => get_site_url(),
224 + ),
225 + 'body' => $data,
226 + 'cookies' => array(),
227 + )
228 + );
310 229
311 - // Booleans would persist as '1' / '' otherwise, store them as 1/0 so type checks behave consistently.
312 - if ( is_bool( $value ) ) {
313 - $value = $value ? 1 : 0;
314 - }
315 -
316 - // Update the option.
317 - $this->update_blog_option( $this->blog_id, $key, $value );
318 - }
230 + // Check error or status code.
231 + if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) != 200 ) {
232 + $this->update_blog_option( $this->blog_id, 'patchstack_api_token', '' );
233 + return;
319 234 }
320 235
321 - return $response;
236 + return json_decode( wp_remote_retrieve_body( $response ), true );
322 237 }
323 238
324 239 /**
325 240 * Send a request to our API for the IP address header.
326 - *
327 - * @return void
328 241 */
329 242 public function send_header_request()
330 243 {
331 244 $header = get_option( 'patchstack_firewall_ip_header', '' );
332 245 $computed = get_option( 'patchstack_ip_header_computed', 0 );
333 - $force = get_option( 'patchstack_ip_header_force_compute', 0 );
334 246
335 - if ( ( $header == '' && ! $computed ) || $force ) {
247 + if ( $header == '' && ! $computed ) {
336 248 // Create an OTT token.
337 249 $ott = md5( wp_generate_password( 32, true, true ) );
338 250 update_option( 'patchstack_ott_action', $ott );
339 251
@@ -339,23 +251,23 @@
339 251
340 252 // Tell our API.
341 253 wp_remote_request(
342 254 $this->plugin->api_url . '/api/header',
343 - [
255 + array(
344 256 'method' => 'POST',
345 257 'timeout' => 60,
346 258 'redirection' => 5,
347 259 'httpversion' => '1.0',
348 260 'blocking' => true,
349 - 'headers' => [
261 + 'headers' => array(
350 262 'Source-Host' => get_site_url(),
351 - ],
352 - 'body' => [
263 + ),
264 + 'body' => array(
353 265 'token' => $ott,
354 266 'url' => get_site_url()
355 - ],
356 - 'cookies' => [],
357 - ]
267 + ),
268 + 'cookies' => array(),
269 + )
358 270 );
359 271 }
360 272 }
361 273
@@ -364,9 +276,14 @@
364 276 *
365 277 * @return array The firewall rules.
366 278 */
367 279 public function post_firewall_rule_json() {
368 - return $this->send_request( '/api/get-rules/3', 'POST' );
280 + // If the request is coming from the API, fetch fresh rules.
281 + if ( isset( $_POST['webarx_refresh_rules'] ) ) {
282 + return $this->send_request( '/api/get-rules/2?bypass=cache', 'POST' );
283 + }
284 +
285 + return $this->send_request( '/api/get-rules/2', 'POST' );
369 286 }
370 287
371 288 /**
372 289 * Get the .htaccess rules.
@@ -378,8 +295,17 @@
378 295 return $this->send_request( '/api/rules', 'POST', $settings );
379 296 }
380 297
381 298 /**
299 + * Get the .htaccess firewall rules.
300 + *
301 + * @return array The .htaccess rules.
302 + */
303 + public function post_firewall_htaccess_rule() {
304 + return $this->send_request( '/api/rules/htaccess', 'POST' );
305 + }
306 +
307 + /**
382 308 * Send the firewall logs to the API.
383 309 *
384 310 * @param array $logs
385 311 * @return array
@@ -447,47 +373,7 @@
447 373 *
448 374 * @return void
449 375 */
450 376 public function ping() {
451 - $this->send_request( '/api/ping', 'POST', [ 'firewall' => $this->get_option( 'patchstack_basic_firewall' ) == 1 ? 1 : 0 ] );
452 - }
453 -
454 - /**
455 - * Generate a secret value and send it to the Patchstack API for quick activation.
456 - *
457 - * @param string $secret
458 - * @return void
459 - */
460 - public function send_secret_token( $secret ) {
461 - $response = wp_remote_request(
462 - $this->plugin->api_url . '/api/secret',
463 - [
464 - 'method' => 'POST',
465 - 'timeout' => 60,
466 - 'redirection' => 5,
467 - 'httpversion' => '1.0',
468 - 'blocking' => true,
469 - 'headers' => [
470 - 'Source-Host' => get_site_url(),
471 - ],
472 - 'body' => [
473 - 'secret' => $secret,
474 - 'url' => get_site_url()
475 - ],
476 - 'cookies' => [],
477 - ]
478 - );
479 -
480 - // Check error or status code.
481 - if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) != 200 ) {
482 - return false;
483 - }
484 -
485 - // Determine if auto-activation succeeded.
486 - $result = json_decode( wp_remote_retrieve_body( $response ), true );
487 - if ($result && isset($result['activated'])) {
488 - return $result['activated'];
489 - }
490 -
491 - return false;
377 + $this->send_request( '/api/ping', 'POST', array( 'firewall' => $this->get_option( 'patchstack_basic_firewall' ) == 1 ? 1 : 0 ) );
492 378 }
493 379 }