PluginProbe
Patchstack – WordPress & Plugins Security / 2.1.3
Patchstack – WordPress & Plugins Security v2.1.3
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 +84 -241 2.3.72.1.3 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
@@ -75,32 +68,24 @@
75 68 * Fetch the API Token from API Server.
76 69 *
77 70 * @param string $clientid The API client ID.
78 71 * @param string $secretkey The API secret key.
79 - * @return string|array|object
72 + * @return string|array
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 - $client_id = $this->get_blog_option( $this->blog_id, 'patchstack_clientid', $clientid );
91 -
92 - // Decrypt the secret key, if it is encrypted.
93 - $client_secret = $this->get_blog_option( $this->blog_id, 'patchstack_secretkey', $secretkey );
94 - $client_nonce = $this->get_blog_option( $this->blog_id, 'patchstack_secretkey_nonce', false );
95 - if ( $client_nonce ) {
96 - $client_secret = $this->decrypt( $client_secret, $client_nonce );
97 - }
98 -
99 - // Make sure these values are set.
83 + $client_id = $this->get_blog_option( $this->blog_id, 'patchstack_clientid', false ) ? $this->get_blog_option( $this->blog_id, 'patchstack_clientid', false ) : $clientid;
84 + $client_secret = $this->get_blog_option( $this->blog_id, 'patchstack_secretkey', false ) ? $this->get_blog_option( $this->blog_id, 'patchstack_secretkey', false ) : $secretkey;
100 85 if ( empty( $client_id ) || empty( $client_secret ) ) {
101 86 $response_data->result = 'failed';
102 - $response_data->message = esc_attr__( 'API keys missing! Unable to obtain an access token.', 'patchstack' );
87 + $response_data->message = __( 'API keys missing! Unable to obtain an access token.', 'patchstack' );
103 88 return $response_data;
104 89 }
105 90
106 91 // Send a request to our server to obtain the access token.
@@ -105,38 +90,28 @@
105 90
106 91 // Send a request to our server to obtain the access token.
107 92 $response = wp_remote_post(
108 93 $this->plugin->auth_url . '/oauth/token',
109 - [
94 + array(
110 95 'method' => 'POST',
111 96 'timeout' => 60,
112 97 'redirection' => 5,
113 98 'httpversion' => '1.0',
114 99 'blocking' => true,
115 - 'headers' => [],
116 - 'body' => [
100 + 'headers' => array(),
101 + 'body' => array(
117 102 'client_id' => $client_id,
118 103 'client_secret' => $client_secret,
119 104 'grant_type' => 'client_credentials',
120 - ],
121 - 'cookies' => [],
122 - ]
105 + ),
106 + 'cookies' => array(),
107 + )
123 108 );
124 109
125 110 // 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 -
111 + if ( is_wp_error( $response ) ) {
136 112 $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;
113 + $response_data->message = __( 'Unexpected error! Unable to obtain an access token.', 'patchstack' ) . $response->get_error_message();
139 114 return $response_data;
140 115 }
141 116
142 117 // Parse the result.
@@ -149,86 +124,30 @@
149 124 // We need to know when the token expires.
150 125 // Defer to 'expires' if it is provided instead.
151 126 if ( isset( $result->expires_in ) ) {
152 127 if ( ! is_numeric( $result->expires_in ) ) {
153 - $response_data->result = 'failed';
154 128 $response_data->message = 'expires_in value must be an integer';
155 129 return $response_data;
156 130 }
157 131 $response_data->expiresin = $result->expires_in != 0 ? time() + $result->expires_in : 0;
132 + } elseif ( ! empty( $result->expires_in ) ) {
133 + // Some providers supply the seconds until expiration rather than
134 + // the exact timestamp. Take a best guess at which we received.
135 + $expires = $options['expires'];
136 + if ( ! $this->isExpirationTimestamp( $expires ) ) {
137 + $expires += time();
138 + }
139 + $response_data->expiresin = $expires;
158 140 }
159 -
160 141 return $response_data;
161 142 } elseif ( isset( $result->error ) ) {
162 143 $response_data->result = $result->error;
163 - $response_data->message = esc_attr__( 'Unexpected error! Unable to obtain an access token.', 'patchstack' ) . $result->message;
144 + $response_data->message = __( 'Unexpected error! Unable to obtain an access token.', 'patchstack' ) . $result->message;
164 145 return $response_data;
165 146 }
166 147 }
167 148
168 149 /**
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 150 * Checks if the API token has expired.
232 151 *
233 152 * @param integer $expiresin API token expiry.
234 153 * @return boolean If the token has expired.
@@ -233,12 +152,8 @@
233 152 * @param integer $expiresin API token expiry.
234 153 * @return boolean If the token has expired.
235 154 */
236 155 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 156 return ( $expiresin < ( time() + 30 ) );
242 157 }
243 158
244 159 /**
@@ -243,121 +158,75 @@
243 158
244 159 /**
245 160 * Retrieve the status of a license.
246 161 *
247 - * @param boolean $fetchPolicy Whether or not to fetch the policy settings.
248 162 * @return void|array
249 163 */
250 - public function update_license_status($fetchPolicy = false) {
164 + public function update_license_status() {
251 165 // Get current license status.
252 - $response = $this->send_request( '/api/license/verify' . ($fetchPolicy ? '?fetchPolicy=true' : ''), 'GET' );
166 + $response = $this->send_request( '/api/license/verify', 'GET' );
253 167
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 168 // Update the representing options.
264 - // Expiry date.
265 169 if ( isset( $response['expires_at'] ) ) {
266 170 $this->update_blog_option( $this->blog_id, 'patchstack_license_expiry', $response['expires_at'] );
267 171 }
268 172
269 - // Free vs Paid license.
270 173 if ( isset( $response['free'] ) ) {
271 174 $this->update_blog_option( $this->blog_id, 'patchstack_license_free', $response['free'] == false ? 0 : 1 );
272 175
273 176 if ( $response['free'] == true ) {
274 177 $this->update_blog_option( $this->blog_id, 'patchstack_show_settings', 0 );
275 - $this->update_blog_option( $this->blog_id, 'patchstack_firewall_rules_v3', '[]' );
276 - } else {
277 - $this->send_header_request();
278 178 }
279 179 }
280 180
281 - // Active subscription.
282 - if ( isset( $response['active'] ) ) {
283 - $this->update_blog_option( $this->blog_id, 'patchstack_license_activated', $response['active'] == true ? 1 : 0 );
181 + if ( isset( $response['active'] ) && $response['active'] == true ) {
182 + $this->update_blog_option( $this->blog_id, 'patchstack_license_activated', true );
284 183 }
285 184
286 - // Subscription class.
287 - if ( isset( $response['class'] ) ) {
288 - $this->update_blog_option( $this->blog_id, 'patchstack_subscription_class', $response['class'] );
289 - $this->update_blog_option( $this->blog_id, 'patchstack_last_license_check', time() );
290 - }
291 -
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 - }
297 -
298 - // Site ID.
299 - if ( isset( $response['site_id'] ) ) {
300 - $this->update_blog_option( $this->blog_id, 'patchstack_site_id', $response['site_id'] );
301 - }
302 -
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 - }
310 -
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 - }
319 - }
320 -
321 185 return $response;
322 186 }
323 187
324 188 /**
325 - * Send a request to our API for the IP address header.
326 - *
327 - * @return void
189 + * Send a request to the API with optionally POST data.
190 + *
191 + * @param string $url
192 + * @param string $request
193 + * @param array $data
194 + * @return void|array If successful array, otherwise void.
328 195 */
329 - public function send_header_request()
330 - {
331 - $header = get_option( 'patchstack_firewall_ip_header', '' );
332 - $computed = get_option( 'patchstack_ip_header_computed', 0 );
333 - $force = get_option( 'patchstack_ip_header_force_compute', 0 );
196 + public function send_request( $url, $request, $data = array() ) {
197 + // Attempt to get the access token.
198 + $token = $this->get_access_token();
199 + if ( empty( $token ) ) {
200 + return;
201 + }
334 202
335 - if ( ( $header == '' && ! $computed ) || $force ) {
336 - // Create an OTT token.
337 - $ott = md5( wp_generate_password( 32, true, true ) );
338 - update_option( 'patchstack_ott_action', $ott );
339 -
340 - // Tell our API.
341 - wp_remote_request(
342 - $this->plugin->api_url . '/api/header',
343 - [
344 - 'method' => 'POST',
345 - 'timeout' => 60,
346 - 'redirection' => 5,
347 - 'httpversion' => '1.0',
348 - 'blocking' => true,
349 - 'headers' => [
350 - 'Source-Host' => get_site_url(),
351 - ],
352 - 'body' => [
353 - 'token' => $ott,
354 - 'url' => get_site_url()
355 - ],
356 - 'cookies' => [],
357 - ]
358 - );
203 + // Send the remote request using the WordPress built-in method.
204 + $response = wp_remote_request(
205 + $this->plugin->api_url . $url,
206 + array(
207 + 'method' => $request,
208 + 'timeout' => 60,
209 + 'redirection' => 5,
210 + 'httpversion' => '1.0',
211 + 'blocking' => true,
212 + 'headers' => array(
213 + 'Authorization' => 'Bearer ' . $token,
214 + 'LicenseID' => $this->get_blog_option( $this->blog_id, 'patchstack_clientid', 0 ),
215 + 'Source-Host' => get_site_url(),
216 + ),
217 + 'body' => $data,
218 + 'cookies' => array(),
219 + )
220 + );
221 +
222 + // Check error or status code.
223 + if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) != 200 ) {
224 + $this->update_blog_option( $this->blog_id, 'patchstack_api_token', '' );
225 + return;
359 226 }
227 +
228 + return json_decode( wp_remote_retrieve_body( $response ), true );
360 229 }
361 230
362 231 /**
363 232 * Get the firewall rules.
@@ -364,9 +233,14 @@
364 233 *
365 234 * @return array The firewall rules.
366 235 */
367 236 public function post_firewall_rule_json() {
368 - return $this->send_request( '/api/get-rules/3', 'POST' );
237 + // If the request is coming from the API, fetch fresh rules.
238 + if ( isset( $_POST['webarx_refresh_rules'] ) ) {
239 + return $this->send_request( '/api/get-rules/2?bypass=cache', 'POST' );
240 + }
241 +
242 + return $this->send_request( '/api/get-rules/2', 'POST' );
369 243 }
370 244
371 245 /**
372 246 * Get the .htaccess rules.
@@ -378,8 +252,17 @@
378 252 return $this->send_request( '/api/rules', 'POST', $settings );
379 253 }
380 254
381 255 /**
256 + * Get the .htaccess firewall rules.
257 + *
258 + * @return array The .htaccess rules.
259 + */
260 + public function post_firewall_htaccess_rule() {
261 + return $this->send_request( '/api/rules/htaccess', 'POST' );
262 + }
263 +
264 + /**
382 265 * Send the firewall logs to the API.
383 266 *
384 267 * @param array $logs
385 268 * @return array
@@ -447,47 +330,7 @@
447 330 *
448 331 * @return void
449 332 */
450 333 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;
334 + $this->send_request( '/api/ping', 'POST', array( 'firewall' => $this->get_option( 'patchstack_basic_firewall' ) == 1 ? 1 : 0 ) );
492 335 }
493 336 }