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

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