PluginProbe
Patchstack – WordPress & Plugins Security / 2.2.1
Patchstack – WordPress & Plugins Security v2.2.1
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.1, at includes/api.php

384 lines 11.1 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 } else {
180 $this->send_header_request();
181 }
182 }
183
184 if ( isset( $response['active'] ) && $response['active'] == true ) {
185 $this->update_blog_option( $this->blog_id, 'patchstack_license_activated', true );
186 }
187
188 if ( isset( $response['class'] ) ) {
189 $this->update_blog_option( $this->blog_id, 'patchstack_subscription_class', $response['class'] );
190 $this->update_blog_option( $this->blog_id, 'patchstack_last_license_check', time() );
191 }
192
193 if ( isset( $response['managed'], $response['managed_string'] ) ) {
194 $this->update_blog_option( $this->blog_id, 'patchstack_managed', $response['managed'] );
195 $this->update_blog_option( $this->blog_id, 'patchstack_managed_text', $response['managed_string'] );
196 }
197
198 if ( isset( $response['site_id'] ) ) {
199 $this->update_blog_option( $this->blog_id, 'patchstack_site_id', $response['site_id'] );
200 }
201
202 return $response;
203 }
204
205 /**
206 * Send a request to the API with optionally POST data.
207 *
208 * @param string $url
209 * @param string $request
210 * @param array $data
211 * @return void|array If successful array, otherwise void.
212 */
213 public function send_request( $url, $request, $data = [] ) {
214 // Attempt to get the access token.
215 $token = $this->get_access_token();
216 if ( empty( $token ) ) {
217 return;
218 }
219
220 // Send the remote request using the WordPress built-in method.
221 $response = wp_remote_request(
222 $this->plugin->api_url . $url,
223 [
224 'method' => $request,
225 'timeout' => 60,
226 'redirection' => 5,
227 'httpversion' => '1.0',
228 'blocking' => true,
229 'headers' => [
230 'Authorization' => 'Bearer ' . $token,
231 'LicenseID' => $this->get_blog_option( $this->blog_id, 'patchstack_clientid', 0 ),
232 'Source-Host' => get_site_url(),
233 ],
234 'body' => $data,
235 'cookies' => [],
236 ]
237 );
238
239 // Check error or status code.
240 if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) != 200 ) {
241 $this->update_blog_option( $this->blog_id, 'patchstack_api_token', '' );
242 return;
243 }
244
245 return json_decode( wp_remote_retrieve_body( $response ), true );
246 }
247
248 /**
249 * Send a request to our API for the IP address header.
250 */
251 public function send_header_request()
252 {
253 $header = get_option( 'patchstack_firewall_ip_header', '' );
254 $computed = get_option( 'patchstack_ip_header_computed', 0 );
255
256 if ( $header == '' && ! $computed ) {
257 // Create an OTT token.
258 $ott = md5( wp_generate_password( 32, true, true ) );
259 update_option( 'patchstack_ott_action', $ott );
260
261 // Tell our API.
262 wp_remote_request(
263 $this->plugin->api_url . '/api/header',
264 [
265 'method' => 'POST',
266 'timeout' => 60,
267 'redirection' => 5,
268 'httpversion' => '1.0',
269 'blocking' => true,
270 'headers' => [
271 'Source-Host' => get_site_url(),
272 ],
273 'body' => [
274 'token' => $ott,
275 'url' => get_site_url()
276 ],
277 'cookies' => [],
278 ]
279 );
280 }
281 }
282
283 /**
284 * Get the firewall rules.
285 *
286 * @return array The firewall rules.
287 */
288 public function post_firewall_rule_json() {
289 return $this->send_request( '/api/get-rules/3', 'POST' );
290 }
291
292 /**
293 * Get the .htaccess rules.
294 *
295 * @param array $settings The settings on which .htaccess rules to get.
296 * @return array The .htaccess rules.
297 */
298 public function post_firewall_rule( $settings ) {
299 return $this->send_request( '/api/rules', 'POST', $settings );
300 }
301
302 /**
303 * Get the .htaccess firewall rules.
304 *
305 * @return array The .htaccess rules.
306 */
307 public function post_firewall_htaccess_rule() {
308 return $this->send_request( '/api/rules/htaccess', 'POST' );
309 }
310
311 /**
312 * Send the firewall logs to the API.
313 *
314 * @param array $logs
315 * @return array
316 */
317 public function upload_firewall_logs( $logs ) {
318 return $this->send_request( '/api/logs/log', 'POST', $logs );
319 }
320
321 /**
322 * Send the activity logs to the server.
323 *
324 * @param array $logs
325 * @return array
326 */
327 public function upload_activity_logs( $logs ) {
328 return $this->send_request( '/api/activity/log', 'POST', $logs );
329 }
330
331 /**
332 * Send WordPress core, theme, plugins versions and information to the API.
333 *
334 * @param array $software
335 * @return array
336 */
337 public function upload_software( $software ) {
338 return $this->send_request( '/api/sw/json', 'POST', $software );
339 }
340
341 /**
342 * Update the firewall status.
343 *
344 * @param array $status
345 * @return array
346 */
347 public function update_firewall_status( $status ) {
348 if ( $this->get_option( 'patchstack_license_free', 0 ) == 1 ) {
349 return;
350 }
351
352 return $this->send_request( '/api/firewall/update/status', 'POST', $status );
353 }
354
355 /**
356 * Update the URL on the API.
357 *
358 * @param array $url The current URL of the site.
359 * @return array
360 */
361 public function update_url( $url ) {
362 return $this->send_request( '/api/plugin/update/url', 'POST', $url );
363 }
364
365 /**
366 * Send list of sites and get the id and secret key in response.
367 *
368 * @param array $sites
369 * @return array
370 */
371 public function get_site_licenses( $sites ) {
372 return $this->send_request( '/api/multisite-keys', 'POST', $sites );
373 }
374
375 /**
376 * Send a ping to the Patchstack API every 3 hours to make sure that the plugin is still running.
377 *
378 * @return void
379 */
380 public function ping() {
381 $this->send_request( '/api/ping', 'POST', [ 'firewall' => $this->get_option( 'patchstack_basic_firewall' ) == 1 ? 1 : 0 ] );
382 }
383 }
384