ericek111

proxycheck.io

Sep 8th, 2025
176
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 10.96 KB | None | 0 0
  1. <?php
  2. namespace Lsa\Components\Impl;
  3.  
  4. class IPBlock implements \Lsa\Components\IRegisterable {
  5.  
  6.     const DEFAULT_ALLOWED_COUNTRIES = ['SK', 'CZ', 'DE', 'UK', 'FR', 'AT', 'AU', 'PL', 'HU', 'NL', 'DK', 'NO', 'FI', 'SW'];
  7.  
  8.     const OPTION_ALLOWED = 'lsa_ipblock_countries';
  9.  
  10.     const OPTION_API_KEY = 'lsa_ipblock_apikey';
  11.  
  12.     const OPTION_THRESHOLD = 'lsa_ipblock_threshold';
  13.  
  14.     const OPTION_MODE = 'lsa_ipblock_mode';
  15.  
  16.     const OPTION_FREEGEOIP_URL = 'lsa_freegeoip_url';
  17.  
  18.     const SECTION_ANTISPAM = 'lsa_antispam';
  19.  
  20.     const SECTION_ANTISPAM_PAGE = 'writing';
  21.  
  22.     public static $MODES = [
  23.         'wl-c' => 'Whitelist krajiny',
  24.         'bl-c' => 'Blacklist krajiny',
  25.         'wl-c_p' => 'Whitelist krajiny, potom kontrola proxy',
  26.         'bl-c_p' => 'Blacklist krajiny, potom kontrola proxy',
  27.         'p_wl-c' => 'Kontrola proxy, nad prah whitelist krajiny', // pri označení ako proxy
  28.         'p_bl-c' => 'Kontrola proxy, nad prah blacklist krajiny',
  29.         'p' => 'Len kontrola proxy, nad prah zamietnuť'
  30.     ];
  31.  
  32.     /** @var self */
  33.     private static $instance = null;
  34.  
  35.     /** @var string[] ProxyCheck.io cache, array IP => API result */
  36.     private $ipCache = [];
  37.  
  38.     /** @var string[] GeoIP cache, array IP => country */
  39.     protected $geoIPCache = [];
  40.  
  41.     public function __construct() {
  42.         self::$instance = $this;
  43.     }
  44.  
  45.     public function register(): void {
  46.         add_filter('registration_errors', [$this, 'registration_errors'], 10, 3);
  47.         add_filter('pre_comment_approved', [$this, 'pre_comment_approved'], 10, 2);
  48.         if (is_admin()) {
  49.             add_action('admin_init', [$this, 'admin_init']);
  50.         }
  51.     }
  52.  
  53.     public function getClientCountry(string $ip = null): string {
  54.         $ourGeo = $this->getGeoLoc($ip);
  55.        
  56.         // TODO: Probably should be moved to getGeoLoc?
  57.         if (empty($ourGeo)) {
  58.             $res = $this->doProxyCheck($ip);
  59.             if ($res && isset($res[$ip]) && isset($res[$ip]['isocode'])) {
  60.                 $ourGeo = strtoupper($res[$ip]['isocode']);
  61.                 $this->geoIPCache[$ip] = $ourGeo;
  62.             }
  63.         }
  64.  
  65.         return $ourGeo;
  66.     }
  67.  
  68.     public function isClientAllowed(string $ip = null): bool {
  69.         $ip = $ip ?: $_SERVER['REMOTE_ADDR'];
  70.  
  71.         $mode = $this->getMode();
  72.         $inList = in_array($this->getClientCountry($ip), $this->getAllowedCountries());
  73.  
  74.         switch ($this->getMode()) {
  75.             case 'wl-c':    // Whitelist krajiny
  76.                 return $inList;
  77.             case 'bl-c':    // Blacklist krajiny
  78.                 return !$inList;
  79.             case 'wl-c_p':  // Whitelist krajiny, potom kontrola proxy
  80.                 if (!$inList)
  81.                     return false;
  82.  
  83.                 $res = $this->doProxyCheck($ip);
  84.                 if (!$res)
  85.                     return true;
  86.  
  87.                 $entry = $res[$ip];
  88.                 if ($entry['risk'] > $this->getThreshold())
  89.                     return false;
  90.  
  91.                 return $entry['proxy'] == 'no';
  92.             case 'bl-c_p':  // Blacklist krajiny, potom kontrola proxy
  93.                 if ($inList)
  94.                     return false;
  95.  
  96.                 $res = $this->doProxyCheck($ip);
  97.                 if (!$res)
  98.                     return true;
  99.  
  100.                 $entry = $res[$ip];
  101.                 if ($entry['risk'] > $this->getThreshold())
  102.                     return false;
  103.  
  104.                 return $entry['proxy'] == 'no';
  105.             case 'p_wl-c':  // Kontrola proxy, pri neistom označení whitelist krajiny
  106.                 $res = $this->doProxyCheck($ip);
  107.                 if (!$res)
  108.                     return true;
  109.  
  110.                 $entry = $res[$ip];
  111.                 if ($entry['risk'] > $this->getThreshold())
  112.                     return $inList;
  113.  
  114.                 return $entry['proxy'] == 'no';
  115.             case 'p_bl-c':  // Kontrola proxy, pri neistom označení blacklist krajiny
  116.                 $res = $this->doProxyCheck($ip);
  117.                 if (!$res)
  118.                     return true;
  119.  
  120.                 $entry = $res[$ip];
  121.                 if ($entry['risk'] > $this->getThreshold())
  122.                     return !$inList;
  123.  
  124.                 return $entry['proxy'] == 'no';
  125.             case 'p':       // Len kontrola proxy, nad prah zamietnuť
  126.                 $res = $this->doProxyCheck($ip);
  127.                 if (!$res)
  128.                     return true;
  129.  
  130.                 $entry = $res[$ip];
  131.                 if ($entry['risk'] > $this->getThreshold())
  132.                     return false;
  133.  
  134.                 return $entry['proxy'] == 'no';
  135.             default:
  136.                 // TODO: NotImplementedException
  137.                 error_log('IPBlock: Unknown mode: ' . $mode . ' while checking ' . $ip);
  138.                 return false;
  139.         }
  140.  
  141.     }
  142.    
  143.     public function pre_comment_approved($approved, array $comment) {
  144.         if (!is_user_logged_in() && !$this->isClientAllowed() && $approved === 1) {
  145.             return 'spam';
  146.         }
  147.         return $approved;
  148.     }
  149.    
  150.     public function registration_errors(\WP_Error $errors, string $sanitizedUserLogin, string $userMail): \WP_Error {
  151.         if (!$this->isClientAllowed()) {
  152.             $errors->add('banned_country', 'You are not allowed to register from this country. Contact the admin for an exception.');
  153.         }
  154.         return $errors;
  155.     }
  156.    
  157.     public function admin_init(): void {
  158.         if (!current_user_can('administrator'))
  159.             return;
  160.  
  161.         add_settings_section(self::SECTION_ANTISPAM, 'Nastavenia antispamu', [$this, 'doSectionHeader'], self::SECTION_ANTISPAM_PAGE);
  162.  
  163.         add_settings_field(
  164.             self::OPTION_MODE,
  165.             'Režim detekcie',
  166.             [$this, 'doModeField'],
  167.             self::SECTION_ANTISPAM_PAGE,
  168.             self::SECTION_ANTISPAM
  169.         );
  170.        
  171.         add_settings_field(
  172.             self::OPTION_ALLOWED,
  173.             'Povolené krajiny prispievateľov',
  174.             [$this, 'doWhitelistField'],
  175.             self::SECTION_ANTISPAM_PAGE,
  176.             self::SECTION_ANTISPAM
  177.         );
  178.  
  179.         add_settings_field(
  180.             self::OPTION_API_KEY,
  181.             'API kľúč Proxycheck.io',
  182.             [$this, 'doApiKeyField'],
  183.             self::SECTION_ANTISPAM_PAGE,
  184.             self::SECTION_ANTISPAM
  185.         );
  186.  
  187.         add_settings_field(
  188.             self::OPTION_THRESHOLD,
  189.             'Maximálna hranica rizika',
  190.             [$this, 'doThresholdField'],
  191.             self::SECTION_ANTISPAM_PAGE,
  192.             self::SECTION_ANTISPAM
  193.         );
  194.  
  195.         add_settings_field(
  196.             self::OPTION_FREEGEOIP_URL,
  197.             'URL FreeGeoIP endpointu',
  198.             [$this, 'doEndpointUrlField'],
  199.             self::SECTION_ANTISPAM_PAGE,
  200.             self::SECTION_ANTISPAM
  201.         );
  202.  
  203.         register_setting(self::SECTION_ANTISPAM_PAGE, self::OPTION_MODE);
  204.         register_setting(self::SECTION_ANTISPAM_PAGE, self::OPTION_ALLOWED);
  205.         register_setting(self::SECTION_ANTISPAM_PAGE, self::OPTION_API_KEY);
  206.         register_setting(self::SECTION_ANTISPAM_PAGE, self::OPTION_THRESHOLD);
  207.         register_setting(self::SECTION_ANTISPAM_PAGE, self::OPTION_FREEGEOIP_URL);
  208.     }
  209.  
  210.     public function doModeField(): void {
  211.         ?>
  212.         <select name="<?=self::OPTION_MODE?>" id="<?=self::OPTION_MODE?>">
  213.             <?php foreach (self::$MODES as $key => $value) { ?>
  214.                 <option value="<?=esc_attr($key)?>" <?php selected($key, $this->getMode()); ?>><?=esc_html($value)?></option>
  215.             <?php } ?>
  216.         </select>
  217.         <?php
  218.     }
  219.  
  220.     public function doWhitelistField(): void {
  221.         ?>
  222.         <input type="text" name="<?=self::OPTION_ALLOWED?>" id="<?=self::OPTION_ALLOWED?>" value="<?=esc_attr(implode(',', $this->getAllowedCountries()))?>" class="regular-text" />
  223.         <p class="description" id="tagline-description">Krajiny, z ktorých sa používatelia môžu registrovať alebo anonymne pridávať príspevky. Podľa ISO 3166-1 alpha-2.</p>
  224.         <?php
  225.     }
  226.  
  227.     public function doApiKeyField(): void {
  228.         ?>
  229.         <input type="text" name="<?=self::OPTION_API_KEY?>" id="<?=self::OPTION_API_KEY?>" value="<?=esc_attr($this->getProxyCheckApiKey())?>" class="regular-text" />
  230.         <p class="description" id="tagline-description">API kľúč pre službu <a href="https://proxycheck.io/">ProxyCheck.io</a></p>
  231.         <?php
  232.     }
  233.  
  234.     public function doThresholdField(): void {
  235.         ?>
  236.         <input type="number" name="<?=self::OPTION_THRESHOLD?>" id="<?=self::OPTION_THRESHOLD?>" value="<?=esc_attr($this->getThreshold())?>" class="regular-text" />
  237.         <p class="description" id="tagline-description">Maximálna hodnota <a href="https://proxycheck.io/api/#risk_score" title="Dokumentácia API ProxyCheck.io" target="_blank">"skóre rizika"</a>, pri ktorej príspevok prejde. Zadané číslo 0 = 0 %, čiže nástroj si je stopercentne istý, že klient <b>nie je</b> proxy/VPN. pod 33 % je nízka miera rizika.</p>
  238.         <?php
  239.     }
  240.  
  241.     public function doEndpointUrlField(): void {
  242.         ?>
  243.        
  244.         <input type="text" name="<?=self::OPTION_FREEGEOIP_URL?>" id="<?=self::OPTION_FREEGEOIP_URL?>" value="<?=esc_attr($this->getEndpointUrl())?>" class="regular-text" />
  245.         <p class="description" id="tagline-description">Placeholder <i>%IP%</i> bude nahradený urlencode-nutou IP klienta.</p>
  246.         <?php
  247.     }
  248.  
  249.     public function doSectionHeader(): void {
  250.         ?>
  251.         <p>Kontrola krajiny je vždy vykonávaná cez FreeGeoIP. Ak toto nebude správne fungovať, treba povedať Erikovi. :)</p>
  252.         <p>V prípade nedostupnosti alebo nesprávnej konfigurácie služby bude považovaná proxy kontrola za <b>úspešnú</b>, teda s výsledkom <i>"nie je proxy/VPN"</i>.</p>
  253.         <?php
  254.     }
  255.  
  256.     public function getMode(): string {
  257.         return get_option(self::OPTION_MODE, 'p');
  258.     }
  259.  
  260.     public function getProxyCheckApiKey(): ?string {
  261.         return get_option(self::OPTION_API_KEY);
  262.     }
  263.  
  264.     public function getThreshold(): int {
  265.         return (int) get_option(self::OPTION_THRESHOLD, 33);
  266.     }
  267.  
  268.     public function getEndpointUrl(): string {
  269.         return get_option(self::OPTION_FREEGEOIP_URL, 'https://www.letemsvetemapplem.eu/freegeoip.php?ip=%IP%');
  270.     }
  271.  
  272.     public function getAllowedCountries(): array {
  273.         $res = get_option(self::OPTION_ALLOWED, implode(',', self::DEFAULT_ALLOWED_COUNTRIES));
  274.         $res = preg_replace('/\s+/', '', $res);
  275.         $res = mb_strtoupper($res);
  276.  
  277.         return explode(',', $res);;
  278.     }
  279.  
  280.     public function doProxyCheck(string $ip): ?array {
  281.         $cachedKey = 'lsa_proxycheck_' . md5($ip);
  282.         if (( $cached = get_transient($cachedKey) ))
  283.             return $cached;
  284.        
  285.         // https://proxycheck.io/api/#query_flags
  286.         $url = 'http://proxycheck.io/v2/' . urlencode($ip);
  287.         $url .= '?key=' . ($this->getProxyCheckApiKey() ?: '');
  288.         $url .= '&vpn=1'; // Check for both VPN's and Proxies instead of just Proxies.
  289.         $url .= '&asn=1'; // Enable ASN data response.
  290.         $url .= '&risk=1'; // 0 = Off, 1 = Risk Score (0-100), 2 = Risk Score & Attack History.
  291.         $url .= '&days=14'; // Restrict checking to proxies seen in the past # of days.
  292.  
  293.         $ch = curl_init($url);
  294.         curl_setopt_array($ch, [
  295.             CURLOPT_CONNECTTIMEOUT => 30,
  296.             CURLOPT_RETURNTRANSFER => true
  297.         ]);
  298.         $res = curl_exec($ch);
  299.         $resCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  300.  
  301.         if (curl_errno($ch)) {
  302.             error_log('ProxyCheck for ' . $ip . ' (HTTP ' . $resCode . ') failed with cURL error ' . curl_error($ch));
  303.             curl_close($ch);
  304.             return null;
  305.         }
  306.  
  307.         curl_close($ch);
  308.  
  309.         if (empty($res))
  310.             return null;
  311.  
  312.         $dec = json_decode($res, true);
  313.  
  314.         if (isset($dec['status']) && !in_array($dec['status'], ['ok', 'warning'])) {
  315.             error_log('ProxyCheck.io error returned by API: ' . $dec['message']);
  316.             return null;
  317.         }
  318.        
  319.         set_transient($cachedKey, $dec, 86400);
  320.         return $dec;
  321.     }
  322.  
  323.     public function getGeoLoc(string $ip = ""): string {
  324.         $ip = empty($ip) ? $_SERVER['REMOTE_ADDR'] : $ip;
  325.         if (isset($this->geoIPCache[$ip]))
  326.             return $this->geoIPCache[$ip];
  327.        
  328.         $ch = curl_init(str_replace('%IP%', urlencode($ip), $this->getEndpointUrl()));
  329.         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  330.         curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
  331.         curl_setopt($ch, CURLOPT_TIMEOUT, 5);
  332.         $c = '';
  333.         $suc = !( ($c = curl_exec($ch)) === false);
  334.         curl_close($ch);
  335.  
  336.         $c = strtoupper($c);
  337.         $this->geoIPCache[$ip] = $c;
  338.  
  339.         return $c;
  340.     }
  341.  
  342.     public static function get() : self {
  343.         if (self::$instance == null) {
  344.             self::$instance = new self();
  345.         }
  346.         return self::$instance;
  347.     }
  348.    
  349. }
Advertisement
Add Comment
Please, Sign In to add comment