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

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