diff options
author | Lukas Reschke <lukas@statuscode.ch> | 2017-04-12 20:32:48 +0200 |
---|---|---|
committer | Lukas Reschke <lukas@statuscode.ch> | 2017-04-13 12:00:16 +0200 |
commit | 66835476b59b8be7593d4cfa03a51c4f265d7e26 (patch) | |
tree | 91770c8fe403da25af50e6336727ab55fe57cd27 /lib/private/AppFramework | |
parent | 5505faa3d7b6f5a95f18fe5027355d700d69f396 (diff) | |
download | nextcloud-server-66835476b59b8be7593d4cfa03a51c4f265d7e26.tar.gz nextcloud-server-66835476b59b8be7593d4cfa03a51c4f265d7e26.zip |
Add support for ratelimiting via annotations
This allows adding rate limiting via annotations to controllers, as one example:
```
@UserRateThrottle(limit=5, period=100)
@AnonRateThrottle(limit=1, period=100)
```
Would mean that logged-in users can access the page 5 times within 100 seconds, and anonymous users 1 time within 100 seconds. If only an AnonRateThrottle is specified that one will also be applied to logged-in users.
Signed-off-by: Lukas Reschke <lukas@statuscode.ch>
Diffstat (limited to 'lib/private/AppFramework')
3 files changed, 83 insertions, 43 deletions
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index 4fb13b09ae0..a414772c4d6 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -53,11 +53,13 @@ use OCP\AppFramework\QueryException; use OCP\Files\Folder; use OCP\Files\IAppData; use OCP\IL10N; +use OCP\IMemcache; use OCP\IRequest; use OCP\IServerContainer; use OCP\IUserSession; use OCP\RichObjectStrings\IValidator; use OCP\Util; +use SearchDAV\XML\Limit; class DIContainer extends SimpleContainer implements IAppContainer { @@ -162,6 +164,22 @@ class DIContainer extends SimpleContainer implements IAppContainer { return $c->query(Validator::class); }); + $this->registerService(OC\Security\RateLimiting\Limiter::class, function($c) { + return new OC\Security\RateLimiting\Limiter( + $this->getServer()->getUserSession(), + $this->getServer()->getRequest(), + new OC\AppFramework\Utility\TimeFactory(), + $c->query(OC\Security\RateLimiting\Backend\IBackend::class) + ); + }); + + $this->registerService(OC\Security\RateLimiting\Backend\IBackend::class, function($c) { + return new OC\Security\RateLimiting\Backend\MemoryCache( + $this->getServer()->getMemCacheFactory(), + new OC\AppFramework\Utility\TimeFactory() + ); + }); + $this->registerService(\OC\Security\IdentityProof\Manager::class, function ($c) { return new \OC\Security\IdentityProof\Manager( $this->getServer()->getAppDataDir('identityproof'), @@ -169,7 +187,6 @@ class DIContainer extends SimpleContainer implements IAppContainer { ); }); - /** * App Framework APIs */ @@ -220,12 +237,13 @@ class DIContainer extends SimpleContainer implements IAppContainer { $server->getLogger(), $server->getSession(), $c['AppName'], - $app->isLoggedIn(), + $server->getUserSession(), $app->isAdminUser(), $server->getContentSecurityPolicyManager(), $server->getCsrfTokenManager(), $server->getContentSecurityPolicyNonceManager(), - $server->getBruteForceThrottler() + $server->getBruteForceThrottler(), + $c->query(OC\Security\RateLimiting\Limiter::class) ); }); diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index edba6a3e759..d8bbe84c86e 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -26,7 +26,6 @@ * */ - namespace OC\AppFramework\Middleware\Security; use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException; @@ -40,6 +39,7 @@ use OC\Security\Bruteforce\Throttler; use OC\Security\CSP\ContentSecurityPolicyManager; use OC\Security\CSP\ContentSecurityPolicyNonceManager; use OC\Security\CSRF\CsrfTokenManager; +use OC\Security\RateLimiting\Limiter; use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\EmptyContentSecurityPolicy; use OCP\AppFramework\Http\RedirectResponse; @@ -54,6 +54,7 @@ use OCP\IURLGenerator; use OCP\IRequest; use OCP\ILogger; use OCP\AppFramework\Controller; +use OCP\IUserSession; use OCP\Util; use OC\AppFramework\Middleware\Security\Exceptions\SecurityException; @@ -78,8 +79,8 @@ class SecurityMiddleware extends Middleware { private $logger; /** @var ISession */ private $session; - /** @var bool */ - private $isLoggedIn; + /** @var IUserSession */ + private $userSession; /** @var bool */ private $isAdminUser; /** @var ContentSecurityPolicyManager */ @@ -90,6 +91,8 @@ class SecurityMiddleware extends Middleware { private $cspNonceManager; /** @var Throttler */ private $throttler; + /** @var Limiter */ + private $limiter; /** * @param IRequest $request @@ -99,12 +102,13 @@ class SecurityMiddleware extends Middleware { * @param ILogger $logger * @param ISession $session * @param string $appName - * @param bool $isLoggedIn + * @param IUserSession $userSession * @param bool $isAdminUser * @param ContentSecurityPolicyManager $contentSecurityPolicyManager * @param CSRFTokenManager $csrfTokenManager * @param ContentSecurityPolicyNonceManager $cspNonceManager * @param Throttler $throttler + * @param Limiter $limiter */ public function __construct(IRequest $request, ControllerMethodReflector $reflector, @@ -113,12 +117,13 @@ class SecurityMiddleware extends Middleware { ILogger $logger, ISession $session, $appName, - $isLoggedIn, + IUserSession $userSession, $isAdminUser, ContentSecurityPolicyManager $contentSecurityPolicyManager, CsrfTokenManager $csrfTokenManager, ContentSecurityPolicyNonceManager $cspNonceManager, - Throttler $throttler) { + Throttler $throttler, + Limiter $limiter) { $this->navigationManager = $navigationManager; $this->request = $request; $this->reflector = $reflector; @@ -126,15 +131,15 @@ class SecurityMiddleware extends Middleware { $this->urlGenerator = $urlGenerator; $this->logger = $logger; $this->session = $session; - $this->isLoggedIn = $isLoggedIn; + $this->userSession = $userSession; $this->isAdminUser = $isAdminUser; $this->contentSecurityPolicyManager = $contentSecurityPolicyManager; $this->csrfTokenManager = $csrfTokenManager; $this->cspNonceManager = $cspNonceManager; $this->throttler = $throttler; + $this->limiter = $limiter; } - /** * This runs all the security checks before a method call. The * security checks are determined by inspecting the controller method @@ -152,7 +157,7 @@ class SecurityMiddleware extends Middleware { // security checks $isPublicPage = $this->reflector->hasAnnotation('PublicPage'); if(!$isPublicPage) { - if(!$this->isLoggedIn) { + if(!$this->userSession->isLoggedIn()) { throw new NotLoggedInException(); } @@ -191,8 +196,29 @@ class SecurityMiddleware extends Middleware { } } + $anonLimit = $this->reflector->getAnnotationParameter('AnonRateThrottle', 'limit'); + $anonPeriod = $this->reflector->getAnnotationParameter('AnonRateThrottle', 'period'); + $userLimit = $this->reflector->getAnnotationParameter('UserRateThrottle', 'limit'); + $userPeriod = $this->reflector->getAnnotationParameter('UserRateThrottle', 'period'); + $rateLimitIdentifier = get_class($controller) . '::' . $methodName; + if($userLimit !== '' && $userPeriod !== '' && $this->userSession->isLoggedIn()) { + $this->limiter->registerUserRequest( + $rateLimitIdentifier, + $userLimit, + $userPeriod, + $this->userSession->getUser() + ); + } elseif ($anonLimit !== '' && $anonPeriod !== '') { + $this->limiter->registerAnonRequest( + $rateLimitIdentifier, + $anonLimit, + $anonPeriod, + $this->request->getRemoteAddress() + ); + } + if($this->reflector->hasAnnotation('BruteForceProtection')) { - $action = $this->reflector->getAnnotationParameter('BruteForceProtection'); + $action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action'); $this->throttler->sleepDelay($this->request->getRemoteAddress(), $action); $this->throttler->registerAttempt($action, $this->request->getRemoteAddress()); } @@ -206,7 +232,6 @@ class SecurityMiddleware extends Middleware { if(\OC_App::getAppPath($this->appName) !== false && !\OC_App::isEnabled($this->appName)) { throw new AppNotEnabledException(); } - } /** diff --git a/lib/private/AppFramework/Utility/ControllerMethodReflector.php b/lib/private/AppFramework/Utility/ControllerMethodReflector.php index 034fc3a1759..19eafdb25ac 100644 --- a/lib/private/AppFramework/Utility/ControllerMethodReflector.php +++ b/lib/private/AppFramework/Utility/ControllerMethodReflector.php @@ -24,27 +24,17 @@ * */ - namespace OC\AppFramework\Utility; use \OCP\AppFramework\Utility\IControllerMethodReflector; - /** * Reads and parses annotations from doc comments */ -class ControllerMethodReflector implements IControllerMethodReflector{ - - private $annotations; - private $types; - private $parameters; - - public function __construct() { - $this->types = array(); - $this->parameters = array(); - $this->annotations = array(); - } - +class ControllerMethodReflector implements IControllerMethodReflector { + public $annotations = []; + private $types = []; + private $parameters = []; /** * @param object $object an object or classname @@ -55,9 +45,21 @@ class ControllerMethodReflector implements IControllerMethodReflector{ $docs = $reflection->getDocComment(); // extract everything prefixed by @ and first letter uppercase - preg_match_all('/^\h+\*\h+@(?P<annotation>[A-Z]\w+)(\h+(?P<parameter>\w+))?$/m', $docs, $matches); + preg_match_all('/^\h+\*\h+@(?P<annotation>[A-Z]\w+)((?P<parameter>.*))?$/m', $docs, $matches); foreach($matches['annotation'] as $key => $annontation) { - $this->annotations[$annontation] = $matches['parameter'][$key]; + $annotationValue = $matches['parameter'][$key]; + if($annotationValue[0] === '(' && $annotationValue[strlen($annotationValue) - 1] === ')') { + $cutString = substr($annotationValue, 1, -1); + $cutString = str_replace(' ', '', $cutString); + $splittedArray = explode(',', $cutString); + foreach($splittedArray as $annotationValues) { + list($key, $value) = explode('=', $annotationValues); + $this->annotations[$annontation][$key] = $value; + } + continue; + } + + $this->annotations[$annontation] = [$annotationValue]; } // extract type parameter information @@ -83,7 +85,6 @@ class ControllerMethodReflector implements IControllerMethodReflector{ } } - /** * Inspects the PHPDoc parameters for types * @param string $parameter the parameter whose type comments should be @@ -99,7 +100,6 @@ class ControllerMethodReflector implements IControllerMethodReflector{ } } - /** * @return array the arguments of the method with key => default value */ @@ -107,30 +107,27 @@ class ControllerMethodReflector implements IControllerMethodReflector{ return $this->parameters; } - /** * Check if a method contains an annotation * @param string $name the name of the annotation * @return bool true if the annotation is found */ - public function hasAnnotation($name){ + public function hasAnnotation($name) { return array_key_exists($name, $this->annotations); } - /** - * Get optional annotation parameter + * Get optional annotation parameter by key + * * @param string $name the name of the annotation + * @param string $key the string of the annotation * @return string */ - public function getAnnotationParameter($name){ - $parameter = ''; - if($this->hasAnnotation($name)) { - $parameter = $this->annotations[$name]; + public function getAnnotationParameter($name, $key) { + if(isset($this->annotations[$name][$key])) { + return $this->annotations[$name][$key]; } - return $parameter; + return ''; } - - } |