unify IP-related logic into traffic limiter
This commit is contained in:
parent
dbe8debe30
commit
1034d4038e
|
@ -195,35 +195,14 @@ class Controller
|
||||||
*/
|
*/
|
||||||
private function _create()
|
private function _create()
|
||||||
{
|
{
|
||||||
// Check if whitelist feature is enabled
|
|
||||||
if (($option = $this->_conf->getKey('creators', 'traffic')) !== '') {
|
|
||||||
// Parse whitelist into array
|
|
||||||
$whitelist = explode(',', $option);
|
|
||||||
// Check for source IP in HTTP header
|
|
||||||
if (($option = $this->_conf->getKey('header', 'traffic')) !== null) {
|
|
||||||
$httpHeader = 'HTTP_' . $option;
|
|
||||||
// Grab source IP from HTTP header (if it exists)
|
|
||||||
if (array_key_exists($httpHeader, $_SERVER) && !empty($_SERVER[$httpHeader])) {
|
|
||||||
// Check if source IP reported from HTTP header is in whitelist array
|
|
||||||
if (!in_array($_SERVER[$httpHeader], $whitelist)) {
|
|
||||||
$this->_return_message(1, I18n::_('Your IP is not authorized to create pastes.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure last paste from visitors IP address was more than configured amount of seconds ago.
|
// Ensure last paste from visitors IP address was more than configured amount of seconds ago.
|
||||||
ServerSalt::setStore($this->_model->getStore());
|
ServerSalt::setStore($this->_model->getStore());
|
||||||
TrafficLimiter::setConfiguration($this->_conf);
|
TrafficLimiter::setConfiguration($this->_conf);
|
||||||
TrafficLimiter::setStore($this->_model->getStore());
|
TrafficLimiter::setStore($this->_model->getStore());
|
||||||
if (!TrafficLimiter::canPass()) {
|
try {
|
||||||
$this->_return_message(
|
TrafficLimiter::canPass();
|
||||||
1, I18n::_(
|
} catch (Exception $e) {
|
||||||
'Please wait %d seconds between each post.',
|
$this->_return_message(1, $e->getMessage());
|
||||||
$this->_conf->getKey('limit', 'traffic')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,10 @@
|
||||||
|
|
||||||
namespace PrivateBin\Persistence;
|
namespace PrivateBin\Persistence;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use IPLib\Factory;
|
use IPLib\Factory;
|
||||||
use PrivateBin\Configuration;
|
use PrivateBin\Configuration;
|
||||||
|
use PrivateBin\I18n;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TrafficLimiter
|
* TrafficLimiter
|
||||||
|
@ -24,13 +26,13 @@ use PrivateBin\Configuration;
|
||||||
class TrafficLimiter extends AbstractPersistence
|
class TrafficLimiter extends AbstractPersistence
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* time limit in seconds, defaults to 10s
|
* listed IPs are the only ones allowed to create, defaults to null
|
||||||
*
|
*
|
||||||
* @access private
|
* @access private
|
||||||
* @static
|
* @static
|
||||||
* @var int
|
* @var string|null
|
||||||
*/
|
*/
|
||||||
private static $_limit = 10;
|
private static $_creators = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* listed IPs are exempted from limits, defaults to null
|
* listed IPs are exempted from limits, defaults to null
|
||||||
|
@ -51,19 +53,49 @@ class TrafficLimiter extends AbstractPersistence
|
||||||
private static $_ipKey = 'REMOTE_ADDR';
|
private static $_ipKey = 'REMOTE_ADDR';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set the time limit in seconds
|
* time limit in seconds, defaults to 10s
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @static
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private static $_limit = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set configuration options of the traffic limiter
|
||||||
*
|
*
|
||||||
* @access public
|
* @access public
|
||||||
* @static
|
* @static
|
||||||
* @param int $limit
|
* @param Configuration $conf
|
||||||
*/
|
*/
|
||||||
public static function setLimit($limit)
|
public static function setConfiguration(Configuration $conf)
|
||||||
{
|
{
|
||||||
self::$_limit = $limit;
|
self::setCreators($conf->getKey('creators', 'traffic'));
|
||||||
|
self::setExempted($conf->getKey('exempted', 'traffic'));
|
||||||
|
self::setLimit($conf->getKey('limit', 'traffic'));
|
||||||
|
|
||||||
|
if (($option = $conf->getKey('header', 'traffic')) !== '') {
|
||||||
|
$httpHeader = 'HTTP_' . $option;
|
||||||
|
if (array_key_exists($httpHeader, $_SERVER) && !empty($_SERVER[$httpHeader])) {
|
||||||
|
self::$_ipKey = $httpHeader;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set a list of IP(-ranges) as string
|
* set a list of creator IP(-ranges) as string
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @static
|
||||||
|
* @param string $creators
|
||||||
|
*/
|
||||||
|
public static function setCreators($creators)
|
||||||
|
{
|
||||||
|
self::$_creators = $creators;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set a list of exempted IP(-ranges) as string
|
||||||
*
|
*
|
||||||
* @access public
|
* @access public
|
||||||
* @static
|
* @static
|
||||||
|
@ -75,23 +107,15 @@ class TrafficLimiter extends AbstractPersistence
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set configuration options of the traffic limiter
|
* set the time limit in seconds
|
||||||
*
|
*
|
||||||
* @access public
|
* @access public
|
||||||
* @static
|
* @static
|
||||||
* @param Configuration $conf
|
* @param int $limit
|
||||||
*/
|
*/
|
||||||
public static function setConfiguration(Configuration $conf)
|
public static function setLimit($limit)
|
||||||
{
|
{
|
||||||
self::setLimit($conf->getKey('limit', 'traffic'));
|
self::$_limit = $limit;
|
||||||
self::setExempted($conf->getKey('exempted', 'traffic'));
|
|
||||||
|
|
||||||
if (($option = $conf->getKey('header', 'traffic')) !== '') {
|
|
||||||
$httpHeader = 'HTTP_' . $option;
|
|
||||||
if (array_key_exists($httpHeader, $_SERVER) && !empty($_SERVER[$httpHeader])) {
|
|
||||||
self::$_ipKey = $httpHeader;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -108,7 +132,7 @@ class TrafficLimiter extends AbstractPersistence
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate $_ipKey against configured ipranges. If matched we will ignore the ip
|
* validate $_ipKey against configured ipranges. If matched we will ignore the ip
|
||||||
*
|
*
|
||||||
* @access private
|
* @access private
|
||||||
* @static
|
* @static
|
||||||
|
@ -136,22 +160,33 @@ class TrafficLimiter extends AbstractPersistence
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* traffic limiter
|
* make sure the IP address is allowed to perfom a request
|
||||||
*
|
|
||||||
* Make sure the IP address makes at most 1 request every 10 seconds.
|
|
||||||
*
|
*
|
||||||
* @access public
|
* @access public
|
||||||
* @static
|
* @static
|
||||||
* @return bool
|
* @throws Exception
|
||||||
|
* @return true
|
||||||
*/
|
*/
|
||||||
public static function canPass()
|
public static function canPass()
|
||||||
{
|
{
|
||||||
|
// if creators are defined, the traffic limiter will only allow creation
|
||||||
|
// for these, with no limits, and skip any other rules
|
||||||
|
if (!empty(self::$_creators)) {
|
||||||
|
$creatorIps = explode(',', self::$_creators);
|
||||||
|
foreach ($creatorIps as $ipRange) {
|
||||||
|
if (self::matchIp($ipRange) === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Exception(I18n::_('Your IP is not authorized to create pastes.'));
|
||||||
|
}
|
||||||
|
|
||||||
// disable limits if set to less then 1
|
// disable limits if set to less then 1
|
||||||
if (self::$_limit < 1) {
|
if (self::$_limit < 1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if $_ipKey is exempted from ratelimiting
|
// check if $_ipKey is exempted from ratelimiting
|
||||||
if (!empty(self::$_exempted)) {
|
if (!empty(self::$_exempted)) {
|
||||||
$exIp_array = explode(',', self::$_exempted);
|
$exIp_array = explode(',', self::$_exempted);
|
||||||
foreach ($exIp_array as $ipRange) {
|
foreach ($exIp_array as $ipRange) {
|
||||||
|
@ -175,6 +210,10 @@ class TrafficLimiter extends AbstractPersistence
|
||||||
if (!self::$_store->setValue((string) $tl, 'traffic_limiter', $hash)) {
|
if (!self::$_store->setValue((string) $tl, 'traffic_limiter', $hash)) {
|
||||||
error_log('failed to store the traffic limiter, it probably contains outdated information');
|
error_log('failed to store the traffic limiter, it probably contains outdated information');
|
||||||
}
|
}
|
||||||
return $result;
|
if ($result) return true;
|
||||||
|
throw new Exception(I18n::_(
|
||||||
|
'Please wait %d seconds between each post.',
|
||||||
|
self::$_limit
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,13 +38,21 @@ class TrafficLimiterTest extends PHPUnit_Framework_TestCase
|
||||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'first request may pass');
|
$this->assertTrue(TrafficLimiter::canPass(), 'first request may pass');
|
||||||
sleep(1);
|
sleep(1);
|
||||||
$this->assertFalse(TrafficLimiter::canPass(), 'second request is to fast, may not pass');
|
try {
|
||||||
|
$this->assertFalse(TrafficLimiter::canPass(), 'expected an exception');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->assertEquals($e->getMessage(), 'Please wait 4 seconds between each post.', 'second request is to fast, may not pass');
|
||||||
|
}
|
||||||
sleep(4);
|
sleep(4);
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'third request waited long enough and may pass');
|
$this->assertTrue(TrafficLimiter::canPass(), 'third request waited long enough and may pass');
|
||||||
$_SERVER['REMOTE_ADDR'] = '2001:1620:2057:dead:beef::cafe:babe';
|
$_SERVER['REMOTE_ADDR'] = '2001:1620:2057:dead:beef::cafe:babe';
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'fourth request has different ip and may pass');
|
$this->assertTrue(TrafficLimiter::canPass(), 'fourth request has different ip and may pass');
|
||||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||||
$this->assertFalse(TrafficLimiter::canPass(), 'fifth request is to fast, may not pass');
|
try {
|
||||||
|
$this->assertFalse(TrafficLimiter::canPass(), 'expected an exception');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->assertEquals($e->getMessage(), 'Please wait 4 seconds between each post.', 'fifth request is to fast, may not pass');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTrafficLimitExempted()
|
public function testTrafficLimitExempted()
|
||||||
|
@ -52,7 +60,11 @@ class TrafficLimiterTest extends PHPUnit_Framework_TestCase
|
||||||
TrafficLimiter::setExempted('1.2.3.4,10.10.10.0/24,2001:1620:2057::/48');
|
TrafficLimiter::setExempted('1.2.3.4,10.10.10.0/24,2001:1620:2057::/48');
|
||||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'first request may pass');
|
$this->assertTrue(TrafficLimiter::canPass(), 'first request may pass');
|
||||||
$this->assertFalse(TrafficLimiter::canPass(), 'not exempted');
|
try {
|
||||||
|
$this->assertFalse(TrafficLimiter::canPass(), 'expected an exception');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->assertEquals($e->getMessage(), 'Please wait 4 seconds between each post.', 'not exempted');
|
||||||
|
}
|
||||||
$_SERVER['REMOTE_ADDR'] = '10.10.10.10';
|
$_SERVER['REMOTE_ADDR'] = '10.10.10.10';
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'IPv4 in exempted range');
|
$this->assertTrue(TrafficLimiter::canPass(), 'IPv4 in exempted range');
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but IPv4 in exempted range');
|
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but IPv4 in exempted range');
|
||||||
|
@ -61,7 +73,11 @@ class TrafficLimiterTest extends PHPUnit_Framework_TestCase
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but IPv6 in exempted range');
|
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but IPv6 in exempted range');
|
||||||
TrafficLimiter::setExempted('127.*,foobar');
|
TrafficLimiter::setExempted('127.*,foobar');
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'first cached request may pass');
|
$this->assertTrue(TrafficLimiter::canPass(), 'first cached request may pass');
|
||||||
$this->assertFalse(TrafficLimiter::canPass(), 'request is to fast, invalid range');
|
try {
|
||||||
|
$this->assertFalse(TrafficLimiter::canPass(), 'expected an exception');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->assertEquals($e->getMessage(), 'Please wait 4 seconds between each post.', 'request is to fast, invalid range');
|
||||||
|
}
|
||||||
$_SERVER['REMOTE_ADDR'] = 'foobar';
|
$_SERVER['REMOTE_ADDR'] = 'foobar';
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'non-IP address');
|
$this->assertTrue(TrafficLimiter::canPass(), 'non-IP address');
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but non-IP address matches exempted range');
|
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but non-IP address matches exempted range');
|
||||||
|
@ -71,16 +87,23 @@ class TrafficLimiterTest extends PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
TrafficLimiter::setCreators('1.2.3.4,10.10.10.0/24,2001:1620:2057::/48');
|
TrafficLimiter::setCreators('1.2.3.4,10.10.10.0/24,2001:1620:2057::/48');
|
||||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||||
$this->assertFalse(TrafficLimiter::canPass(), 'not a creator');
|
try {
|
||||||
|
$this->assertFalse(TrafficLimiter::canPass(), 'expected an exception');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->assertEquals($e->getMessage(), 'Your IP is not authorized to create pastes.', 'not a creator');
|
||||||
|
}
|
||||||
$_SERVER['REMOTE_ADDR'] = '10.10.10.10';
|
$_SERVER['REMOTE_ADDR'] = '10.10.10.10';
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'IPv4 in creator range');
|
$this->assertTrue(TrafficLimiter::canPass(), 'IPv4 in creator range');
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but IPv4 in creator range');
|
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but IPv4 in creator range');
|
||||||
$_SERVER['REMOTE_ADDR'] = '2001:1620:2057:dead:beef::cafe:babe';
|
$_SERVER['REMOTE_ADDR'] = '2001:1620:2057:dead:beef::cafe:babe';
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'IPv6 in creator range');
|
$this->assertTrue(TrafficLimiter::canPass(), 'IPv6 in creator range');
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but IPv6 in creator range');
|
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but IPv6 in creator range');
|
||||||
TrafficLimiter::setExempted('127.*,foobar');
|
TrafficLimiter::setCreators('127.*,foobar');
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'first cached request may pass');
|
try {
|
||||||
$this->assertFalse(TrafficLimiter::canPass(), 'request is to fast, not a creator');
|
$this->assertFalse(TrafficLimiter::canPass(), 'expected an exception');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->assertEquals($e->getMessage(), 'Your IP is not authorized to create pastes.', 'request is to fast, not a creator');
|
||||||
|
}
|
||||||
$_SERVER['REMOTE_ADDR'] = 'foobar';
|
$_SERVER['REMOTE_ADDR'] = 'foobar';
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'non-IP address');
|
$this->assertTrue(TrafficLimiter::canPass(), 'non-IP address');
|
||||||
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but non-IP address matches creator');
|
$this->assertTrue(TrafficLimiter::canPass(), 'request is to fast, but non-IP address matches creator');
|
||||||
|
|
Loading…
Reference in New Issue