PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.6.0
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.6.0
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 / admin / includes / Security / SecurityMiddleware.php
commercebird / admin / includes / Security Last commit date
SecurityConfig.php 9 months ago SecurityMiddleware.php 9 months ago index.php 11 months ago
SecurityMiddleware.php
224 lines
1 <?php
2 /**
3 * Security Middleware Trait for CommerceBird
4 *
5 * @package CommerceBird\Admin\Security
6 */
7
8 namespace CommerceBird\Admin\Security;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit;
12 }
13
14 trait SecurityMiddleware {
15
16 /**
17 * Apply security headers to responses.
18 */
19 private function apply_security_headers(): void {
20 $headers = SecurityConfig::get_security_headers();
21
22 foreach ( $headers as $header => $value ) {
23 if ( ! headers_sent() ) {
24 header( $header . ': ' . $value );
25 }
26 }
27 }
28
29 /**
30 * Validate input data against known patterns.
31 *
32 * @param array $data Input data to validate.
33 * @param array $field_types Field type mappings.
34 * @return bool True if valid, false otherwise.
35 */
36 private function validate_input_patterns( array $data, array $field_types ): bool {
37 foreach ( $field_types as $field => $type ) {
38 if ( ! isset( $data[ $field ] ) ) {
39 continue;
40 }
41
42 $pattern = SecurityConfig::get_validation_pattern( $type );
43 if ( $pattern && ! preg_match( $pattern, $data[ $field ] ) ) {
44 return false;
45 }
46 }
47
48 return true;
49 }
50
51 /**
52 * Check user capability for specific action.
53 *
54 * @param string $action Action being performed.
55 * @return bool True if user has capability, false otherwise.
56 */
57 private function check_user_capability( string $action ): bool {
58 $required_cap = SecurityConfig::get_required_capability( $action );
59
60 if ( ! $required_cap ) {
61 return true; // No specific capability required.
62 }
63
64 return current_user_can( $required_cap );
65 }
66
67 /**
68 * Advanced rate limiting with multiple time windows.
69 *
70 * @param string $action Action being rate limited.
71 * @return bool True if within limits, false if exceeded.
72 */
73 private function check_advanced_rate_limit( string $action ): bool {
74 $user_id = get_current_user_id();
75 $ip = $_SERVER['REMOTE_ADDR'];
76 $base_key = "rate_limit_{$action}_{$user_id}_{$ip}";
77
78 // Check per-minute limit.
79 $minute_key = $base_key . '_minute';
80 $minute_requests = get_transient( $minute_key );
81
82 if ( false === $minute_requests ) {
83 set_transient( $minute_key, 1, MINUTE_IN_SECONDS );
84 } else {
85 if ( $minute_requests >= SecurityConfig::RATE_LIMIT_REQUESTS_PER_MINUTE ) {
86 return false;
87 }
88 set_transient( $minute_key, $minute_requests + 1, MINUTE_IN_SECONDS );
89 }
90
91 // Check per-hour limit.
92 $hour_key = $base_key . '_hour';
93 $hour_requests = get_transient( $hour_key );
94
95 if ( false === $hour_requests ) {
96 set_transient( $hour_key, 1, HOUR_IN_SECONDS );
97 } else {
98 if ( $hour_requests >= SecurityConfig::RATE_LIMIT_REQUESTS_PER_HOUR ) {
99 return false;
100 }
101 set_transient( $hour_key, $hour_requests + 1, HOUR_IN_SECONDS );
102 }
103
104 return true;
105 }
106
107 /**
108 * Log security event with context.
109 *
110 * @param string $event_type Type of security event.
111 * @param array $context Additional context data.
112 */
113 private function log_security_event( string $event_type, array $context = array() ): void {
114 if ( ! SecurityConfig::LOG_SECURITY_EVENTS ) {
115 return;
116 }
117
118 $event_data = array(
119 'timestamp' => current_time( 'mysql' ),
120 'event_type' => $event_type,
121 'user_id' => get_current_user_id(),
122 'ip_address' => $_SERVER['REMOTE_ADDR'],
123 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
124 'context' => $context,
125 );
126
127 $security_log = get_transient( 'commercebird_security_log' );
128 if ( ! is_array( $security_log ) ) {
129 $security_log = array();
130 }
131
132 $security_log[] = $event_data;
133
134 // Maintain log size limits.
135 if ( count( $security_log ) > SecurityConfig::LOG_MAX_ENTRIES ) {
136 $security_log = array_slice( $security_log, -SecurityConfig::LOG_MAX_ENTRIES );
137 }
138
139 set_transient( 'commercebird_security_log', $security_log, DAY_IN_SECONDS * SecurityConfig::LOG_RETENTION_DAYS );
140 }
141
142 /**
143 * Sanitize and validate subscription data.
144 *
145 * @param array $data Raw subscription data.
146 * @return array Sanitized and validated data.
147 */
148 private function sanitize_subscription_response( array $data ): array {
149 $allowed_fields = array(
150 'status',
151 'plan',
152 'currency',
153 'total',
154 'needs_payment',
155 'next_payment_date_gmt',
156 'variation_id',
157 );
158
159 $sanitized = array();
160
161 foreach ( $allowed_fields as $field ) {
162 if ( isset( $data[ $field ] ) ) {
163 if ( is_array( $data[ $field ] ) ) {
164 $sanitized[ $field ] = array_map( 'sanitize_text_field', $data[ $field ] );
165 } else {
166 $sanitized[ $field ] = sanitize_text_field( $data[ $field ] );
167 }
168 }
169 }
170
171 // Handle billing data separately with stricter controls.
172 if ( isset( $data['billing'] ) && is_array( $data['billing'] ) ) {
173 $sanitized['billing'] = array();
174
175 // Only include essential billing fields.
176 if ( isset( $data['billing']['email'] ) ) {
177 $email = sanitize_email( $data['billing']['email'] );
178 if ( is_email( $email ) ) {
179 $sanitized['billing']['email'] = $email;
180 }
181 }
182 }
183
184 return $sanitized;
185 }
186
187 /**
188 * Check if request is from a suspicious source.
189 *
190 * @return bool True if suspicious, false otherwise.
191 */
192 private function is_suspicious_request(): bool {
193 // Check for missing or suspicious user agent.
194 $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
195 if ( empty( $user_agent ) || strlen( $user_agent ) < 10 ) {
196 return true;
197 }
198
199 // Check for suspicious patterns in user agent.
200 $suspicious_patterns = array(
201 '/bot/i',
202 '/crawler/i',
203 '/spider/i',
204 '/scraper/i',
205 );
206
207 foreach ( $suspicious_patterns as $pattern ) {
208 if ( preg_match( $pattern, $user_agent ) ) {
209 return true;
210 }
211 }
212
213 // Check referer header.
214 $referer = $_SERVER['HTTP_REFERER'] ?? '';
215 $site_url = site_url();
216
217 if ( ! empty( $referer ) && strpos( $referer, $site_url ) !== 0 ) {
218 return true;
219 }
220
221 return false;
222 }
223 }
224