BruteForceMiddlewareTest.php 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. namespace Test\AppFramework\Middleware\Security;
  7. use OC\AppFramework\Middleware\Security\BruteForceMiddleware;
  8. use OC\AppFramework\Utility\ControllerMethodReflector;
  9. use OCP\AppFramework\Controller;
  10. use OCP\AppFramework\Http\Attribute\BruteForceProtection;
  11. use OCP\AppFramework\Http\Response;
  12. use OCP\IRequest;
  13. use OCP\Security\Bruteforce\IThrottler;
  14. use Psr\Log\LoggerInterface;
  15. use Test\TestCase;
  16. class TestController extends Controller {
  17. /**
  18. * @BruteForceProtection(action=login)
  19. */
  20. public function testMethodWithAnnotation() {
  21. }
  22. public function testMethodWithoutAnnotation() {
  23. }
  24. #[BruteForceProtection(action: 'single')]
  25. public function singleAttribute(): void {
  26. }
  27. #[BruteForceProtection(action: 'first')]
  28. #[BruteForceProtection(action: 'second')]
  29. public function multipleAttributes(): void {
  30. }
  31. }
  32. class BruteForceMiddlewareTest extends TestCase {
  33. /** @var ControllerMethodReflector */
  34. private $reflector;
  35. /** @var IThrottler|\PHPUnit\Framework\MockObject\MockObject */
  36. private $throttler;
  37. /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */
  38. private $request;
  39. /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
  40. private $logger;
  41. private BruteForceMiddleware $bruteForceMiddleware;
  42. protected function setUp(): void {
  43. parent::setUp();
  44. $this->reflector = new ControllerMethodReflector();
  45. $this->throttler = $this->createMock(IThrottler::class);
  46. $this->request = $this->createMock(IRequest::class);
  47. $this->logger = $this->createMock(LoggerInterface::class);
  48. $this->bruteForceMiddleware = new BruteForceMiddleware(
  49. $this->reflector,
  50. $this->throttler,
  51. $this->request,
  52. $this->logger,
  53. );
  54. }
  55. public function testBeforeControllerWithAnnotation(): void {
  56. $this->request
  57. ->expects($this->once())
  58. ->method('getRemoteAddress')
  59. ->willReturn('127.0.0.1');
  60. $this->throttler
  61. ->expects($this->once())
  62. ->method('sleepDelayOrThrowOnMax')
  63. ->with('127.0.0.1', 'login');
  64. $controller = new TestController('test', $this->request);
  65. $this->reflector->reflect($controller, 'testMethodWithAnnotation');
  66. $this->bruteForceMiddleware->beforeController($controller, 'testMethodWithAnnotation');
  67. }
  68. public function testBeforeControllerWithSingleAttribute(): void {
  69. $this->request
  70. ->expects($this->once())
  71. ->method('getRemoteAddress')
  72. ->willReturn('::1');
  73. $this->throttler
  74. ->expects($this->once())
  75. ->method('sleepDelayOrThrowOnMax')
  76. ->with('::1', 'single');
  77. $controller = new TestController('test', $this->request);
  78. $this->reflector->reflect($controller, 'singleAttribute');
  79. $this->bruteForceMiddleware->beforeController($controller, 'singleAttribute');
  80. }
  81. public function testBeforeControllerWithMultipleAttributes(): void {
  82. $this->request
  83. ->expects($this->once())
  84. ->method('getRemoteAddress')
  85. ->willReturn('::1');
  86. $this->throttler
  87. ->expects($this->exactly(2))
  88. ->method('sleepDelayOrThrowOnMax')
  89. ->withConsecutive(
  90. ['::1', 'first'],
  91. ['::1', 'second'],
  92. );
  93. $controller = new TestController('test', $this->request);
  94. $this->reflector->reflect($controller, 'multipleAttributes');
  95. $this->bruteForceMiddleware->beforeController($controller, 'multipleAttributes');
  96. }
  97. public function testBeforeControllerWithoutAnnotation(): void {
  98. $this->request
  99. ->expects($this->never())
  100. ->method('getRemoteAddress');
  101. $this->throttler
  102. ->expects($this->never())
  103. ->method('sleepDelayOrThrowOnMax');
  104. $controller = new TestController('test', $this->request);
  105. $this->reflector->reflect($controller, 'testMethodWithoutAnnotation');
  106. $this->bruteForceMiddleware->beforeController($controller, 'testMethodWithoutAnnotation');
  107. }
  108. public function testAfterControllerWithAnnotationAndThrottledRequest(): void {
  109. /** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
  110. $response = $this->createMock(Response::class);
  111. $response
  112. ->expects($this->once())
  113. ->method('isThrottled')
  114. ->willReturn(true);
  115. $response
  116. ->expects($this->once())
  117. ->method('getThrottleMetadata')
  118. ->willReturn([]);
  119. $this->request
  120. ->expects($this->once())
  121. ->method('getRemoteAddress')
  122. ->willReturn('127.0.0.1');
  123. $this->throttler
  124. ->expects($this->once())
  125. ->method('sleepDelayOrThrowOnMax')
  126. ->with('127.0.0.1', 'login');
  127. $this->throttler
  128. ->expects($this->once())
  129. ->method('registerAttempt')
  130. ->with('login', '127.0.0.1');
  131. $controller = new TestController('test', $this->request);
  132. $this->reflector->reflect($controller, 'testMethodWithAnnotation');
  133. $this->bruteForceMiddleware->afterController($controller, 'testMethodWithAnnotation', $response);
  134. }
  135. public function testAfterControllerWithAnnotationAndNotThrottledRequest(): void {
  136. /** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
  137. $response = $this->createMock(Response::class);
  138. $response
  139. ->expects($this->once())
  140. ->method('isThrottled')
  141. ->willReturn(false);
  142. $this->request
  143. ->expects($this->never())
  144. ->method('getRemoteAddress');
  145. $this->throttler
  146. ->expects($this->never())
  147. ->method('sleepDelayOrThrowOnMax');
  148. $this->throttler
  149. ->expects($this->never())
  150. ->method('registerAttempt');
  151. $controller = new TestController('test', $this->request);
  152. $this->reflector->reflect($controller, 'testMethodWithAnnotation');
  153. $this->bruteForceMiddleware->afterController($controller, 'testMethodWithAnnotation', $response);
  154. }
  155. public function testAfterControllerWithSingleAttribute(): void {
  156. /** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
  157. $response = $this->createMock(Response::class);
  158. $response
  159. ->expects($this->once())
  160. ->method('isThrottled')
  161. ->willReturn(true);
  162. $response
  163. ->expects($this->once())
  164. ->method('getThrottleMetadata')
  165. ->willReturn([]);
  166. $this->request
  167. ->expects($this->once())
  168. ->method('getRemoteAddress')
  169. ->willReturn('::1');
  170. $this->throttler
  171. ->expects($this->once())
  172. ->method('sleepDelayOrThrowOnMax')
  173. ->with('::1', 'single');
  174. $this->throttler
  175. ->expects($this->once())
  176. ->method('registerAttempt')
  177. ->with('single', '::1');
  178. $controller = new TestController('test', $this->request);
  179. $this->reflector->reflect($controller, 'singleAttribute');
  180. $this->bruteForceMiddleware->afterController($controller, 'singleAttribute', $response);
  181. }
  182. public function testAfterControllerWithMultipleAttributesGeneralMatch(): void {
  183. /** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
  184. $response = $this->createMock(Response::class);
  185. $response
  186. ->expects($this->once())
  187. ->method('isThrottled')
  188. ->willReturn(true);
  189. $response
  190. ->expects($this->once())
  191. ->method('getThrottleMetadata')
  192. ->willReturn([]);
  193. $this->request
  194. ->expects($this->once())
  195. ->method('getRemoteAddress')
  196. ->willReturn('::1');
  197. $this->throttler
  198. ->expects($this->exactly(2))
  199. ->method('sleepDelayOrThrowOnMax')
  200. ->withConsecutive(
  201. ['::1', 'first'],
  202. ['::1', 'second'],
  203. );
  204. $this->throttler
  205. ->expects($this->exactly(2))
  206. ->method('registerAttempt')
  207. ->withConsecutive(
  208. ['first', '::1'],
  209. ['second', '::1'],
  210. );
  211. $controller = new TestController('test', $this->request);
  212. $this->reflector->reflect($controller, 'multipleAttributes');
  213. $this->bruteForceMiddleware->afterController($controller, 'multipleAttributes', $response);
  214. }
  215. public function testAfterControllerWithMultipleAttributesSpecificMatch(): void {
  216. /** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
  217. $response = $this->createMock(Response::class);
  218. $response
  219. ->expects($this->once())
  220. ->method('isThrottled')
  221. ->willReturn(true);
  222. $response
  223. ->expects($this->once())
  224. ->method('getThrottleMetadata')
  225. ->willReturn(['action' => 'second']);
  226. $this->request
  227. ->expects($this->once())
  228. ->method('getRemoteAddress')
  229. ->willReturn('::1');
  230. $this->throttler
  231. ->expects($this->once())
  232. ->method('sleepDelayOrThrowOnMax')
  233. ->with('::1', 'second');
  234. $this->throttler
  235. ->expects($this->once())
  236. ->method('registerAttempt')
  237. ->with('second', '::1');
  238. $controller = new TestController('test', $this->request);
  239. $this->reflector->reflect($controller, 'multipleAttributes');
  240. $this->bruteForceMiddleware->afterController($controller, 'multipleAttributes', $response);
  241. }
  242. public function testAfterControllerWithoutAnnotation(): void {
  243. $this->request
  244. ->expects($this->never())
  245. ->method('getRemoteAddress');
  246. $this->throttler
  247. ->expects($this->never())
  248. ->method('sleepDelayOrThrowOnMax');
  249. $controller = new TestController('test', $this->request);
  250. $this->reflector->reflect($controller, 'testMethodWithoutAnnotation');
  251. /** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
  252. $response = $this->createMock(Response::class);
  253. $this->bruteForceMiddleware->afterController($controller, 'testMethodWithoutAnnotation', $response);
  254. }
  255. public function testAfterControllerWithThrottledResponseButUnhandled(): void {
  256. $this->request
  257. ->expects($this->never())
  258. ->method('getRemoteAddress');
  259. $this->throttler
  260. ->expects($this->never())
  261. ->method('sleepDelayOrThrowOnMax');
  262. $controller = new TestController('test', $this->request);
  263. $this->reflector->reflect($controller, 'testMethodWithoutAnnotation');
  264. /** @var Response|\PHPUnit\Framework\MockObject\MockObject $response */
  265. $response = $this->createMock(Response::class);
  266. $response->method('isThrottled')
  267. ->willReturn(true);
  268. $this->logger->expects($this->once())
  269. ->method('debug')
  270. ->with('Response for Test\AppFramework\Middleware\Security\TestController::testMethodWithoutAnnotation got bruteforce throttled but has no annotation nor attribute defined.');
  271. $this->bruteForceMiddleware->afterController($controller, 'testMethodWithoutAnnotation', $response);
  272. }
  273. }