true, 'errors' => [], 'sanitized' => [], 'warnings' => [] ]; foreach ($rules as $field => $rule_set) { $value = $data[$field] ?? null; $field_result = self::validate_field($field, $value, $rule_set); if (!$field_result['valid']) { $result['valid'] = false; $result['errors'][$field] = $field_result['errors']; } if (!empty($field_result['warnings'])) { $result['warnings'][$field] = $field_result['warnings']; } $result['sanitized'][$field] = $field_result['sanitized']; } return $result; } /** * 🔐 SECURITY: Validate individual field with comprehensive rules */ private static function validate_field(string $field, $value, array $rules): array { $result = [ 'valid' => true, 'errors' => [], 'warnings' => [], 'sanitized' => $value ]; // Required field check if (isset($rules['required']) && $rules['required'] && empty($value)) { $result['valid'] = false; $result['errors'][] = "Field '{$field}' is required"; return $result; } // Skip further validation if field is empty and not required if (empty($value) && !($rules['required'] ?? false)) { return $result; } // Apply validation rules foreach ($rules as $rule => $rule_value) { switch ($rule) { case 'type': $type_result = self::validate_type($value, $rule_value); if (!$type_result['valid']) { $result['valid'] = false; $result['errors'] = array_merge($result['errors'], $type_result['errors']); } $result['sanitized'] = $type_result['sanitized']; break; case 'length': $length_result = self::validate_length($value, $rule_value); if (!$length_result['valid']) { $result['valid'] = false; $result['errors'] = array_merge($result['errors'], $length_result['errors']); } break; case 'pattern': $pattern_result = self::validate_pattern($value, $rule_value); if (!$pattern_result['valid']) { $result['valid'] = false; $result['errors'] = array_merge($result['errors'], $pattern_result['errors']); } break; case 'enum': $enum_result = self::validate_enum($value, $rule_value); if (!$enum_result['valid']) { $result['valid'] = false; $result['errors'] = array_merge($result['errors'], $enum_result['errors']); } break; case 'security': $security_result = self::validate_security($value, $rule_value); if (!$security_result['valid']) { $result['valid'] = false; $result['errors'] = array_merge($result['errors'], $security_result['errors']); } if (!empty($security_result['warnings'])) { $result['warnings'] = array_merge($result['warnings'], $security_result['warnings']); } break; } } return $result; } /** * 🔐 SECURITY: Type validation with WordPress sanitization */ private static function validate_type($value, string $type): array { $result = ['valid' => true, 'errors' => [], 'sanitized' => $value]; switch ($type) { case 'oauth2_client_id': // OAuth2 client IDs should be alphanumeric with limited special chars $sanitized = preg_replace('/[^a-zA-Z0-9._-]/', '', $value); if ($sanitized !== $value) { $result['valid'] = false; $result['errors'][] = 'Client ID contains invalid characters'; } $result['sanitized'] = $sanitized; break; case 'oauth2_scope': // OAuth2 scopes: space-separated tokens with limited chars $sanitized = preg_replace('/[^a-zA-Z0-9:._\s-]/', '', $value); $result['sanitized'] = trim($sanitized); if ($sanitized !== $value) { $result['valid'] = false; $result['errors'][] = 'Scope contains invalid characters'; } break; case 'oauth2_response_type': $result['sanitized'] = sanitize_text_field($value); break; case 'oauth2_grant_type': $result['sanitized'] = sanitize_text_field($value); break; case 'url': $sanitized = esc_url_raw($value); if (empty($sanitized) || $sanitized !== $value) { $result['valid'] = false; $result['errors'][] = 'Invalid URL format'; } $result['sanitized'] = $sanitized; break; case 'oauth2_code': // Authorization codes should be base64url-like $sanitized = preg_replace('/[^a-zA-Z0-9._-]/', '', $value); if ($sanitized !== $value) { $result['valid'] = false; $result['errors'][] = 'Authorization code contains invalid characters'; } $result['sanitized'] = $sanitized; break; case 'oauth2_token': // Access tokens should be base64url-like $sanitized = preg_replace('/[^a-zA-Z0-9._-]/', '', $value); if ($sanitized !== $value) { $result['valid'] = false; $result['errors'][] = 'Token contains invalid characters'; } $result['sanitized'] = $sanitized; break; case 'text': $result['sanitized'] = sanitize_text_field($value); break; default: $result['sanitized'] = sanitize_text_field($value); } return $result; } /** * 🔐 SECURITY: Length validation */ private static function validate_length($value, array $constraints): array { $result = ['valid' => true, 'errors' => []]; $length = strlen($value); if (isset($constraints['min']) && $length < $constraints['min']) { $result['valid'] = false; $result['errors'][] = "Value too short (minimum {$constraints['min']} characters)"; } if (isset($constraints['max']) && $length > $constraints['max']) { $result['valid'] = false; $result['errors'][] = "Value too long (maximum {$constraints['max']} characters)"; } return $result; } /** * 🔐 SECURITY: Pattern validation with security considerations */ private static function validate_pattern($value, string $pattern): array { $result = ['valid' => true, 'errors' => []]; if (!preg_match($pattern, $value)) { $result['valid'] = false; $result['errors'][] = 'Value does not match required pattern'; } return $result; } /** * 🔐 SECURITY: Enumeration validation */ private static function validate_enum($value, array $allowed_values): array { $result = ['valid' => true, 'errors' => []]; if (!in_array($value, $allowed_values, true)) { $result['valid'] = false; $result['errors'][] = 'Value not in allowed list: ' . implode(', ', $allowed_values); } return $result; } /** * 🔐 SECURITY: Advanced security validation */ private static function validate_security($value, array $security_rules): array { $result = ['valid' => true, 'errors' => [], 'warnings' => []]; // Check for SQL injection patterns if (isset($security_rules['sql_injection']) && $security_rules['sql_injection']) { $sql_patterns = [ '/(\b(select|insert|update|delete|drop|create|alter|exec|execute)\b)/i', '/(\b(union|or|and)\s+[\w\s]*=)/i', '/(\'|\"|;|--|\*|\/\*|\*\/)/i' ]; foreach ($sql_patterns as $pattern) { if (preg_match($pattern, $value)) { $result['valid'] = false; $result['errors'][] = 'Potential SQL injection attempt detected'; break; } } } // Check for XSS patterns if (isset($security_rules['xss']) && $security_rules['xss']) { $xss_patterns = [ '/)<[^<]*)*<\/script>/i', '/javascript:/i', '/on\w+\s*=/i', '/"\'\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', $value)) { $result['warnings'][] = 'Contains potentially suspicious characters'; } } // Check for common attack patterns if (isset($security_rules['attack_patterns']) && $security_rules['attack_patterns']) { $attack_patterns = [ '/\.\.[\/\\\\]/', // Directory traversal '/\x00/', // Null bytes '/eval\s*\(/i', // Code injection '/system\s*\(/i', // Command injection ]; foreach ($attack_patterns as $pattern) { if (preg_match($pattern, $value)) { $result['valid'] = false; $result['errors'][] = 'Security threat pattern detected'; break; } } } return $result; } /** * 🔐 SECURITY: Get OAuth2 validation rules */ public static function get_oauth2_validation_rules(): array { return [ 'client_id' => [ 'required' => true, 'type' => 'oauth2_client_id', 'length' => ['min' => 1, 'max' => 255], 'pattern' => '/^[a-zA-Z0-9._-]+$/', 'security' => ['sql_injection' => true, 'xss' => true, 'attack_patterns' => true] ], 'client_secret' => [ 'required' => false, // May not be required for public clients 'type' => 'text', 'length' => ['min' => 1, 'max' => 1000], 'security' => ['sql_injection' => true, 'xss' => true, 'attack_patterns' => true] ], 'response_type' => [ 'required' => true, 'type' => 'oauth2_response_type', 'enum' => ['code', 'token', 'id_token'] ], 'grant_type' => [ 'required' => true, 'type' => 'oauth2_grant_type', 'enum' => ['authorization_code', 'refresh_token', 'client_credentials', 'password'] ], 'redirect_uri' => [ 'required' => true, 'type' => 'url', 'length' => ['max' => 2048], 'security' => ['xss' => true, 'attack_patterns' => true] ], 'scope' => [ 'required' => false, 'type' => 'oauth2_scope', 'length' => ['max' => 1000], 'pattern' => '/^[a-zA-Z0-9:._\s-]*$/', 'security' => ['sql_injection' => true, 'xss' => true] ], 'state' => [ 'required' => false, 'type' => 'text', 'length' => ['max' => 1000], 'security' => ['sql_injection' => true, 'xss' => true, 'suspicious_chars' => true] ], 'code' => [ 'required' => false, 'type' => 'oauth2_code', 'length' => ['min' => 10, 'max' => 512], 'pattern' => '/^[a-zA-Z0-9._-]+$/', 'security' => ['sql_injection' => true, 'attack_patterns' => true] ], 'token' => [ 'required' => false, 'type' => 'oauth2_token', 'length' => ['min' => 10, 'max' => 1024], 'pattern' => '/^[a-zA-Z0-9._-]+$/', 'security' => ['sql_injection' => true, 'attack_patterns' => true] ] ]; } }