2017-04-13 16:50:44 -04:00
< ? php
/**
2023-02-28 16:26:22 -05:00
* @ copyright Copyright ( c ) 2023 Joas Schilling < coding @ schilljs . com >
2017-04-13 16:50:44 -04:00
* @ 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 ;
2023-02-28 16:26:22 -05:00
use OCP\AppFramework\Http\Attribute\BruteForceProtection ;
2017-04-13 16:50:44 -04:00
use OCP\AppFramework\Http\Response ;
use OCP\IRequest ;
2023-03-08 06:08:53 -05:00
use Psr\Log\LoggerInterface ;
2017-04-13 16:50:44 -04:00
use Test\TestCase ;
2023-02-28 16:26:22 -05:00
class TestController extends Controller {
/**
* @ BruteForceProtection ( action = login )
*/
public function testMethodWithAnnotation () {
}
public function testMethodWithoutAnnotation () {
}
#[BruteForceProtection(action: 'single')]
public function singleAttribute () : void {
}
#[BruteForceProtection(action: 'first')]
#[BruteForceProtection(action: 'second')]
public function multipleAttributes () : void {
}
}
2017-04-13 16:50:44 -04:00
class BruteForceMiddlewareTest extends TestCase {
2023-02-28 16:26:22 -05:00
/** @var ControllerMethodReflector */
2017-04-13 16:50:44 -04:00
private $reflector ;
2020-08-11 15:32:18 -04:00
/** @var Throttler|\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 ();
2017-04-13 16:50:44 -04:00
$this -> throttler = $this -> createMock ( Throttler :: class );
$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' );
2023-02-28 16:26:22 -05:00
$controller = new TestController ( 'test' , $this -> request );
$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' );
$controller = new TestController ( 'test' , $this -> request );
$this -> reflector -> reflect ( $controller , 'singleAttribute' );
$this -> bruteForceMiddleware -> beforeController ( $controller , 'singleAttribute' );
}
public function testBeforeControllerWithMultipleAttributes () : void {
$this -> request
-> expects ( $this -> once ())
-> method ( 'getRemoteAddress' )
-> willReturn ( '::1' );
$this -> throttler
-> expects ( $this -> exactly ( 2 ))
-> method ( 'sleepDelayOrThrowOnMax' )
-> withConsecutive (
[ '::1' , 'first' ],
[ '::1' , 'second' ],
);
$controller = new TestController ( 'test' , $this -> request );
$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
2023-02-28 16:26:22 -05:00
$controller = new TestController ( 'test' , $this -> request );
$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' );
2023-02-28 16:26:22 -05:00
$controller = new TestController ( 'test' , $this -> request );
$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' );
2023-02-28 16:26:22 -05:00
$controller = new TestController ( 'test' , $this -> request );
$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' );
$controller = new TestController ( 'test' , $this -> request );
$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' );
$this -> throttler
-> expects ( $this -> exactly ( 2 ))
2023-05-11 03:17:30 -04:00
-> method ( 'sleepDelayOrThrowOnMax' )
2023-02-28 16:26:22 -05:00
-> withConsecutive (
[ '::1' , 'first' ],
[ '::1' , 'second' ],
);
$this -> throttler
-> expects ( $this -> exactly ( 2 ))
-> method ( 'registerAttempt' )
-> withConsecutive (
[ 'first' , '::1' ],
[ 'second' , '::1' ],
);
$controller = new TestController ( 'test' , $this -> request );
$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' );
$controller = new TestController ( 'test' , $this -> request );
$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
2023-02-28 16:26:22 -05:00
$controller = new TestController ( 'test' , $this -> request );
$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
$controller = new TestController ( 'test' , $this -> request );
$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' )
-> with ( 'Response for Test\AppFramework\Middleware\Security\TestController::testMethodWithoutAnnotation got bruteforce throttled but has no annotation nor attribute defined.' );
$this -> bruteForceMiddleware -> afterController ( $controller , 'testMethodWithoutAnnotation' , $response );
}
2017-04-13 16:50:44 -04:00
}