Merge pull request #36928 from nextcloud/techdebt/noid/bruteforce-protection-attribute

feat(middleware): Migrate BruteForceProtection annotation to PHP Attribute and allow multiple
pull/37279/head
Joas Schilling 2023-03-16 19:33:13 +07:00 committed by GitHub
commit bfc37afed3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 307 additions and 81 deletions

@ -35,6 +35,7 @@ return array(
'OCP\\AppFramework\\Db\\QBMapper' => $baseDir . '/lib/public/AppFramework/Db/QBMapper.php', 'OCP\\AppFramework\\Db\\QBMapper' => $baseDir . '/lib/public/AppFramework/Db/QBMapper.php',
'OCP\\AppFramework\\Db\\TTransactional' => $baseDir . '/lib/public/AppFramework/Db/TTransactional.php', 'OCP\\AppFramework\\Db\\TTransactional' => $baseDir . '/lib/public/AppFramework/Db/TTransactional.php',
'OCP\\AppFramework\\Http' => $baseDir . '/lib/public/AppFramework/Http.php', 'OCP\\AppFramework\\Http' => $baseDir . '/lib/public/AppFramework/Http.php',
'OCP\\AppFramework\\Http\\Attribute\\BruteForceProtection' => $baseDir . '/lib/public/AppFramework/Http/Attribute/BruteForceProtection.php',
'OCP\\AppFramework\\Http\\Attribute\\UseSession' => $baseDir . '/lib/public/AppFramework/Http/Attribute/UseSession.php', 'OCP\\AppFramework\\Http\\Attribute\\UseSession' => $baseDir . '/lib/public/AppFramework/Http/Attribute/UseSession.php',
'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php', 'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php',
'OCP\\AppFramework\\Http\\DataDisplayResponse' => $baseDir . '/lib/public/AppFramework/Http/DataDisplayResponse.php', 'OCP\\AppFramework\\Http\\DataDisplayResponse' => $baseDir . '/lib/public/AppFramework/Http/DataDisplayResponse.php',

@ -68,6 +68,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\AppFramework\\Db\\QBMapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/QBMapper.php', 'OCP\\AppFramework\\Db\\QBMapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/QBMapper.php',
'OCP\\AppFramework\\Db\\TTransactional' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/TTransactional.php', 'OCP\\AppFramework\\Db\\TTransactional' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/TTransactional.php',
'OCP\\AppFramework\\Http' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http.php', 'OCP\\AppFramework\\Http' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http.php',
'OCP\\AppFramework\\Http\\Attribute\\BruteForceProtection' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/BruteForceProtection.php',
'OCP\\AppFramework\\Http\\Attribute\\UseSession' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/UseSession.php', 'OCP\\AppFramework\\Http\\Attribute\\UseSession' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/UseSession.php',
'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php', 'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php',
'OCP\\AppFramework\\Http\\DataDisplayResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/DataDisplayResponse.php', 'OCP\\AppFramework\\Http\\DataDisplayResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/DataDisplayResponse.php',

@ -292,7 +292,8 @@ class DIContainer extends SimpleContainer implements IAppContainer {
new OC\AppFramework\Middleware\Security\BruteForceMiddleware( new OC\AppFramework\Middleware\Security\BruteForceMiddleware(
$c->get(IControllerMethodReflector::class), $c->get(IControllerMethodReflector::class),
$c->get(OC\Security\Bruteforce\Throttler::class), $c->get(OC\Security\Bruteforce\Throttler::class),
$c->get(IRequest::class) $c->get(IRequest::class),
$c->get(LoggerInterface::class)
) )
); );
$dispatcher->registerMiddleware( $dispatcher->registerMiddleware(

@ -3,6 +3,7 @@
declare(strict_types=1); declare(strict_types=1);
/** /**
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
* *
* @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Christoph Wurst <christoph@winzerhof-wurst.at>
@ -31,6 +32,7 @@ use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\Security\Bruteforce\Throttler; use OC\Security\Bruteforce\Throttler;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TooManyRequestsResponse; use OCP\AppFramework\Http\TooManyRequestsResponse;
use OCP\AppFramework\Middleware; use OCP\AppFramework\Middleware;
@ -38,6 +40,8 @@ use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCSController; use OCP\AppFramework\OCSController;
use OCP\IRequest; use OCP\IRequest;
use OCP\Security\Bruteforce\MaxDelayReached; use OCP\Security\Bruteforce\MaxDelayReached;
use Psr\Log\LoggerInterface;
use ReflectionMethod;
/** /**
* Class BruteForceMiddleware performs the bruteforce protection for controllers * Class BruteForceMiddleware performs the bruteforce protection for controllers
@ -47,16 +51,12 @@ use OCP\Security\Bruteforce\MaxDelayReached;
* @package OC\AppFramework\Middleware\Security * @package OC\AppFramework\Middleware\Security
*/ */
class BruteForceMiddleware extends Middleware { class BruteForceMiddleware extends Middleware {
private ControllerMethodReflector $reflector; public function __construct(
private Throttler $throttler; protected ControllerMethodReflector $reflector,
private IRequest $request; protected Throttler $throttler,
protected IRequest $request,
public function __construct(ControllerMethodReflector $controllerMethodReflector, protected LoggerInterface $logger,
Throttler $throttler, ) {
IRequest $request) {
$this->reflector = $controllerMethodReflector;
$this->throttler = $throttler;
$this->request = $request;
} }
/** /**
@ -68,6 +68,20 @@ class BruteForceMiddleware extends Middleware {
if ($this->reflector->hasAnnotation('BruteForceProtection')) { if ($this->reflector->hasAnnotation('BruteForceProtection')) {
$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action'); $action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
$this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $action); $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $action);
} else {
$reflectionMethod = new ReflectionMethod($controller, $methodName);
$attributes = $reflectionMethod->getAttributes(BruteForceProtection::class);
if (!empty($attributes)) {
$remoteAddress = $this->request->getRemoteAddress();
foreach ($attributes as $attribute) {
/** @var BruteForceProtection $protection */
$protection = $attribute->newInstance();
$action = $protection->getAction();
$this->throttler->sleepDelayOrThrowOnMax($remoteAddress, $action);
}
}
} }
} }
@ -75,11 +89,34 @@ class BruteForceMiddleware extends Middleware {
* {@inheritDoc} * {@inheritDoc}
*/ */
public function afterController($controller, $methodName, Response $response) { public function afterController($controller, $methodName, Response $response) {
if ($this->reflector->hasAnnotation('BruteForceProtection') && $response->isThrottled()) { if ($response->isThrottled()) {
$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action'); if ($this->reflector->hasAnnotation('BruteForceProtection')) {
$ip = $this->request->getRemoteAddress(); $action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
$this->throttler->sleepDelay($ip, $action); $ip = $this->request->getRemoteAddress();
$this->throttler->registerAttempt($action, $ip, $response->getThrottleMetadata()); $this->throttler->sleepDelay($ip, $action);
$this->throttler->registerAttempt($action, $ip, $response->getThrottleMetadata());
} else {
$reflectionMethod = new ReflectionMethod($controller, $methodName);
$attributes = $reflectionMethod->getAttributes(BruteForceProtection::class);
if (!empty($attributes)) {
$ip = $this->request->getRemoteAddress();
$metaData = $response->getThrottleMetadata();
foreach ($attributes as $attribute) {
/** @var BruteForceProtection $protection */
$protection = $attribute->newInstance();
$action = $protection->getAction();
if (!isset($metaData['action']) || $metaData['action'] === $action) {
$this->throttler->sleepDelay($ip, $action);
$this->throttler->registerAttempt($action, $ip, $metaData);
}
}
} else {
$this->logger->debug('Response for ' . get_class($controller) . '::' . $methodName . ' got bruteforce throttled but has no annotation nor attribute defined.');
}
}
} }
return parent::afterController($controller, $methodName, $response); return parent::afterController($controller, $methodName, $response);

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @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 OCP\AppFramework\Http\Attribute;
use Attribute;
/**
* Attribute for controller methods that want to protect passwords, keys, tokens
* or other data against brute force
*
* @since 27.0.0
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class BruteForceProtection {
/**
* @since 27.0.0
*/
public function __construct(
protected string $action
) {
}
/**
* @since 27.0.0
*/
public function getAction(): string {
return $this->action;
}
}

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
/* /**
* @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at> * @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
* *
* @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at> * @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at>

@ -1,5 +1,6 @@
<?php <?php
/** /**
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
* *
* @license GNU AGPL version 3 or any later version * @license GNU AGPL version 3 or any later version
@ -25,44 +26,60 @@ use OC\AppFramework\Middleware\Security\BruteForceMiddleware;
use OC\AppFramework\Utility\ControllerMethodReflector; use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\Security\Bruteforce\Throttler; use OC\Security\Bruteforce\Throttler;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\Response;
use OCP\IRequest; use OCP\IRequest;
use Psr\Log\LoggerInterface;
use Test\TestCase; use Test\TestCase;
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 {
}
}
class BruteForceMiddlewareTest extends TestCase { class BruteForceMiddlewareTest extends TestCase {
/** @var ControllerMethodReflector|\PHPUnit\Framework\MockObject\MockObject */ /** @var ControllerMethodReflector */
private $reflector; private $reflector;
/** @var Throttler|\PHPUnit\Framework\MockObject\MockObject */ /** @var Throttler|\PHPUnit\Framework\MockObject\MockObject */
private $throttler; private $throttler;
/** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */ /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */
private $request; private $request;
/** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
private $logger;
private BruteForceMiddleware $bruteForceMiddleware; private BruteForceMiddleware $bruteForceMiddleware;
protected function setUp(): void { protected function setUp(): void {
parent::setUp(); parent::setUp();
$this->reflector = $this->createMock(ControllerMethodReflector::class); $this->reflector = new ControllerMethodReflector();
$this->throttler = $this->createMock(Throttler::class); $this->throttler = $this->createMock(Throttler::class);
$this->request = $this->createMock(IRequest::class); $this->request = $this->createMock(IRequest::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->bruteForceMiddleware = new BruteForceMiddleware( $this->bruteForceMiddleware = new BruteForceMiddleware(
$this->reflector, $this->reflector,
$this->throttler, $this->throttler,
$this->request $this->request,
$this->logger,
); );
} }
public function testBeforeControllerWithAnnotation() { public function testBeforeControllerWithAnnotation(): void {
$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 $this->request
->expects($this->once()) ->expects($this->once())
->method('getRemoteAddress') ->method('getRemoteAddress')
@ -72,20 +89,45 @@ class BruteForceMiddlewareTest extends TestCase {
->method('sleepDelayOrThrowOnMax') ->method('sleepDelayOrThrowOnMax')
->with('127.0.0.1', 'login'); ->with('127.0.0.1', 'login');
/** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */ $controller = new TestController('test', $this->request);
$controller = $this->createMock(Controller::class); $this->reflector->reflect($controller, 'testMethodWithAnnotation');
$this->bruteForceMiddleware->beforeController($controller, 'testMethod'); $this->bruteForceMiddleware->beforeController($controller, 'testMethodWithAnnotation');
} }
public function testBeforeControllerWithoutAnnotation() { public function testBeforeControllerWithSingleAttribute(): void {
$this->reflector $this->request
->expects($this->once()) ->expects($this->once())
->method('hasAnnotation') ->method('getRemoteAddress')
->with('BruteForceProtection') ->willReturn('::1');
->willReturn(false); $this->throttler
$this->reflector ->expects($this->once())
->expects($this->never()) ->method('sleepDelayOrThrowOnMax')
->method('getAnnotationParameter'); ->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');
}
public function testBeforeControllerWithoutAnnotation(): void {
$this->request $this->request
->expects($this->never()) ->expects($this->never())
->method('getRemoteAddress'); ->method('getRemoteAddress');
@ -93,19 +135,14 @@ class BruteForceMiddlewareTest extends TestCase {
->expects($this->never()) ->expects($this->never())
->method('sleepDelayOrThrowOnMax'); ->method('sleepDelayOrThrowOnMax');
/** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */ $controller = new TestController('test', $this->request);
$controller = $this->createMock(Controller::class); $this->reflector->reflect($controller, 'testMethodWithoutAnnotation');
$this->bruteForceMiddleware->beforeController($controller, 'testMethod'); $this->bruteForceMiddleware->beforeController($controller, 'testMethodWithoutAnnotation');
} }
public function testAfterControllerWithAnnotationAndThrottledRequest() { public function testAfterControllerWithAnnotationAndThrottledRequest(): void {
/** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */ /** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
$response = $this->createMock(Response::class); $response = $this->createMock(Response::class);
$this->reflector
->expects($this->once())
->method('hasAnnotation')
->with('BruteForceProtection')
->willReturn(true);
$response $response
->expects($this->once()) ->expects($this->once())
->method('isThrottled') ->method('isThrottled')
@ -114,11 +151,6 @@ class BruteForceMiddlewareTest extends TestCase {
->expects($this->once()) ->expects($this->once())
->method('getThrottleMetadata') ->method('getThrottleMetadata')
->willReturn([]); ->willReturn([]);
$this->reflector
->expects($this->once())
->method('getAnnotationParameter')
->with('BruteForceProtection', 'action')
->willReturn('login');
$this->request $this->request
->expects($this->once()) ->expects($this->once())
->method('getRemoteAddress') ->method('getRemoteAddress')
@ -132,26 +164,18 @@ class BruteForceMiddlewareTest extends TestCase {
->method('registerAttempt') ->method('registerAttempt')
->with('login', '127.0.0.1'); ->with('login', '127.0.0.1');
/** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */ $controller = new TestController('test', $this->request);
$controller = $this->createMock(Controller::class); $this->reflector->reflect($controller, 'testMethodWithAnnotation');
$this->bruteForceMiddleware->afterController($controller, 'testMethod', $response); $this->bruteForceMiddleware->afterController($controller, 'testMethodWithAnnotation', $response);
} }
public function testAfterControllerWithAnnotationAndNotThrottledRequest() { public function testAfterControllerWithAnnotationAndNotThrottledRequest(): void {
/** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */ /** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
$response = $this->createMock(Response::class); $response = $this->createMock(Response::class);
$this->reflector
->expects($this->once())
->method('hasAnnotation')
->with('BruteForceProtection')
->willReturn(true);
$response $response
->expects($this->once()) ->expects($this->once())
->method('isThrottled') ->method('isThrottled')
->willReturn(false); ->willReturn(false);
$this->reflector
->expects($this->never())
->method('getAnnotationParameter');
$this->request $this->request
->expects($this->never()) ->expects($this->never())
->method('getRemoteAddress'); ->method('getRemoteAddress');
@ -162,20 +186,123 @@ class BruteForceMiddlewareTest extends TestCase {
->expects($this->never()) ->expects($this->never())
->method('registerAttempt'); ->method('registerAttempt');
/** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */ $controller = new TestController('test', $this->request);
$controller = $this->createMock(Controller::class); $this->reflector->reflect($controller, 'testMethodWithAnnotation');
$this->bruteForceMiddleware->afterController($controller, 'testMethod', $response); $this->bruteForceMiddleware->afterController($controller, 'testMethodWithAnnotation', $response);
} }
public function testAfterControllerWithoutAnnotation() { public function testAfterControllerWithSingleAttribute(): void {
$this->reflector /** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
$response = $this->createMock(Response::class);
$response
->expects($this->once()) ->expects($this->once())
->method('hasAnnotation') ->method('isThrottled')
->with('BruteForceProtection') ->willReturn(true);
->willReturn(false); $response
$this->reflector ->expects($this->once())
->method('getThrottleMetadata')
->willReturn([]);
$this->request
->expects($this->once())
->method('getRemoteAddress')
->willReturn('::1');
$this->throttler
->expects($this->once())
->method('sleepDelay')
->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))
->method('sleepDelay')
->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())
->method('sleepDelay')
->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);
}
public function testAfterControllerWithoutAnnotation(): void {
$this->request
->expects($this->never()) ->expects($this->never())
->method('getAnnotationParameter'); ->method('getRemoteAddress');
$this->throttler
->expects($this->never())
->method('sleepDelay');
$controller = new TestController('test', $this->request);
$this->reflector->reflect($controller, 'testMethodWithoutAnnotation');
/** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
$response = $this->createMock(Response::class);
$this->bruteForceMiddleware->afterController($controller, 'testMethodWithoutAnnotation', $response);
}
public function testAfterControllerWithThrottledResponseButUnhandled(): void {
$this->request $this->request
->expects($this->never()) ->expects($this->never())
->method('getRemoteAddress'); ->method('getRemoteAddress');
@ -183,10 +310,17 @@ class BruteForceMiddlewareTest extends TestCase {
->expects($this->never()) ->expects($this->never())
->method('sleepDelay'); ->method('sleepDelay');
/** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */ $controller = new TestController('test', $this->request);
$controller = $this->createMock(Controller::class); $this->reflector->reflect($controller, 'testMethodWithoutAnnotation');
/** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */ /** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
$response = $this->createMock(Response::class); $response = $this->createMock(Response::class);
$this->bruteForceMiddleware->afterController($controller, 'testMethod', $response); $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);
} }
} }