diff options
author | Lukas Reschke <lukas@statuscode.ch> | 2017-04-12 22:14:11 +0200 |
---|---|---|
committer | Lukas Reschke <lukas@statuscode.ch> | 2017-04-13 12:00:17 +0200 |
commit | a1ae5275f9b2aa4d6446aca5b38e6f324f632832 (patch) | |
tree | f3f54f0805ff809428d1a1331a27bfc5a40706bf /lib/private/AppFramework | |
parent | a05471eb43bd10ab33008993b919375a2a1e8b41 (diff) | |
download | nextcloud-server-a1ae5275f9b2aa4d6446aca5b38e6f324f632832.tar.gz nextcloud-server-a1ae5275f9b2aa4d6446aca5b38e6f324f632832.zip |
Move to dedicated MiddleWare
Signed-off-by: Lukas Reschke <lukas@statuscode.ch>
Diffstat (limited to 'lib/private/AppFramework')
3 files changed, 160 insertions, 38 deletions
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index 588f52e452c..d279140afbb 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -40,6 +40,7 @@ use OC\AppFramework\Http\Output; use OC\AppFramework\Middleware\MiddlewareDispatcher; use OC\AppFramework\Middleware\Security\CORSMiddleware; use OC\AppFramework\Middleware\OCSMiddleware; +use OC\AppFramework\Middleware\Security\RateLimitingMiddleware; use OC\AppFramework\Middleware\Security\SecurityMiddleware; use OC\AppFramework\Middleware\SessionMiddleware; use OC\AppFramework\Utility\SimpleContainer; @@ -219,17 +220,28 @@ class DIContainer extends SimpleContainer implements IAppContainer { $server->getLogger(), $server->getSession(), $c['AppName'], - $server->getUserSession(), + $app->isLoggedIn(), $app->isAdminUser(), $server->getContentSecurityPolicyManager(), $server->getCsrfTokenManager(), $server->getContentSecurityPolicyNonceManager(), - $server->getBruteForceThrottler(), - $c->query(OC\Security\RateLimiting\Limiter::class) + $server->getBruteForceThrottler() ); }); + $this->registerService('RateLimitingMiddleware', function($c) use ($app) { + /** @var \OC\Server $server */ + $server = $app->getServer(); + + return new RateLimitingMiddleware( + $server->getRequest(), + $server->getUserSession(), + $c['ControllerMethodReflector'], + $c->query(OC\Security\RateLimiting\Limiter::class) + ); + }); + $this->registerService('CORSMiddleware', function($c) { return new CORSMiddleware( $c['Request'], @@ -270,6 +282,7 @@ class DIContainer extends SimpleContainer implements IAppContainer { $dispatcher->registerMiddleware($c['OCSMiddleware']); $dispatcher->registerMiddleware($c['SecurityMiddleware']); $dispatcher->registerMiddleWare($c['TwoFactorMiddleware']); + $dispatcher->registerMiddleware($c['RateLimitingMiddleware']); foreach($middleWares as $middleWare) { $dispatcher->registerMiddleware($c[$middleWare]); diff --git a/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php b/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php new file mode 100644 index 00000000000..e2ad7955dd0 --- /dev/null +++ b/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php @@ -0,0 +1,134 @@ +<?php +/** + * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\AppFramework\Middleware\Security; + +use OC\AppFramework\Utility\ControllerMethodReflector; +use OC\Security\RateLimiting\Exception\RateLimitExceededException; +use OC\Security\RateLimiting\Limiter; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\Response; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Middleware; +use OCP\IRequest; +use OCP\IUserSession; + +/** + * Class RateLimitingMiddleware is the middleware responsible for implementing the + * ratelimiting in Nextcloud. + * + * It parses annotations such as: + * + * @UserRateThrottle(limit=5, period=100) + * @AnonRateThrottle(limit=1, period=100) + * + * Those annotations above 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. + * + * @package OC\AppFramework\Middleware\Security + */ +class RateLimitingMiddleware extends Middleware { + /** @var IRequest $request */ + private $request; + /** @var IUserSession */ + private $userSession; + /** @var ControllerMethodReflector */ + private $reflector; + /** @var Limiter */ + private $limiter; + + /** + * @param IRequest $request + * @param IUserSession $userSession + * @param ControllerMethodReflector $reflector + * @param Limiter $limiter + */ + public function __construct(IRequest $request, + IUserSession $userSession, + ControllerMethodReflector $reflector, + Limiter $limiter) { + $this->request = $request; + $this->userSession = $userSession; + $this->reflector = $reflector; + $this->limiter = $limiter; + } + + /** + * {@inheritDoc} + * @throws RateLimitExceededException + */ + public function beforeController($controller, $methodName) { + parent::beforeController($controller, $methodName); + + $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() + ); + } + } + + /** + * {@inheritDoc} + */ + public function afterException($controller, $methodName, \Exception $exception) { + if($exception instanceof RateLimitExceededException) { + if (stripos($this->request->getHeader('Accept'),'html') === false) { + $response = new JSONResponse( + [ + 'message' => $exception->getMessage(), + ], + $exception->getCode() + ); + } else { + $response = new TemplateResponse( + 'core', + '403', + [ + 'file' => $exception->getMessage() + ], + 'guest' + ); + $response->setStatus($exception->getCode()); + } + + return $response; + } + + throw $exception; + } +} diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index d8bbe84c86e..d4d0598c94f 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -26,6 +26,7 @@ * */ + namespace OC\AppFramework\Middleware\Security; use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException; @@ -39,7 +40,6 @@ 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,7 +54,6 @@ 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; @@ -79,8 +78,8 @@ class SecurityMiddleware extends Middleware { private $logger; /** @var ISession */ private $session; - /** @var IUserSession */ - private $userSession; + /** @var bool */ + private $isLoggedIn; /** @var bool */ private $isAdminUser; /** @var ContentSecurityPolicyManager */ @@ -91,8 +90,6 @@ class SecurityMiddleware extends Middleware { private $cspNonceManager; /** @var Throttler */ private $throttler; - /** @var Limiter */ - private $limiter; /** * @param IRequest $request @@ -102,13 +99,12 @@ class SecurityMiddleware extends Middleware { * @param ILogger $logger * @param ISession $session * @param string $appName - * @param IUserSession $userSession + * @param bool $isLoggedIn * @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, @@ -117,13 +113,12 @@ class SecurityMiddleware extends Middleware { ILogger $logger, ISession $session, $appName, - IUserSession $userSession, + $isLoggedIn, $isAdminUser, ContentSecurityPolicyManager $contentSecurityPolicyManager, CsrfTokenManager $csrfTokenManager, ContentSecurityPolicyNonceManager $cspNonceManager, - Throttler $throttler, - Limiter $limiter) { + Throttler $throttler) { $this->navigationManager = $navigationManager; $this->request = $request; $this->reflector = $reflector; @@ -131,15 +126,15 @@ class SecurityMiddleware extends Middleware { $this->urlGenerator = $urlGenerator; $this->logger = $logger; $this->session = $session; - $this->userSession = $userSession; + $this->isLoggedIn = $isLoggedIn; $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 @@ -157,7 +152,7 @@ class SecurityMiddleware extends Middleware { // security checks $isPublicPage = $this->reflector->hasAnnotation('PublicPage'); if(!$isPublicPage) { - if(!$this->userSession->isLoggedIn()) { + if(!$this->isLoggedIn) { throw new NotLoggedInException(); } @@ -196,27 +191,6 @@ 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->throttler->sleepDelay($this->request->getRemoteAddress(), $action); @@ -232,6 +206,7 @@ class SecurityMiddleware extends Middleware { if(\OC_App::getAppPath($this->appName) !== false && !\OC_App::isEnabled($this->appName)) { throw new AppNotEnabledException(); } + } /** |