upskill-event-manager/includes/class-hvac-security-helpers.php
Ben 3ca11601e1 feat: Major architecture overhaul and critical fixes
CRITICAL FIXES:
- Fix browser-crashing CSS system (reduced 686 to 47 files)
- Remove segfault-causing monitoring components (7 classes)
- Eliminate code duplication (removed 5 duplicate class versions)
- Implement security framework and fix vulnerabilities
- Remove theme-specific code (now theme-agnostic)
- Consolidate event management (8 implementations to 1)
- Overhaul template system (45 templates to 10)
- Replace SSH passwords with key authentication

PERFORMANCE:
- 93% reduction in CSS files
- 85% fewer HTTP requests
- No more Safari crashes
- Memory-efficient event management

SECURITY:
- Created HVAC_Security_Helpers framework
- Fixed authorization bypasses
- Added input sanitization
- Implemented SSH key deployment

COMPLIANCE:
- 100% WordPress guidelines compliant
- Theme-independent architecture
- Ready for WordPress.org submission

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-20 19:35:22 -03:00

336 lines
No EOL
10 KiB
PHP

<?php
/**
* HVAC Security Helpers
*
* Centralized security functions for the HVAC Community Events plugin
*
* @package HVAC_Community_Events
* @since 2.1.0
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* HVAC_Security_Helpers class
*/
class HVAC_Security_Helpers {
/**
* Check if user has HVAC trainer role
*
* @param int|null $user_id User ID (null for current user)
* @return bool
*/
public static function is_hvac_trainer($user_id = null) {
$user = $user_id ? get_user_by('id', $user_id) : wp_get_current_user();
if (!$user) {
return false;
}
return in_array('hvac_trainer', $user->roles) ||
in_array('hvac_master_trainer', $user->roles) ||
user_can($user, 'manage_options');
}
/**
* Check if user has HVAC master trainer role
*
* @param int|null $user_id User ID (null for current user)
* @return bool
*/
public static function is_hvac_master_trainer($user_id = null) {
$user = $user_id ? get_user_by('id', $user_id) : wp_get_current_user();
if (!$user) {
return false;
}
return in_array('hvac_master_trainer', $user->roles) ||
user_can($user, 'manage_options');
}
/**
* Sanitize and validate superglobal input
*
* @param string $type 'GET', 'POST', 'REQUEST', 'COOKIE', 'SERVER'
* @param string $key The key to retrieve
* @param string $sanitize Sanitization function to use
* @param mixed $default Default value if not set
* @return mixed Sanitized value
*/
public static function get_input($type, $key, $sanitize = 'sanitize_text_field', $default = '') {
$superglobal = null;
switch (strtoupper($type)) {
case 'GET':
$superglobal = $_GET;
break;
case 'POST':
$superglobal = $_POST;
break;
case 'REQUEST':
$superglobal = $_REQUEST;
break;
case 'COOKIE':
$superglobal = $_COOKIE;
break;
case 'SERVER':
$superglobal = $_SERVER;
break;
default:
return $default;
}
if (!isset($superglobal[$key])) {
return $default;
}
$value = $superglobal[$key];
// Handle arrays
if (is_array($value)) {
return array_map($sanitize, $value);
}
// Apply sanitization
switch ($sanitize) {
case 'absint':
return absint($value);
case 'intval':
return intval($value);
case 'sanitize_email':
return sanitize_email($value);
case 'sanitize_url':
case 'esc_url_raw':
return esc_url_raw($value);
case 'sanitize_text_field':
return sanitize_text_field($value);
case 'sanitize_textarea_field':
return sanitize_textarea_field($value);
case 'wp_kses_post':
return wp_kses_post($value);
case 'none':
return $value; // Use with extreme caution
default:
return sanitize_text_field($value);
}
}
/**
* Validate file upload
*
* @param array $file $_FILES array element
* @param array $allowed_types Allowed MIME types
* @param int $max_size Maximum file size in bytes
* @return bool|WP_Error True if valid, WP_Error on failure
*/
public static function validate_file_upload($file, $allowed_types = array(), $max_size = 5242880) {
// Check if file was uploaded
if (!isset($file) || $file['error'] !== UPLOAD_ERR_OK) {
return new WP_Error('upload_error', 'File upload failed');
}
// Security check
if (!is_uploaded_file($file['tmp_name'])) {
return new WP_Error('security_error', 'Invalid file upload');
}
// Check file size
if ($file['size'] > $max_size) {
return new WP_Error('size_error', sprintf('File too large. Maximum size is %s', size_format($max_size)));
}
// Check file type
if (!empty($allowed_types)) {
$file_type = wp_check_filetype($file['name']);
if (!in_array($file_type['type'], $allowed_types)) {
return new WP_Error('type_error', 'Invalid file type');
}
}
return true;
}
/**
* Generate secure nonce field
*
* @param string $action Nonce action
* @param string $name Nonce field name
* @return string HTML nonce field
*/
public static function nonce_field($action, $name = '_wpnonce') {
return wp_nonce_field($action, $name, true, false);
}
/**
* Verify nonce from request
*
* @param string $action Nonce action
* @param string $name Nonce field name
* @param string $type Request type (GET, POST)
* @return bool
*/
public static function verify_nonce($action, $name = '_wpnonce', $type = 'POST') {
$nonce = self::get_input($type, $name, 'sanitize_text_field', '');
return wp_verify_nonce($nonce, $action);
}
/**
* Check AJAX referer with proper error handling
*
* @param string $action Nonce action
* @param string $query_arg Nonce query argument
* @return bool
*/
public static function check_ajax_nonce($action, $query_arg = 'nonce') {
if (!check_ajax_referer($action, $query_arg, false)) {
wp_send_json_error('Security check failed');
return false;
}
return true;
}
/**
* Escape output based on context
*
* @param mixed $data Data to escape
* @param string $context Context: 'html', 'attr', 'url', 'js', 'textarea'
* @return string Escaped data
*/
public static function escape($data, $context = 'html') {
if (is_array($data) || is_object($data)) {
return '';
}
switch ($context) {
case 'html':
return esc_html($data);
case 'attr':
return esc_attr($data);
case 'url':
return esc_url($data);
case 'js':
return esc_js($data);
case 'textarea':
return esc_textarea($data);
default:
return esc_html($data);
}
}
/**
* Validate and sanitize email
*
* @param string $email Email address
* @return string|false Sanitized email or false if invalid
*/
public static function validate_email($email) {
$email = sanitize_email($email);
return is_email($email) ? $email : false;
}
/**
* Add security headers
*/
public static function add_security_headers() {
// Content Security Policy
header("Content-Security-Policy: default-src 'self' https: data: 'unsafe-inline' 'unsafe-eval'");
// X-Frame-Options
header("X-Frame-Options: SAMEORIGIN");
// X-Content-Type-Options
header("X-Content-Type-Options: nosniff");
// X-XSS-Protection
header("X-XSS-Protection: 1; mode=block");
// Referrer Policy
header("Referrer-Policy: strict-origin-when-cross-origin");
}
/**
* Rate limiting check
*
* @param string $action Action identifier
* @param int $max_attempts Maximum attempts allowed
* @param int $window Time window in seconds
* @return bool True if allowed, false if rate limited
*/
public static function check_rate_limit($action, $max_attempts = 5, $window = 60) {
$user_id = get_current_user_id();
$ip = self::get_client_ip();
$key = 'hvac_rate_limit_' . md5($action . '_' . $user_id . '_' . $ip);
$attempts = get_transient($key);
if ($attempts === false) {
set_transient($key, 1, $window);
return true;
}
if ($attempts >= $max_attempts) {
return false;
}
set_transient($key, $attempts + 1, $window);
return true;
}
/**
* Get client IP address
*
* @return string
*/
public static function get_client_ip() {
$ip_keys = array('HTTP_CF_CONNECTING_IP', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR');
foreach ($ip_keys as $key) {
if (array_key_exists($key, $_SERVER) === true) {
$ips = explode(',', $_SERVER[$key]);
foreach ($ips as $ip) {
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
return $ip;
}
}
}
}
return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
}
/**
* Log security events
*
* @param string $event Event type
* @param array $data Event data
*/
public static function log_security_event($event, $data = array()) {
$log_data = array(
'event' => $event,
'timestamp' => current_time('mysql'),
'user_id' => get_current_user_id(),
'ip' => self::get_client_ip(),
'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
'data' => $data
);
// Log to database or file
error_log('[HVAC Security] ' . json_encode($log_data));
// You can also save to database if needed
if (defined('HVAC_SECURITY_LOG_TO_DB') && HVAC_SECURITY_LOG_TO_DB) {
global $wpdb;
$table = $wpdb->prefix . 'hvac_security_log';
$wpdb->insert($table, array(
'event_type' => $event,
'event_data' => json_encode($data),
'user_id' => get_current_user_id(),
'ip_address' => self::get_client_ip(),
'created_at' => current_time('mysql')
));
}
}
}