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