Browse Source
Make BruteForceProtection annotation more clever
Make BruteForceProtection annotation more clever
This makes the new `@BruteForceProtection` annotation more clever and moves the relevant code into it's own middleware. Basically you can now set `@BruteForceProtection(action=$key)` as annotation and that will make the controller bruteforce protected. However, the difference to before is that you need to call `$responmse->throttle()` to increase the counter. Before the counter was increased every time which leads to all kind of unexpected problems. Signed-off-by: Lukas Reschke <lukas@statuscode.ch>pull/4346/head
No known key found for this signature in database
GPG Key ID: B9F6980CF6E759B1
11 changed files with 329 additions and 247 deletions
-
36core/Controller/LoginController.php
-
1lib/composer/composer/autoload_classmap.php
-
1lib/composer/composer/autoload_static.php
-
17lib/private/AppFramework/DependencyInjection/DIContainer.php
-
83lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php
-
15lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php
-
19lib/public/AppFramework/Http/Response.php
-
133tests/Core/Controller/LoginControllerTest.php
-
5tests/lib/AppFramework/Http/ResponseTest.php
-
190tests/lib/AppFramework/Middleware/Security/BruteForceMiddlewareTest.php
-
76tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php
@ -0,0 +1,83 @@ |
|||
<?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\Bruteforce\Throttler; |
|||
use OCP\AppFramework\Http\Response; |
|||
use OCP\AppFramework\Middleware; |
|||
use OCP\IRequest; |
|||
|
|||
/** |
|||
* Class BruteForceMiddleware performs the bruteforce protection for controllers |
|||
* that are annotated with @BruteForceProtection(action=$action) whereas $action |
|||
* is the action that should be logged within the database. |
|||
* |
|||
* @package OC\AppFramework\Middleware\Security |
|||
*/ |
|||
class BruteForceMiddleware extends Middleware { |
|||
/** @var ControllerMethodReflector */ |
|||
private $reflector; |
|||
/** @var Throttler */ |
|||
private $throttler; |
|||
/** @var IRequest */ |
|||
private $request; |
|||
|
|||
/** |
|||
* @param ControllerMethodReflector $controllerMethodReflector |
|||
* @param Throttler $throttler |
|||
* @param IRequest $request |
|||
*/ |
|||
public function __construct(ControllerMethodReflector $controllerMethodReflector, |
|||
Throttler $throttler, |
|||
IRequest $request) { |
|||
$this->reflector = $controllerMethodReflector; |
|||
$this->throttler = $throttler; |
|||
$this->request = $request; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function beforeController($controller, $methodName) { |
|||
parent::beforeController($controller, $methodName); |
|||
|
|||
if($this->reflector->hasAnnotation('BruteForceProtection')) { |
|||
$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action'); |
|||
$this->throttler->sleepDelay($this->request->getRemoteAddress(), $action); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function afterController($controller, $methodName, Response $response) { |
|||
if($this->reflector->hasAnnotation('BruteForceProtection') && $response->isThrottled()) { |
|||
$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action'); |
|||
$ip = $this->request->getRemoteAddress(); |
|||
$this->throttler->sleepDelay($ip, $action); |
|||
$this->throttler->registerAttempt($action, $ip); |
|||
} |
|||
|
|||
return parent::afterController($controller, $methodName, $response); |
|||
} |
|||
} |
@ -0,0 +1,190 @@ |
|||
<?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 Test\AppFramework\Middleware\Security; |
|||
|
|||
use OC\AppFramework\Middleware\Security\BruteForceMiddleware; |
|||
use OC\AppFramework\Utility\ControllerMethodReflector; |
|||
use OC\Security\Bruteforce\Throttler; |
|||
use OCP\AppFramework\Controller; |
|||
use OCP\AppFramework\Http\Response; |
|||
use OCP\Http\Client\IResponse; |
|||
use OCP\IRequest; |
|||
use Test\TestCase; |
|||
|
|||
class BruteForceMiddlewareTest extends TestCase { |
|||
/** @var ControllerMethodReflector|\PHPUnit_Framework_MockObject_MockObject */ |
|||
private $reflector; |
|||
/** @var Throttler|\PHPUnit_Framework_MockObject_MockObject */ |
|||
private $throttler; |
|||
/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */ |
|||
private $request; |
|||
/** @var BruteForceMiddleware */ |
|||
private $bruteForceMiddleware; |
|||
|
|||
public function setUp() { |
|||
parent::setUp(); |
|||
|
|||
$this->reflector = $this->createMock(ControllerMethodReflector::class); |
|||
$this->throttler = $this->createMock(Throttler::class); |
|||
$this->request = $this->createMock(IRequest::class); |
|||
|
|||
$this->bruteForceMiddleware = new BruteForceMiddleware( |
|||
$this->reflector, |
|||
$this->throttler, |
|||
$this->request |
|||
); |
|||
} |
|||
|
|||
public function testBeforeControllerWithAnnotation() { |
|||
$this->reflector |
|||
->expects($this->once()) |
|||
->method('hasAnnotation') |
|||
->with('BruteForceProtection') |
|||
->willReturn(true); |
|||
$this->reflector |
|||
->expects($this->once()) |
|||
->method('getAnnotationParameter') |
|||
->with('BruteForceProtection', 'action') |
|||
->willReturn('login'); |
|||
$this->request |
|||
->expects($this->once()) |
|||
->method('getRemoteAddress') |
|||
->willReturn('127.0.0.1'); |
|||
$this->throttler |
|||
->expects($this->once()) |
|||
->method('sleepDelay') |
|||
->with('127.0.0.1', 'login'); |
|||
|
|||
/** @var Controller|\PHPUnit_Framework_MockObject_MockObject $controller */ |
|||
$controller = $this->createMock(Controller::class); |
|||
$this->bruteForceMiddleware->beforeController($controller, 'testMethod'); |
|||
} |
|||
|
|||
public function testBeforeControllerWithoutAnnotation() { |
|||
$this->reflector |
|||
->expects($this->once()) |
|||
->method('hasAnnotation') |
|||
->with('BruteForceProtection') |
|||
->willReturn(false); |
|||
$this->reflector |
|||
->expects($this->never()) |
|||
->method('getAnnotationParameter'); |
|||
$this->request |
|||
->expects($this->never()) |
|||
->method('getRemoteAddress'); |
|||
$this->throttler |
|||
->expects($this->never()) |
|||
->method('sleepDelay'); |
|||
|
|||
/** @var Controller|\PHPUnit_Framework_MockObject_MockObject $controller */ |
|||
$controller = $this->createMock(Controller::class); |
|||
$this->bruteForceMiddleware->beforeController($controller, 'testMethod'); |
|||
} |
|||
|
|||
public function testAfterControllerWithAnnotationAndThrottledRequest() { |
|||
/** @var Response|\PHPUnit_Framework_MockObject_MockObject $response */ |
|||
$response = $this->createMock(Response::class); |
|||
$this->reflector |
|||
->expects($this->once()) |
|||
->method('hasAnnotation') |
|||
->with('BruteForceProtection') |
|||
->willReturn(true); |
|||
$response |
|||
->expects($this->once()) |
|||
->method('isThrottled') |
|||
->willReturn(true); |
|||
$this->reflector |
|||
->expects($this->once()) |
|||
->method('getAnnotationParameter') |
|||
->with('BruteForceProtection', 'action') |
|||
->willReturn('login'); |
|||
$this->request |
|||
->expects($this->once()) |
|||
->method('getRemoteAddress') |
|||
->willReturn('127.0.0.1'); |
|||
$this->throttler |
|||
->expects($this->once()) |
|||
->method('sleepDelay') |
|||
->with('127.0.0.1', 'login'); |
|||
$this->throttler |
|||
->expects($this->once()) |
|||
->method('registerAttempt') |
|||
->with('login', '127.0.0.1'); |
|||
|
|||
/** @var Controller|\PHPUnit_Framework_MockObject_MockObject $controller */ |
|||
$controller = $this->createMock(Controller::class); |
|||
$this->bruteForceMiddleware->afterController($controller, 'testMethod' ,$response); |
|||
} |
|||
|
|||
public function testAfterControllerWithAnnotationAndNotThrottledRequest() { |
|||
/** @var Response|\PHPUnit_Framework_MockObject_MockObject $response */ |
|||
$response = $this->createMock(Response::class); |
|||
$this->reflector |
|||
->expects($this->once()) |
|||
->method('hasAnnotation') |
|||
->with('BruteForceProtection') |
|||
->willReturn(true); |
|||
$response |
|||
->expects($this->once()) |
|||
->method('isThrottled') |
|||
->willReturn(false); |
|||
$this->reflector |
|||
->expects($this->never()) |
|||
->method('getAnnotationParameter'); |
|||
$this->request |
|||
->expects($this->never()) |
|||
->method('getRemoteAddress'); |
|||
$this->throttler |
|||
->expects($this->never()) |
|||
->method('sleepDelay'); |
|||
$this->throttler |
|||
->expects($this->never()) |
|||
->method('registerAttempt'); |
|||
|
|||
/** @var Controller|\PHPUnit_Framework_MockObject_MockObject $controller */ |
|||
$controller = $this->createMock(Controller::class); |
|||
$this->bruteForceMiddleware->afterController($controller, 'testMethod' ,$response); |
|||
} |
|||
|
|||
public function testAfterControllerWithoutAnnotation() { |
|||
$this->reflector |
|||
->expects($this->once()) |
|||
->method('hasAnnotation') |
|||
->with('BruteForceProtection') |
|||
->willReturn(false); |
|||
$this->reflector |
|||
->expects($this->never()) |
|||
->method('getAnnotationParameter'); |
|||
$this->request |
|||
->expects($this->never()) |
|||
->method('getRemoteAddress'); |
|||
$this->throttler |
|||
->expects($this->never()) |
|||
->method('sleepDelay'); |
|||
|
|||
/** @var Controller|\PHPUnit_Framework_MockObject_MockObject $controller */ |
|||
$controller = $this->createMock(Controller::class); |
|||
/** @var Response|\PHPUnit_Framework_MockObject_MockObject $response */ |
|||
$response = $this->createMock(Response::class); |
|||
$this->bruteForceMiddleware->afterController($controller, 'testMethod' ,$response); |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue