<?php
/**
* Created by PhpStorm.
* User: bumz
* Date: 7/9/14
* Time: 3:30 PM
*/
namespace Sindrive\IpsetBundle\Listener;
use Sindrive\IpsetBundle\Event\BruteForceEvent;
use Sindrive\IpsetBundle\Service\Ban;
use Sindrive\MemcachedBundle\Services\SindriveMemcached;
class BruteForceListener
{
/**
* @var string
*/
private $banKey;
/**
* @var \Sindrive\IpsetBundle\Service\Ban
*/
private $banService;
/**
* @var SindriveMemcached|\Memcached
*/
private $memcached;
/**
* @param SindriveMemcached $memcached
* @param \Sindrive\IpsetBundle\Service\Ban $banService
* @param string $banKey
*/
public function __construct(SindriveMemcached $memcached, Ban $banService, string $banKey = 'sindrive-ips-brute-force-listener')
{
$this->memcached = $memcached;
$this->banService = $banService;
$this->banKey = $banKey;
}
public function watch(BruteForceEvent $event): bool
{
$banKey = sprintf('%s-%s', $this->banKey, $event->getIp());
$data = $event->getData();
do {
$resultStore = $this->memcached->getCas($banKey);
$result = $resultStore['value'];
$cas = $resultStore['cas'];
$currentTime = time();
if ($event->getTimeOffset()) {
$time = $currentTime + $event->getTimeOffset();
} else {
$time = 'inf.';
}
if ($this->memcached->getResultCode() === \Memcached::RES_NOTFOUND) {
$result = array($data => $time);
$this->memcached->add($banKey, $result, $time === 'inf.' ? $currentTime + 365 * 24 * 60 * 60 : $time);
} elseif ($this->memcached->getResultCode() === \Memcached::RES_SUCCESS) {
$result = array_filter($result, static function($item) use ($currentTime) {
return $item === 'inf.' || $item > $currentTime;
});
if (!isset($result[$data]) || $result[$data] !== 'inf.') {
$result[$data] = $time;
}
$this->memcached->cas($cas, $banKey, $result, in_array('inf.', $result, true) ? $currentTime + 365 * 24 * 60 * 60 : $time);
} else {
return false;
}
} while ($this->memcached->getResultCode() !== \Memcached::RES_SUCCESS);
if (count($result) > $event->getCount()) {
$this->banService->add($event->getIp());
}
// if we do have a bot that is too greedy
if (count($result) > $event->getCount() + 2) {
$this->banService->ban();
}
return true;
}
public function forget(BruteForceEvent $event): void
{
$banKey = sprintf('%s-%s', $this->banKey, $event->getIp());
$this->memcached->delete($banKey);
}
}