mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Fixes to ratelimiter and new features
This commit is contained in:
parent
04b1bb816e
commit
51ac297c59
@ -17,12 +17,18 @@ SilverStripe\Core\Injector\Injector:
|
||||
SilverStripe\Control\Middleware\TrustedProxyMiddleware:
|
||||
properties:
|
||||
TrustedProxyIPs: '`SS_TRUSTED_PROXY_IPS`'
|
||||
SecurityRateLimitMiddleware:
|
||||
class: SilverStripe\Control\Middleware\RateLimitMiddleware
|
||||
properties:
|
||||
ExtraKey: 'Security'
|
||||
MaxAttempts: 10
|
||||
Decay: 1
|
||||
RateLimitedSecurityController:
|
||||
class: SilverStripe\Control\Middleware\RequestHandlerMiddlewareAdapter
|
||||
properties:
|
||||
RequestHandler: '%$SilverStripe\Security\Security'
|
||||
Middlewares:
|
||||
- '%$SilverStripe\Control\Middleware\RateLimitMiddleware'
|
||||
- '%$SecurityRateLimitMiddleware'
|
||||
---
|
||||
Name: errorrequestprocessors
|
||||
After:
|
||||
|
@ -11,10 +11,18 @@ After:
|
||||
---
|
||||
SilverStripe\Control\Director:
|
||||
rules:
|
||||
'Security//$Action/$ID/$OtherID': '%$RateLimitedSecurityController'
|
||||
'Security//$Action/$ID/$OtherID': SilverStripe\Security\Security
|
||||
'CMSSecurity//$Action/$ID/$OtherID': SilverStripe\Security\CMSSecurity
|
||||
'dev': SilverStripe\Dev\DevelopmentAdmin
|
||||
'interactive': SilverStripe\Dev\SapphireREPL
|
||||
'InstallerTest//$Action/$ID/$OtherID': SilverStripe\Dev\InstallerTest
|
||||
'SapphireInfo//$Action/$ID/$OtherID': SilverStripe\Dev\SapphireInfo
|
||||
'SapphireREPL//$Action/$ID/$OtherID': SilverStripe\Dev\SapphireREPL
|
||||
---
|
||||
Name: security-limited
|
||||
Except:
|
||||
environment: dev
|
||||
---
|
||||
SilverStripe\Control\Director:
|
||||
rules:
|
||||
'Security//$Action/$ID/$OtherID': '%$RateLimitedSecurityController'
|
@ -774,6 +774,14 @@ class HTTPRequest implements ArrayAccess
|
||||
return sizeof($this->dirParts) <= $this->unshiftedButParsedParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Return the host from the request
|
||||
*/
|
||||
public function getHost()
|
||||
{
|
||||
return $this->getHeader('host');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client IP address which originated this request.
|
||||
*
|
||||
|
@ -5,13 +5,27 @@ namespace SilverStripe\Control\Middleware;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Core\Cache\RateLimiter;
|
||||
use SilverStripe\ErrorPage\ErrorPage;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
use SilverStripe\Security\Security;
|
||||
|
||||
class RateLimitMiddleware implements HTTPMiddleware
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string Optional extra data to add to request key generation
|
||||
*/
|
||||
private $extraKey;
|
||||
|
||||
/**
|
||||
* @var int Maximum number of attempts within the decay period
|
||||
*/
|
||||
private $maxAttempts = 10;
|
||||
|
||||
/**
|
||||
* @var int The decay period (in minutes)
|
||||
*/
|
||||
private $decay = 1;
|
||||
|
||||
/**
|
||||
* @param HTTPRequest $request
|
||||
* @param callable $delegate
|
||||
@ -19,7 +33,11 @@ class RateLimitMiddleware implements HTTPMiddleware
|
||||
*/
|
||||
public function process(HTTPRequest $request, callable $delegate)
|
||||
{
|
||||
$limiter = new RateLimiter($this->getKeyFromRequest($request), 10, 1);
|
||||
$limiter = RateLimiter::create(
|
||||
$this->getKeyFromRequest($request),
|
||||
$this->getMaxAttempts(),
|
||||
$this->getDecay()
|
||||
);
|
||||
if ($limiter->canAccess()) {
|
||||
$limiter->hit();
|
||||
$response = $delegate($request);
|
||||
@ -36,11 +54,14 @@ class RateLimitMiddleware implements HTTPMiddleware
|
||||
*/
|
||||
protected function getKeyFromRequest($request)
|
||||
{
|
||||
$domain = parse_url($request->getURL(), PHP_URL_HOST);
|
||||
$key = $this->getExtraKey() ? $this->getExtraKey() . '-' : '';
|
||||
$key .= $request->getHost() . '-';
|
||||
if ($currentUser = Security::getCurrentUser()) {
|
||||
return md5($domain . '-' . $currentUser->ID);
|
||||
$key .= $currentUser->ID;
|
||||
} else {
|
||||
$key .= $request->getIP();
|
||||
}
|
||||
return md5($domain . '-' . $request->getIP());
|
||||
return md5($key);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,11 +69,7 @@ class RateLimitMiddleware implements HTTPMiddleware
|
||||
*/
|
||||
protected function getErrorHTTPResponse()
|
||||
{
|
||||
$response = null;
|
||||
if (class_exists(ErrorPage::class)) {
|
||||
$response = ErrorPage::response_for(429);
|
||||
}
|
||||
return $response ?: new HTTPResponse('<h1>429 - Too many requests</h1>', 429);
|
||||
return HTTPResponse::create('<h1>429 - Too many requests</h1>', 429);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,4 +86,58 @@ class RateLimitMiddleware implements HTTPMiddleware
|
||||
$response->addHeader('Retry-After', $ttl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return $this
|
||||
*/
|
||||
public function setExtraKey($key)
|
||||
{
|
||||
$this->extraKey = $key;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getExtraKey()
|
||||
{
|
||||
return $this->extraKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $maxAttempts
|
||||
* @return $this
|
||||
*/
|
||||
public function setMaxAttempts($maxAttempts)
|
||||
{
|
||||
$this->maxAttempts = $maxAttempts;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxAttempts()
|
||||
{
|
||||
return $this->maxAttempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $decay Time in minutes
|
||||
* @return $this
|
||||
*/
|
||||
public function setDecay($decay)
|
||||
{
|
||||
$this->decay = $decay;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDecay()
|
||||
{
|
||||
return $this->decay;
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,14 @@
|
||||
namespace SilverStripe\Core\Cache;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
|
||||
class RateLimiter
|
||||
{
|
||||
use Injectable;
|
||||
|
||||
/**
|
||||
* @var CacheInterface
|
||||
*/
|
||||
@ -24,7 +27,7 @@ class RateLimiter
|
||||
private $maxAttempts;
|
||||
|
||||
/**
|
||||
* @var int How long the rate limit lasts for
|
||||
* @var int How long the rate limit lasts for in minutes
|
||||
*/
|
||||
private $decay;
|
||||
|
||||
|
@ -1090,7 +1090,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase implements TestOnly
|
||||
*/
|
||||
public static function getExtraControllers()
|
||||
{
|
||||
return array_merge([Security::class], static::$extra_controllers);
|
||||
return static::$extra_controllers;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user