Files
InformatiQ-Toolkit/includes/class-itk-whitelist.php
Malin 52af2d9931 feat: global IP/CIDR/UA whitelist bypassing all restrictions
- class-itk-whitelist.php: static class with 5min transient cache,
  supports exact IP, CIDR notation, and ua: prefix for UA substrings
- config/whitelist.conf: editable config file (template with examples)
- whitelist check added to bot-blocker, WAF, protection (4 methods),
  and honeypot validator — matched requests skip all ITK enforcement
- admin: whitelist.conf added to Config Files editor tab

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 10:00:16 +02:00

91 lines
2.9 KiB
PHP

<?php
if (!defined('ABSPATH')) exit;
/**
* ITK Whitelist
*
* Loads config/whitelist.conf and provides a fast static check.
* Any IP/CIDR or UA substring match bypasses all ITK restrictions.
*/
class ITK_Whitelist {
private static ?bool $result = null;
/**
* Returns true if the current request is whitelisted.
* Result is cached for the lifetime of the request.
*/
public static function allowed(): bool {
if (self::$result !== null) return self::$result;
self::$result = self::check();
return self::$result;
}
private static function check(): bool {
$ip = self::get_ip();
$ua = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
$entries = self::load();
foreach ($entries as $entry) {
if (str_starts_with($entry, 'ua:')) {
$needle = substr($entry, 3);
if ($needle !== '' && stripos($ua, $needle) !== false) return true;
} elseif (str_contains($entry, '/')) {
if (self::ip_in_cidr($ip, $entry)) return true;
} else {
if ($ip === $entry) return true;
}
}
return false;
}
private static function load(): array {
$transient = 'itk_whitelist';
$cached = get_transient($transient);
if ($cached !== false) return $cached;
$file = ITK_PATH . 'config/whitelist.conf';
$entries = [];
if (file_exists($file)) {
foreach (file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
$line = trim($line);
if ($line === '' || $line[0] === '#') continue;
$entries[] = $line;
}
}
set_transient($transient, $entries, 300); // 5-minute cache
return $entries;
}
public static function invalidate_cache(): void {
delete_transient('itk_whitelist');
self::$result = null;
}
private static function get_ip(): string {
// Prefer X-Forwarded-For first header (same logic as bot blocker)
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return trim(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]);
}
return $_SERVER['REMOTE_ADDR'] ?? '';
}
private static function ip_in_cidr(string $ip, string $cidr): bool {
[$subnet, $bits] = explode('/', $cidr, 2);
$bits = (int)$bits;
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) &&
filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$ip_long = ip2long($ip);
$sub_long = ip2long($subnet);
if ($ip_long === false || $sub_long === false) return false;
$mask = $bits === 0 ? 0 : (~0 << (32 - $bits));
return ($ip_long & $mask) === ($sub_long & $mask);
}
return false;
}
}