PluginProbe
Patchstack – WordPress & Plugins Security / 2.2.8
Patchstack – WordPress & Plugins Security v2.2.8
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
patchstack / includes / api.php

api.php in Patchstack – WordPress & Plugins Security 2.2.8, at includes/api.php

385 lines 11.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // Do not allow the file to be called directly.
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 /**
9 * This class is used as a base for communicating with the Patchstack API.
10 */
11 class P_Api extends P_Core {
12
13 /**
14 * @var integer The current blog id.
15 */
16 public $blog_id;
17
18 /**
19 * Add the actions required for the API.
20 *
21 * @param Patchstack $core
22 * @return void
23 */
24 public function __construct( $core ) {
25 parent::__construct( $core );
26 $this->blog_id = get_current_blog_id();
27 add_action( 'patchstack_update_license_status', [ $this, 'update_license_status' ] );
28 add_action( 'patchstack_send_ping', [ $this, 'ping' ] );
29 }
30
31 /**
32 * Get the API token.
33 *
34 * @param string $clientid The API client ID.
35 * @param string $secretkey The API secret key.
36 * @param boolean $fresh Whether or not to get a fresh token.
37 * @return null|string
38 */
39 public function get_access_token( $clientid = '', $secretkey = '', $fresh = false ) {
40 // Get current access token, if it exists.
41 $token_data = $this->get_blog_option( $this->blog_id, 'patchstack_api_token', false );
42
43 // If we do not need a fresh token, get the current one if it's not expired.
44 if ( ! $fresh && isset( $token_data['token'] ) && ! $this->has_expired( $token_data['expiresin'] ) ) {
45 return $token_data['token'];
46 }
47
48 // Call API and get the new access token.
49 $response = $this->fetch_access_token( $clientid, $secretkey );
50 if ( $response && $response->result == 'success' ) {
51 $this->update_blog_option(
52 $this->blog_id,
53 'patchstack_api_token',
54 [
55 'token' => $response->message,
56 'expiresin' => $response->expiresin,
57 ]
58 );
59 return $response->message;
60 }
61
62 // If we reach this, it means we were not able to get the access token.
63 $this->update_blog_option( $this->blog_id, 'patchstack_api_token', '' );
64 return null;
65 }
66
67 /**
68 * Fetch the API Token from API Server.
69 *
70 * @param string $clientid The API client ID.
71 * @param string $secretkey The API secret key.
72 * @return string|array
73 */
74 public function fetch_access_token( $clientid = '', $secretkey = '' ) {
75 // Skeleton for the response data.
76 $response_data = (object) [
77 'result' => '',
78 'message' => '',
79 'expiresin' => '',
80 ];
81
82 // Determine if the license id/key is set.
83 $client_id = $this->get_blog_option( $this->blog_id, 'patchstack_clientid', $clientid );
84
85 // Decrypt the secret key, if it is encrypted.
86 $client_secret = $this->get_blog_option( $this->blog_id, 'patchstack_secretkey', $secretkey );
87 $client_nonce = $this->get_blog_option( $this->blog_id, 'patchstack_secretkey_nonce', false );
88 if ( $client_nonce ) {
89 $client_secret = $this->decrypt( $client_secret, $client_nonce );
90 }
91
92 // Make sure these values are set.
93 if ( empty( $client_id ) || empty( $client_secret ) ) {
94 $response_data->result = 'failed';
95 $response_data->message = __( 'API keys missing! Unable to obtain an access token.', 'patchstack' );
96 return $response_data;
97 }
98
99 // Send a request to our server to obtain the access token.
100 $response = wp_remote_post(
101 $this->plugin->auth_url . '/oauth/token',
102 [
103 'method' => 'POST',
104 'timeout' => 60,
105 'redirection' => 5,
106 'httpversion' => '1.0',
107 'blocking' => true,
108 'headers' => [],
109 'body' => [
110 'client_id' => $client_id,
111 'client_secret' => $client_secret,
112 'grant_type' => 'client_credentials',
113 ],
114 'cookies' => [],
115 ]
116 );
117
118 // Stop if we received an error from the API.
119 if ( is_wp_error( $response ) ) {
120 $response_data->result = 'failed';
121 $response_data->message = __( 'Unexpected error! Unable to obtain an access token.', 'patchstack' ) . $response->get_error_message();
122 return $response_data;
123 }
124
125 // Parse the result.
126 $result = json_decode( wp_remote_retrieve_body( $response ) );
127 if ( isset( $result->access_token ) ) {
128 $response_data->result = 'success';
129 $response_data->message = $result->access_token;
130 $response_data->expiresin = $result->expires_in;
131
132 // We need to know when the token expires.
133 // Defer to 'expires' if it is provided instead.
134 if ( isset( $result->expires_in ) ) {
135 if ( ! is_numeric( $result->expires_in ) ) {
136 $response_data->message = 'expires_in value must be an integer';
137 return $response_data;
138 }
139 $response_data->expiresin = $result->expires_in != 0 ? time() + $result->expires_in : 0;
140 }
141
142 return $response_data;
143 } elseif ( isset( $result->error ) ) {
144 $response_data->result = $result->error;
145 $response_data->message = __( 'Unexpected error! Unable to obtain an access token.', 'patchstack' ) . $result->message;
146 return $response_data;
147 }
148 }
149
150 /**
151 * Checks if the API token has expired.
152 *
153 * @param integer $expiresin API token expiry.
154 * @return boolean If the token has expired.
155 */
156 public function has_expired( $expiresin ) {
157 return ( $expiresin < ( time() + 30 ) );
158 }
159
160 /**
161 * Retrieve the status of a license.
162 *
163 * @return void|array
164 */
165 public function update_license_status() {
166 // Get current license status.
167 $response = $this->send_request( '/api/license/verify', 'GET' );
168
169 // Update the representing options.
170 if ( isset( $response['expires_at'] ) ) {
171 $this->update_blog_option( $this->blog_id, 'patchstack_license_expiry', $response['expires_at'] );
172 }
173
174 if ( isset( $response['free'] ) ) {
175 $this->update_blog_option( $this->blog_id, 'patchstack_license_free', $response['free'] == false ? 0 : 1 );
176
177 if ( $response['free'] == true ) {
178 $this->update_blog_option( $this->blog_id, 'patchstack_show_settings', 0 );
179 $this->update_blog_option( $this->blog_id, 'patchstack_firewall_rules_v3', '[]' );
180 } else {
181 $this->send_header_request();
182 }
183 }
184
185 if ( isset( $response['active'] ) ) {
186 $this->update_blog_option( $this->blog_id, 'patchstack_license_activated', $response['active'] == true );
187 }
188
189 if ( isset( $response['class'] ) ) {
190 $this->update_blog_option( $this->blog_id, 'patchstack_subscription_class', $response['class'] );
191 $this->update_blog_option( $this->blog_id, 'patchstack_last_license_check', time() );
192 }
193
194 if ( isset( $response['managed'], $response['managed_string'] ) ) {
195 $this->update_blog_option( $this->blog_id, 'patchstack_managed', $response['managed'] );
196 $this->update_blog_option( $this->blog_id, 'patchstack_managed_text', $response['managed_string'] );
197 }
198
199 if ( isset( $response['site_id'] ) ) {
200 $this->update_blog_option( $this->blog_id, 'patchstack_site_id', $response['site_id'] );
201 }
202
203 return $response;
204 }
205
206 /**
207 * Send a request to the API with optionally POST data.
208 *
209 * @param string $url
210 * @param string $request
211 * @param array $data
212 * @return void|array If successful array, otherwise void.
213 */
214 public function send_request( $url, $request, $data = [] ) {
215 // Attempt to get the access token.
216 $token = $this->get_access_token();
217 if ( empty( $token ) ) {
218 return;
219 }
220
221 // Send the remote request using the WordPress built-in method.
222 $response = wp_remote_request(
223 $this->plugin->api_url . $url,
224 [
225 'method' => $request,
226 'timeout' => 60,
227 'redirection' => 5,
228 'httpversion' => '1.0',
229 'blocking' => true,
230 'headers' => [
231 'Authorization' => 'Bearer ' . $token,
232 'LicenseID' => $this->get_blog_option( $this->blog_id, 'patchstack_clientid', 0 ),
233 'Source-Host' => get_site_url(),
234 ],
235 'body' => $data,
236 'cookies' => [],
237 ]
238 );
239
240 // Check error or status code.
241 if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) != 200 ) {
242 $this->update_blog_option( $this->blog_id, 'patchstack_api_token', '' );
243 return;
244 }
245
246 return json_decode( wp_remote_retrieve_body( $response ), true );
247 }
248
249 /**
250 * Send a request to our API for the IP address header.
251 */
252 public function send_header_request()
253 {
254 $header = get_option( 'patchstack_firewall_ip_header', '' );
255 $computed = get_option( 'patchstack_ip_header_computed', 0 );
256
257 if ( $header == '' && ! $computed ) {
258 // Create an OTT token.
259 $ott = md5( wp_generate_password( 32, true, true ) );
260 update_option( 'patchstack_ott_action', $ott );
261
262 // Tell our API.
263 wp_remote_request(
264 $this->plugin->api_url . '/api/header',
265 [
266 'method' => 'POST',
267 'timeout' => 60,
268 'redirection' => 5,
269 'httpversion' => '1.0',
270 'blocking' => true,
271 'headers' => [
272 'Source-Host' => get_site_url(),
273 ],
274 'body' => [
275 'token' => $ott,
276 'url' => get_site_url()
277 ],
278 'cookies' => [],
279 ]
280 );
281 }
282 }
283
284 /**
285 * Get the firewall rules.
286 *
287 * @return array The firewall rules.
288 */
289 public function post_firewall_rule_json() {
290 return $this->send_request( '/api/get-rules/3', 'POST' );
291 }
292
293 /**
294 * Get the .htaccess rules.
295 *
296 * @param array $settings The settings on which .htaccess rules to get.
297 * @return array The .htaccess rules.
298 */
299 public function post_firewall_rule( $settings ) {
300 return $this->send_request( '/api/rules', 'POST', $settings );
301 }
302
303 /**
304 * Get the .htaccess firewall rules.
305 *
306 * @return array The .htaccess rules.
307 */
308 public function post_firewall_htaccess_rule() {
309 return $this->send_request( '/api/rules/htaccess', 'POST' );
310 }
311
312 /**
313 * Send the firewall logs to the API.
314 *
315 * @param array $logs
316 * @return array
317 */
318 public function upload_firewall_logs( $logs ) {
319 return $this->send_request( '/api/logs/log', 'POST', $logs );
320 }
321
322 /**
323 * Send the activity logs to the server.
324 *
325 * @param array $logs
326 * @return array
327 */
328 public function upload_activity_logs( $logs ) {
329 return $this->send_request( '/api/activity/log', 'POST', $logs );
330 }
331
332 /**
333 * Send WordPress core, theme, plugins versions and information to the API.
334 *
335 * @param array $software
336 * @return array
337 */
338 public function upload_software( $software ) {
339 return $this->send_request( '/api/sw/json', 'POST', $software );
340 }
341
342 /**
343 * Update the firewall status.
344 *
345 * @param array $status
346 * @return array
347 */
348 public function update_firewall_status( $status ) {
349 if ( $this->get_option( 'patchstack_license_free', 0 ) == 1 ) {
350 return;
351 }
352
353 return $this->send_request( '/api/firewall/update/status', 'POST', $status );
354 }
355
356 /**
357 * Update the URL on the API.
358 *
359 * @param array $url The current URL of the site.
360 * @return array
361 */
362 public function update_url( $url ) {
363 return $this->send_request( '/api/plugin/update/url', 'POST', $url );
364 }
365
366 /**
367 * Send list of sites and get the id and secret key in response.
368 *
369 * @param array $sites
370 * @return array
371 */
372 public function get_site_licenses( $sites ) {
373 return $this->send_request( '/api/multisite-keys', 'POST', $sites );
374 }
375
376 /**
377 * Send a ping to the Patchstack API every 3 hours to make sure that the plugin is still running.
378 *
379 * @return void
380 */
381 public function ping() {
382 $this->send_request( '/api/ping', 'POST', [ 'firewall' => $this->get_option( 'patchstack_basic_firewall' ) == 1 ? 1 : 0 ] );
383 }
384 }
385