2017-04-13 16:50:44 -04:00
< ? php
2025-06-30 09:04:05 -04:00
2017-04-13 16:50:44 -04:00
/**
2024-05-10 09:09:14 -04:00
* SPDX - FileCopyrightText : 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX - License - Identifier : AGPL - 3.0 - or - later
2017-04-13 16:50:44 -04:00
*/
namespace Test\AppFramework\Middleware\Security ;
use OC\AppFramework\Middleware\Security\BruteForceMiddleware ;
use OC\AppFramework\Utility\ControllerMethodReflector ;
use OCP\AppFramework\Http\Response ;
use OCP\IRequest ;
2023-08-28 09:50:45 -04:00
use OCP\Security\Bruteforce\IThrottler ;
2023-03-08 06:08:53 -05:00
use Psr\Log\LoggerInterface ;
2025-09-27 13:32:42 -04:00
use Test\AppFramework\Middleware\Security\Mock\BruteForceMiddlewareController ;
2017-04-13 16:50:44 -04:00
use Test\TestCase ;
class BruteForceMiddlewareTest extends TestCase {
2023-02-28 16:26:22 -05:00
/** @var ControllerMethodReflector */
2017-04-13 16:50:44 -04:00
private $reflector ;
2023-08-28 09:50:45 -04:00
/** @var IThrottler|\PHPUnit\Framework\MockObject\MockObject */
2017-04-13 16:50:44 -04:00
private $throttler ;
2020-08-11 15:32:18 -04:00
/** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */
2017-04-13 16:50:44 -04:00
private $request ;
2023-03-08 06:08:53 -05:00
/** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
private $logger ;
2022-04-12 11:55:01 -04:00
private BruteForceMiddleware $bruteForceMiddleware ;
2017-04-13 16:50:44 -04:00
2019-11-27 09:27:18 -05:00
protected function setUp () : void {
2017-04-13 16:50:44 -04:00
parent :: setUp ();
2023-02-28 16:26:22 -05:00
$this -> reflector = new ControllerMethodReflector ();
2023-08-28 09:50:45 -04:00
$this -> throttler = $this -> createMock ( IThrottler :: class );
2017-04-13 16:50:44 -04:00
$this -> request = $this -> createMock ( IRequest :: class );
2023-03-08 06:08:53 -05:00
$this -> logger = $this -> createMock ( LoggerInterface :: class );
2017-04-13 16:50:44 -04:00
$this -> bruteForceMiddleware = new BruteForceMiddleware (
$this -> reflector ,
$this -> throttler ,
2023-03-08 06:08:53 -05:00
$this -> request ,
$this -> logger ,
2017-04-13 16:50:44 -04:00
);
}
2023-03-08 06:08:53 -05:00
public function testBeforeControllerWithAnnotation () : void {
2017-04-13 16:50:44 -04:00
$this -> request
-> expects ( $this -> once ())
-> method ( 'getRemoteAddress' )
-> willReturn ( '127.0.0.1' );
$this -> throttler
-> expects ( $this -> once ())
2020-08-19 06:40:25 -04:00
-> method ( 'sleepDelayOrThrowOnMax' )
2017-04-13 16:50:44 -04:00
-> with ( '127.0.0.1' , 'login' );
2025-09-27 13:32:42 -04:00
$controller = new BruteForceMiddlewareController ( 'test' , $this -> request );
2023-02-28 16:26:22 -05:00
$this -> reflector -> reflect ( $controller , 'testMethodWithAnnotation' );
$this -> bruteForceMiddleware -> beforeController ( $controller , 'testMethodWithAnnotation' );
2017-04-13 16:50:44 -04:00
}
2023-02-28 16:26:22 -05:00
public function testBeforeControllerWithSingleAttribute () : void {
$this -> request
2017-04-13 16:50:44 -04:00
-> expects ( $this -> once ())
2023-02-28 16:26:22 -05:00
-> method ( 'getRemoteAddress' )
-> willReturn ( '::1' );
$this -> throttler
-> expects ( $this -> once ())
-> method ( 'sleepDelayOrThrowOnMax' )
-> with ( '::1' , 'single' );
2025-09-27 13:32:42 -04:00
$controller = new BruteForceMiddlewareController ( 'test' , $this -> request );
2023-02-28 16:26:22 -05:00
$this -> reflector -> reflect ( $controller , 'singleAttribute' );
$this -> bruteForceMiddleware -> beforeController ( $controller , 'singleAttribute' );
}
public function testBeforeControllerWithMultipleAttributes () : void {
$this -> request
-> expects ( $this -> once ())
-> method ( 'getRemoteAddress' )
-> willReturn ( '::1' );
2025-05-09 11:14:51 -04:00
$calls = [
[ '::1' , 'first' ],
[ '::1' , 'second' ],
];
2023-02-28 16:26:22 -05:00
$this -> throttler
-> expects ( $this -> exactly ( 2 ))
-> method ( 'sleepDelayOrThrowOnMax' )
2025-05-15 02:48:13 -04:00
-> willReturnCallback ( function () use ( & $calls ) {
2025-05-09 11:14:51 -04:00
$expected = array_shift ( $calls );
$this -> assertEquals ( $expected , func_get_args ());
return 0 ;
});
2023-02-28 16:26:22 -05:00
2025-09-27 13:32:42 -04:00
$controller = new BruteForceMiddlewareController ( 'test' , $this -> request );
2023-02-28 16:26:22 -05:00
$this -> reflector -> reflect ( $controller , 'multipleAttributes' );
$this -> bruteForceMiddleware -> beforeController ( $controller , 'multipleAttributes' );
}
2023-03-08 06:08:53 -05:00
public function testBeforeControllerWithoutAnnotation () : void {
2017-04-13 16:50:44 -04:00
$this -> request
-> expects ( $this -> never ())
-> method ( 'getRemoteAddress' );
$this -> throttler
-> expects ( $this -> never ())
2020-08-19 06:40:25 -04:00
-> method ( 'sleepDelayOrThrowOnMax' );
2017-04-13 16:50:44 -04:00
2025-09-27 13:32:42 -04:00
$controller = new BruteForceMiddlewareController ( 'test' , $this -> request );
2023-02-28 16:26:22 -05:00
$this -> reflector -> reflect ( $controller , 'testMethodWithoutAnnotation' );
$this -> bruteForceMiddleware -> beforeController ( $controller , 'testMethodWithoutAnnotation' );
2017-04-13 16:50:44 -04:00
}
2023-03-08 06:08:53 -05:00
public function testAfterControllerWithAnnotationAndThrottledRequest () : void {
2020-08-11 15:32:18 -04:00
/** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
2017-04-13 16:50:44 -04:00
$response = $this -> createMock ( Response :: class );
$response
-> expects ( $this -> once ())
-> method ( 'isThrottled' )
-> willReturn ( true );
2017-07-27 08:14:20 -04:00
$response
-> expects ( $this -> once ())
-> method ( 'getThrottleMetadata' )
-> willReturn ([]);
2017-04-13 16:50:44 -04:00
$this -> request
-> expects ( $this -> once ())
-> method ( 'getRemoteAddress' )
-> willReturn ( '127.0.0.1' );
$this -> throttler
-> expects ( $this -> once ())
2023-05-11 03:17:30 -04:00
-> method ( 'sleepDelayOrThrowOnMax' )
2017-04-13 16:50:44 -04:00
-> with ( '127.0.0.1' , 'login' );
$this -> throttler
-> expects ( $this -> once ())
-> method ( 'registerAttempt' )
-> with ( 'login' , '127.0.0.1' );
2025-09-27 13:32:42 -04:00
$controller = new BruteForceMiddlewareController ( 'test' , $this -> request );
2023-02-28 16:26:22 -05:00
$this -> reflector -> reflect ( $controller , 'testMethodWithAnnotation' );
$this -> bruteForceMiddleware -> afterController ( $controller , 'testMethodWithAnnotation' , $response );
2017-04-13 16:50:44 -04:00
}
2023-03-08 06:08:53 -05:00
public function testAfterControllerWithAnnotationAndNotThrottledRequest () : void {
2020-08-11 15:32:18 -04:00
/** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
2017-04-13 16:50:44 -04:00
$response = $this -> createMock ( Response :: class );
$response
-> expects ( $this -> once ())
-> method ( 'isThrottled' )
-> willReturn ( false );
$this -> request
-> expects ( $this -> never ())
-> method ( 'getRemoteAddress' );
$this -> throttler
-> expects ( $this -> never ())
2023-05-11 03:17:30 -04:00
-> method ( 'sleepDelayOrThrowOnMax' );
2017-04-13 16:50:44 -04:00
$this -> throttler
-> expects ( $this -> never ())
-> method ( 'registerAttempt' );
2025-09-27 13:32:42 -04:00
$controller = new BruteForceMiddlewareController ( 'test' , $this -> request );
2023-02-28 16:26:22 -05:00
$this -> reflector -> reflect ( $controller , 'testMethodWithAnnotation' );
$this -> bruteForceMiddleware -> afterController ( $controller , 'testMethodWithAnnotation' , $response );
2017-04-13 16:50:44 -04:00
}
2023-02-28 16:26:22 -05:00
public function testAfterControllerWithSingleAttribute () : void {
/** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
$response = $this -> createMock ( Response :: class );
$response
2017-04-13 16:50:44 -04:00
-> expects ( $this -> once ())
2023-02-28 16:26:22 -05:00
-> method ( 'isThrottled' )
-> willReturn ( true );
$response
-> expects ( $this -> once ())
-> method ( 'getThrottleMetadata' )
-> willReturn ([]);
$this -> request
-> expects ( $this -> once ())
-> method ( 'getRemoteAddress' )
-> willReturn ( '::1' );
$this -> throttler
-> expects ( $this -> once ())
2023-05-11 03:17:30 -04:00
-> method ( 'sleepDelayOrThrowOnMax' )
2023-02-28 16:26:22 -05:00
-> with ( '::1' , 'single' );
$this -> throttler
-> expects ( $this -> once ())
-> method ( 'registerAttempt' )
-> with ( 'single' , '::1' );
2025-09-27 13:32:42 -04:00
$controller = new BruteForceMiddlewareController ( 'test' , $this -> request );
2023-02-28 16:26:22 -05:00
$this -> reflector -> reflect ( $controller , 'singleAttribute' );
$this -> bruteForceMiddleware -> afterController ( $controller , 'singleAttribute' , $response );
}
public function testAfterControllerWithMultipleAttributesGeneralMatch () : void {
/** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
$response = $this -> createMock ( Response :: class );
$response
-> expects ( $this -> once ())
-> method ( 'isThrottled' )
-> willReturn ( true );
$response
-> expects ( $this -> once ())
-> method ( 'getThrottleMetadata' )
-> willReturn ([]);
$this -> request
-> expects ( $this -> once ())
-> method ( 'getRemoteAddress' )
-> willReturn ( '::1' );
2025-05-09 11:14:51 -04:00
$sleepCalls = [
[ '::1' , 'first' ],
[ '::1' , 'second' ],
];
2023-02-28 16:26:22 -05:00
$this -> throttler
-> expects ( $this -> exactly ( 2 ))
2023-05-11 03:17:30 -04:00
-> method ( 'sleepDelayOrThrowOnMax' )
2025-05-15 02:48:13 -04:00
-> willReturnCallback ( function () use ( & $sleepCalls ) {
2025-05-09 11:14:51 -04:00
$expected = array_shift ( $sleepCalls );
$this -> assertEquals ( $expected , func_get_args ());
return 0 ;
});
$attemptCalls = [
[ 'first' , '::1' , []],
[ 'second' , '::1' , []],
];
2023-02-28 16:26:22 -05:00
$this -> throttler
-> expects ( $this -> exactly ( 2 ))
-> method ( 'registerAttempt' )
2025-06-12 12:31:58 -04:00
-> willReturnCallback ( function () use ( & $attemptCalls ) : void {
2025-05-09 11:14:51 -04:00
$expected = array_shift ( $attemptCalls );
$this -> assertEquals ( $expected , func_get_args ());
});
2023-02-28 16:26:22 -05:00
2025-09-27 13:32:42 -04:00
$controller = new BruteForceMiddlewareController ( 'test' , $this -> request );
2023-02-28 16:26:22 -05:00
$this -> reflector -> reflect ( $controller , 'multipleAttributes' );
$this -> bruteForceMiddleware -> afterController ( $controller , 'multipleAttributes' , $response );
}
public function testAfterControllerWithMultipleAttributesSpecificMatch () : void {
/** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
$response = $this -> createMock ( Response :: class );
$response
-> expects ( $this -> once ())
-> method ( 'isThrottled' )
-> willReturn ( true );
$response
-> expects ( $this -> once ())
-> method ( 'getThrottleMetadata' )
-> willReturn ([ 'action' => 'second' ]);
$this -> request
-> expects ( $this -> once ())
-> method ( 'getRemoteAddress' )
-> willReturn ( '::1' );
$this -> throttler
-> expects ( $this -> once ())
2023-05-11 03:17:30 -04:00
-> method ( 'sleepDelayOrThrowOnMax' )
2023-02-28 16:26:22 -05:00
-> with ( '::1' , 'second' );
$this -> throttler
-> expects ( $this -> once ())
-> method ( 'registerAttempt' )
-> with ( 'second' , '::1' );
2025-09-27 13:32:42 -04:00
$controller = new BruteForceMiddlewareController ( 'test' , $this -> request );
2023-02-28 16:26:22 -05:00
$this -> reflector -> reflect ( $controller , 'multipleAttributes' );
$this -> bruteForceMiddleware -> afterController ( $controller , 'multipleAttributes' , $response );
}
2023-03-08 06:08:53 -05:00
public function testAfterControllerWithoutAnnotation () : void {
2017-04-13 16:50:44 -04:00
$this -> request
-> expects ( $this -> never ())
-> method ( 'getRemoteAddress' );
$this -> throttler
-> expects ( $this -> never ())
2023-05-11 03:17:30 -04:00
-> method ( 'sleepDelayOrThrowOnMax' );
2017-04-13 16:50:44 -04:00
2025-09-27 13:32:42 -04:00
$controller = new BruteForceMiddlewareController ( 'test' , $this -> request );
2023-02-28 16:26:22 -05:00
$this -> reflector -> reflect ( $controller , 'testMethodWithoutAnnotation' );
2020-08-11 15:32:18 -04:00
/** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
2017-04-13 16:50:44 -04:00
$response = $this -> createMock ( Response :: class );
2023-02-28 16:26:22 -05:00
$this -> bruteForceMiddleware -> afterController ( $controller , 'testMethodWithoutAnnotation' , $response );
2017-04-13 16:50:44 -04:00
}
2023-03-08 06:08:53 -05:00
public function testAfterControllerWithThrottledResponseButUnhandled () : void {
$this -> request
-> expects ( $this -> never ())
-> method ( 'getRemoteAddress' );
$this -> throttler
-> expects ( $this -> never ())
2023-05-11 03:17:30 -04:00
-> method ( 'sleepDelayOrThrowOnMax' );
2023-03-08 06:08:53 -05:00
2025-09-27 13:32:42 -04:00
$controller = new BruteForceMiddlewareController ( 'test' , $this -> request );
2023-03-08 06:08:53 -05:00
$this -> reflector -> reflect ( $controller , 'testMethodWithoutAnnotation' );
/** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
$response = $this -> createMock ( Response :: class );
$response -> method ( 'isThrottled' )
-> willReturn ( true );
$this -> logger -> expects ( $this -> once ())
-> method ( 'debug' )
2025-09-27 13:32:42 -04:00
-> with ( 'Response for Test\AppFramework\Middleware\Security\Mock\BruteForceMiddlewareController::testMethodWithoutAnnotation got bruteforce throttled but has no annotation nor attribute defined.' );
2023-03-08 06:08:53 -05:00
$this -> bruteForceMiddleware -> afterController ( $controller , 'testMethodWithoutAnnotation' , $response );
}
2017-04-13 16:50:44 -04:00
}