aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/AppFramework/Middleware/Security/BruteForceMiddlewareTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/AppFramework/Middleware/Security/BruteForceMiddlewareTest.php')
-rw-r--r--tests/lib/AppFramework/Middleware/Security/BruteForceMiddlewareTest.php310
1 files changed, 223 insertions, 87 deletions
diff --git a/tests/lib/AppFramework/Middleware/Security/BruteForceMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/BruteForceMiddlewareTest.php
index 7dfcfe22261..3fd2cb38a33 100644
--- a/tests/lib/AppFramework/Middleware/Security/BruteForceMiddlewareTest.php
+++ b/tests/lib/AppFramework/Middleware/Security/BruteForceMiddlewareTest.php
@@ -1,68 +1,70 @@
<?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/>.
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
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\Attribute\BruteForceProtection;
use OCP\AppFramework\Http\Response;
use OCP\IRequest;
+use OCP\Security\Bruteforce\IThrottler;
+use Psr\Log\LoggerInterface;
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 {
- /** @var ControllerMethodReflector|\PHPUnit\Framework\MockObject\MockObject */
+ /** @var ControllerMethodReflector */
private $reflector;
- /** @var Throttler|\PHPUnit\Framework\MockObject\MockObject */
+ /** @var IThrottler|\PHPUnit\Framework\MockObject\MockObject */
private $throttler;
/** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */
private $request;
+ /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
+ private $logger;
private BruteForceMiddleware $bruteForceMiddleware;
protected function setUp(): void {
parent::setUp();
- $this->reflector = $this->createMock(ControllerMethodReflector::class);
- $this->throttler = $this->createMock(Throttler::class);
+ $this->reflector = new ControllerMethodReflector();
+ $this->throttler = $this->createMock(IThrottler::class);
$this->request = $this->createMock(IRequest::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
$this->bruteForceMiddleware = new BruteForceMiddleware(
$this->reflector,
$this->throttler,
- $this->request
+ $this->request,
+ $this->logger,
);
}
- 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');
+ public function testBeforeControllerWithAnnotation(): void {
$this->request
->expects($this->once())
->method('getRemoteAddress')
@@ -72,20 +74,51 @@ class BruteForceMiddlewareTest extends TestCase {
->method('sleepDelayOrThrowOnMax')
->with('127.0.0.1', 'login');
- /** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */
- $controller = $this->createMock(Controller::class);
- $this->bruteForceMiddleware->beforeController($controller, 'testMethod');
+ $controller = new TestController('test', $this->request);
+ $this->reflector->reflect($controller, 'testMethodWithAnnotation');
+ $this->bruteForceMiddleware->beforeController($controller, 'testMethodWithAnnotation');
}
- public function testBeforeControllerWithoutAnnotation() {
- $this->reflector
+ public function testBeforeControllerWithSingleAttribute(): void {
+ $this->request
->expects($this->once())
- ->method('hasAnnotation')
- ->with('BruteForceProtection')
- ->willReturn(false);
- $this->reflector
- ->expects($this->never())
- ->method('getAnnotationParameter');
+ ->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');
+
+ $calls = [
+ ['::1', 'first'],
+ ['::1', 'second'],
+ ];
+ $this->throttler
+ ->expects($this->exactly(2))
+ ->method('sleepDelayOrThrowOnMax')
+ ->willReturnCallback(function () use (&$calls) {
+ $expected = array_shift($calls);
+ $this->assertEquals($expected, func_get_args());
+ return 0;
+ });
+
+ $controller = new TestController('test', $this->request);
+ $this->reflector->reflect($controller, 'multipleAttributes');
+ $this->bruteForceMiddleware->beforeController($controller, 'multipleAttributes');
+ }
+
+ public function testBeforeControllerWithoutAnnotation(): void {
$this->request
->expects($this->never())
->method('getRemoteAddress');
@@ -93,19 +126,14 @@ class BruteForceMiddlewareTest extends TestCase {
->expects($this->never())
->method('sleepDelayOrThrowOnMax');
- /** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */
- $controller = $this->createMock(Controller::class);
- $this->bruteForceMiddleware->beforeController($controller, 'testMethod');
+ $controller = new TestController('test', $this->request);
+ $this->reflector->reflect($controller, 'testMethodWithoutAnnotation');
+ $this->bruteForceMiddleware->beforeController($controller, 'testMethodWithoutAnnotation');
}
- public function testAfterControllerWithAnnotationAndThrottledRequest() {
+ public function testAfterControllerWithAnnotationAndThrottledRequest(): void {
/** @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')
@@ -114,79 +142,187 @@ class BruteForceMiddlewareTest extends TestCase {
->expects($this->once())
->method('getThrottleMetadata')
->willReturn([]);
- $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')
+ ->method('sleepDelayOrThrowOnMax')
->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);
+ $controller = new TestController('test', $this->request);
+ $this->reflector->reflect($controller, 'testMethodWithAnnotation');
+ $this->bruteForceMiddleware->afterController($controller, 'testMethodWithAnnotation', $response);
}
- public function testAfterControllerWithAnnotationAndNotThrottledRequest() {
+ public function testAfterControllerWithAnnotationAndNotThrottledRequest(): void {
/** @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');
+ ->method('sleepDelayOrThrowOnMax');
$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);
+ $controller = new TestController('test', $this->request);
+ $this->reflector->reflect($controller, 'testMethodWithAnnotation');
+ $this->bruteForceMiddleware->afterController($controller, 'testMethodWithAnnotation', $response);
}
- public function testAfterControllerWithoutAnnotation() {
- $this->reflector
+ public function testAfterControllerWithSingleAttribute(): void {
+ /** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
+ $response = $this->createMock(Response::class);
+ $response
->expects($this->once())
- ->method('hasAnnotation')
- ->with('BruteForceProtection')
- ->willReturn(false);
- $this->reflector
+ ->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())
+ ->method('sleepDelayOrThrowOnMax')
+ ->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');
+
+ $sleepCalls = [
+ ['::1', 'first'],
+ ['::1', 'second'],
+ ];
+ $this->throttler
+ ->expects($this->exactly(2))
+ ->method('sleepDelayOrThrowOnMax')
+ ->willReturnCallback(function () use (&$sleepCalls) {
+ $expected = array_shift($sleepCalls);
+ $this->assertEquals($expected, func_get_args());
+ return 0;
+ });
+
+ $attemptCalls = [
+ ['first', '::1', []],
+ ['second', '::1', []],
+ ];
+ $this->throttler
+ ->expects($this->exactly(2))
+ ->method('registerAttempt')
+ ->willReturnCallback(function () use (&$attemptCalls): void {
+ $expected = array_shift($attemptCalls);
+ $this->assertEquals($expected, func_get_args());
+ });
+
+ $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('sleepDelayOrThrowOnMax')
+ ->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())
+ ->method('getRemoteAddress');
+ $this->throttler
->expects($this->never())
- ->method('getAnnotationParameter');
+ ->method('sleepDelayOrThrowOnMax');
+
+ $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
->expects($this->never())
->method('getRemoteAddress');
$this->throttler
->expects($this->never())
- ->method('sleepDelay');
+ ->method('sleepDelayOrThrowOnMax');
- /** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */
- $controller = $this->createMock(Controller::class);
+ $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, '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);
}
}