PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.4.4
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.4.4
3.0.3 3.0.2 3.0.1 trunk 2.2.14 2.2.15 2.2.16 2.2.17 2.2.18 2.2.19 2.3.0 2.3.1 2.3.10 2.3.11 2.3.12 2.3.13 2.3.14 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.3.9 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.7.91 2.7.92 2.7.93 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0
commercebird / includes / classes / apis / trait-api-permission.php
commercebird / includes / classes / apis Last commit date
class-api-for-cmbird.php 1 year ago class-api-for-exact-webhooks.php 1 year ago class-api-for-product-webhook.php 11 months ago class-api-for-shipping-status.php 1 year ago class-api-for-woo-order.php 1 year ago class-api-for-zoho-inventory.php 1 year ago class-commercebird-list-items-api-controller.php 1 year ago class-commercebird-media-api-controller.php 1 year ago class-commercebird-metadata-controller.php 1 year ago index.php 1 year ago trait-api-permission.php 11 months ago
trait-api-permission.php
301 lines
1 <?php
2
3 namespace CommerceBird\API;
4
5 use Exception;
6 // use CommerceBird\Admin\Actions\Ajax\ZohoInventoryAjax;
7 use CommerceBird\Admin\Actions\Ajax\SettingsAjax;
8 use WP_Error;
9 use WP_REST_Request;
10 use WP_REST_Response;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 trait Api {
17
18 private static string $namespace = 'v2';
19
20 private string $empty_response = 'No data found from webhook. check on ' . __METHOD__ . ' of ' . __CLASS__ . ' on line ' . __LINE__;
21
22 public static function endpoint(): string {
23 return get_rest_url() . self::$namespace . '/' . self::$endpoint;
24 }
25
26 public function permission_check() {
27 // Get all headers
28 $headers = getallheaders();
29 // Check if the Authorization header is present
30 if ( isset( $headers['Authorization'] ) ) {
31 $authorization = $headers['Authorization'];
32 if ( ! password_verify( 'commercebird-zi-webhook-token', $authorization ) ) {
33 return new WP_Error(
34 'rest_forbidden',
35 'You are not allowed to access this endpoint',
36 array(
37 'status' => 400,
38 'header' => $authorization,
39 )
40 );
41 }
42 }
43 // $subscription = ZohoInventoryAjax::instance()->get_subscription_data();
44 $subscription = SettingsAjax::instance()->get_subscription_data();
45
46 if ( isset( $subscription['plan'] ) ) {
47 $subscription_plan = implode( ' ', $subscription['plan'] );
48 if ( stripos( $subscription_plan, 'Premium' ) === false ) {
49 return new WP_Error(
50 'rest_forbidden',
51 $subscription_plan,
52 array(
53 'status' => 403,
54 'data' => strpos( 'Premium', $subscription_plan ),
55 )
56 );
57 }
58 return true;
59 }
60 }
61 public function handle( WP_REST_Request $request ) {
62 // Rate limiting check with queuing support
63 $rate_limit_response = $this->check_rate_limit_with_queue();
64 if ( is_wp_error( $rate_limit_response ) ) {
65 $response = new WP_REST_Response();
66 $response->set_data( $rate_limit_response->get_error_message() );
67 $response->set_status( 429 ); // Too Many Requests
68 return rest_ensure_response( $response );
69 }
70
71 $response = new WP_REST_Response();
72 $response->set_data( $this->empty_response );
73 $response->set_status( 404 );
74 $data = $request->get_json_params();
75 if ( empty( $data ) ) {
76 return rest_ensure_response( $response );
77 }
78 if ( array_key_exists( 'JSONString', $data ) ) {
79 $data = str_replace( '\\', '', $data['JSONString'] );
80 }
81
82 // Create a unique key based on the payload
83 $payload_hash = wp_hash( wp_json_encode( $data ) );
84 $lock_key = 'cmbird_processing_payload_' . $payload_hash;
85
86 // Check and set lock using update_option
87 $lock_acquired = add_option( $lock_key, time() );
88 if ( ! $lock_acquired ) {
89 $response->set_data( 'Duplicate payload ignored. Already being processed.' );
90 $response->set_status( 200 );
91 return rest_ensure_response( $response );
92 }
93
94 // Process the payload
95 if ( ! empty( $data ) ) {
96 try {
97 $response = $this->process( $data );
98 } catch (Exception $exception) {
99 $response->set_data( $exception->getMessage() );
100 $response->set_status( 500 );
101 }
102 }
103
104 // Clean up the lock after processing
105 delete_option( $lock_key );
106 return rest_ensure_response( $response );
107 }
108
109 /**
110 * Rate limiting with queue support
111 * Queues requests when limits are reached instead of rejecting immediately
112 */
113 private function check_rate_limit_with_queue() {
114 $current_time = time();
115 $window_size = 60; // 1 minute window
116 $max_requests_per_ip = 20; // Reduced per IP limit
117 $max_requests_global = 60; // Reduced global limit
118 $max_queue_size = 50; // Maximum queued requests
119
120 // Get client IP
121 $client_ip = $this->get_client_ip();
122 $ip_key = 'cmbird_rate_limit_ip_' . md5( $client_ip );
123 $global_key = 'cmbird_rate_limit_global';
124 $queue_key = 'cmbird_request_queue';
125
126 // Check current queue size
127 $current_queue = get_option( $queue_key, array() );
128 $current_queue = is_array( $current_queue ) ? $current_queue : array();
129
130 // Clean expired queue items
131 $current_queue = array_filter( $current_queue, function ($item) use ($current_time) {
132 return ( $current_time - $item['timestamp'] ) < 300; // 5 minute queue expiry
133 } );
134
135 // Check IP-based rate limit
136 $ip_requests = get_transient( $ip_key );
137 if ( false === $ip_requests ) {
138 $ip_requests = array();
139 }
140
141 // Clean old requests (outside window)
142 $ip_requests = array_filter( $ip_requests, function ($timestamp) use ($current_time, $window_size) {
143 return ( $current_time - $timestamp ) < $window_size;
144 } );
145
146 // Check global rate limit
147 $global_requests = get_transient( $global_key );
148 if ( false === $global_requests ) {
149 $global_requests = array();
150 }
151
152 // Clean old requests (outside window)
153 $global_requests = array_filter( $global_requests, function ($timestamp) use ($current_time, $window_size) {
154 return ( $current_time - $timestamp ) < $window_size;
155 } );
156
157 // Check if limits are exceeded
158 $ip_limit_exceeded = count( $ip_requests ) >= $max_requests_per_ip;
159 $global_limit_exceeded = count( $global_requests ) >= $max_requests_global;
160
161 if ( $ip_limit_exceeded || $global_limit_exceeded ) {
162 // Try to queue the request if queue isn't full
163 if ( count( $current_queue ) < $max_queue_size ) {
164 $queue_item = array(
165 'ip' => $client_ip,
166 'timestamp' => $current_time,
167 'data' => $_POST // Store request data
168 );
169 $current_queue[] = $queue_item;
170 update_option( $queue_key, $current_queue );
171
172 // Schedule processing of queue if not already scheduled
173 if ( ! wp_next_scheduled( 'cmbird_process_webhook_queue' ) ) {
174 wp_schedule_single_event( $current_time + 30, 'cmbird_process_webhook_queue' );
175 }
176
177 return new WP_Error(
178 'request_queued',
179 'Request rate limit reached. Your request has been queued and will be processed shortly.',
180 array( 'status' => 202 ) // Accepted
181 );
182 } else {
183 return new WP_Error(
184 'queue_full',
185 'Rate limit exceeded and queue is full. Please try again later.',
186 array( 'status' => 503 ) // Service Unavailable
187 );
188 }
189 }
190
191 // Add current request to counters
192 $ip_requests[] = $current_time;
193 $global_requests[] = $current_time;
194
195 // Store updated counters with expiration
196 set_transient( $ip_key, $ip_requests, $window_size );
197 set_transient( $global_key, $global_requests, $window_size );
198
199 return true;
200 }
201
202 /**
203 * Rate limiting implementation
204 * Limits requests per IP and globally
205 */
206 private function check_rate_limit() {
207 $current_time = time();
208 $window_size = 60; // 1 minute window
209 $max_requests_per_ip = 30; // Per IP limit
210 $max_requests_global = 100; // Global limit
211
212 // Get client IP
213 $client_ip = $this->get_client_ip();
214 $ip_key = 'cmbird_rate_limit_ip_' . md5( $client_ip );
215 $global_key = 'cmbird_rate_limit_global';
216
217 // Check IP-based rate limit
218 $ip_requests = get_transient( $ip_key );
219 if ( false === $ip_requests ) {
220 $ip_requests = array();
221 }
222
223 // Clean old requests (outside window)
224 $ip_requests = array_filter( $ip_requests, function ($timestamp) use ($current_time, $window_size) {
225 return ( $current_time - $timestamp ) < $window_size;
226 } );
227
228 // Check if IP limit exceeded
229 if ( count( $ip_requests ) >= $max_requests_per_ip ) {
230 return new WP_Error(
231 'rate_limit_exceeded',
232 'Rate limit exceeded for this IP. Maximum ' . $max_requests_per_ip . ' requests per minute allowed.',
233 array( 'status' => 429 )
234 );
235 }
236
237 // Check global rate limit
238 $global_requests = get_transient( $global_key );
239 if ( false === $global_requests ) {
240 $global_requests = array();
241 }
242
243 // Clean old requests (outside window)
244 $global_requests = array_filter( $global_requests, function ($timestamp) use ($current_time, $window_size) {
245 return ( $current_time - $timestamp ) < $window_size;
246 } );
247
248 // Check if global limit exceeded
249 if ( count( $global_requests ) >= $max_requests_global ) {
250 return new WP_Error(
251 'global_rate_limit_exceeded',
252 'Global rate limit exceeded. Maximum ' . $max_requests_global . ' requests per minute allowed.',
253 array( 'status' => 429 )
254 );
255 }
256
257 // Add current request to counters
258 $ip_requests[] = $current_time;
259 $global_requests[] = $current_time;
260
261 // Store updated counters with expiration
262 set_transient( $ip_key, $ip_requests, $window_size );
263 set_transient( $global_key, $global_requests, $window_size );
264
265 return true;
266 }
267
268 /**
269 * Get client IP address safely
270 */
271 private function get_client_ip() {
272 // Check for various headers that might contain the real IP
273 $ip_headers = array(
274 'HTTP_X_FORWARDED_FOR',
275 'HTTP_X_REAL_IP',
276 'HTTP_CLIENT_IP',
277 'HTTP_X_FORWARDED',
278 'HTTP_FORWARDED_FOR',
279 'HTTP_FORWARDED',
280 'REMOTE_ADDR'
281 );
282
283 foreach ( $ip_headers as $header ) {
284 if ( ! empty( $_SERVER[ $header ] ) ) {
285 $ip = $_SERVER[ $header ];
286 // Handle comma-separated IPs (X-Forwarded-For)
287 if ( strpos( $ip, ',' ) !== false ) {
288 $ip = trim( explode( ',', $ip )[0] );
289 }
290 // Validate IP
291 if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
292 return $ip;
293 }
294 }
295 }
296
297 // Fallback to REMOTE_ADDR
298 return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
299 }
300 }
301