RateLimitingMiddlewareTest.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
  4. *
  5. * @license GNU AGPL version 3 or any later version
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Affero General Public License as
  9. * published by the Free Software Foundation, either version 3 of the
  10. * License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Affero General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. */
  21. namespace Test\AppFramework\Middleware\Security;
  22. use OC\AppFramework\Middleware\Security\RateLimitingMiddleware;
  23. use OC\AppFramework\Utility\ControllerMethodReflector;
  24. use OC\Security\RateLimiting\Exception\RateLimitExceededException;
  25. use OC\Security\RateLimiting\Limiter;
  26. use OCP\AppFramework\Controller;
  27. use OCP\AppFramework\Http\DataResponse;
  28. use OCP\AppFramework\Http\TemplateResponse;
  29. use OCP\IRequest;
  30. use OCP\IUser;
  31. use OCP\IUserSession;
  32. use Test\TestCase;
  33. /**
  34. * @group DB
  35. */
  36. class RateLimitingMiddlewareTest extends TestCase {
  37. /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */
  38. private $request;
  39. /** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */
  40. private $userSession;
  41. /** @var ControllerMethodReflector|\PHPUnit\Framework\MockObject\MockObject */
  42. private $reflector;
  43. /** @var Limiter|\PHPUnit\Framework\MockObject\MockObject */
  44. private $limiter;
  45. /** @var RateLimitingMiddleware */
  46. private $rateLimitingMiddleware;
  47. protected function setUp(): void {
  48. parent::setUp();
  49. $this->request = $this->createMock(IRequest::class);
  50. $this->userSession = $this->createMock(IUserSession::class);
  51. $this->reflector = $this->createMock(ControllerMethodReflector::class);
  52. $this->limiter = $this->createMock(Limiter::class);
  53. $this->rateLimitingMiddleware = new RateLimitingMiddleware(
  54. $this->request,
  55. $this->userSession,
  56. $this->reflector,
  57. $this->limiter
  58. );
  59. }
  60. public function testBeforeControllerWithoutAnnotation() {
  61. $this->reflector
  62. ->expects($this->exactly(4))
  63. ->method('getAnnotationParameter')
  64. ->withConsecutive(
  65. ['AnonRateThrottle', 'limit'],
  66. ['AnonRateThrottle', 'period'],
  67. ['UserRateThrottle', 'limit'],
  68. ['UserRateThrottle', 'period']
  69. )
  70. ->willReturnMap([
  71. ['AnonRateThrottle', 'limit', ''],
  72. ['AnonRateThrottle', 'period', ''],
  73. ['UserRateThrottle', 'limit', ''],
  74. ['UserRateThrottle', 'period', ''],
  75. ]);
  76. $this->limiter
  77. ->expects($this->never())
  78. ->method('registerUserRequest');
  79. $this->limiter
  80. ->expects($this->never())
  81. ->method('registerAnonRequest');
  82. /** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */
  83. $controller = $this->createMock(Controller::class);
  84. $this->rateLimitingMiddleware->beforeController($controller, 'testMethod');
  85. }
  86. public function testBeforeControllerForAnon() {
  87. /** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */
  88. $controller = $this->createMock(Controller::class);
  89. $this->request
  90. ->expects($this->once())
  91. ->method('getRemoteAddress')
  92. ->willReturn('127.0.0.1');
  93. $this->reflector
  94. ->expects($this->exactly(4))
  95. ->method('getAnnotationParameter')
  96. ->withConsecutive(
  97. ['AnonRateThrottle', 'limit'],
  98. ['AnonRateThrottle', 'period'],
  99. ['UserRateThrottle', 'limit'],
  100. ['UserRateThrottle', 'period']
  101. )
  102. ->willReturnMap([
  103. ['AnonRateThrottle', 'limit', '100'],
  104. ['AnonRateThrottle', 'period', '10'],
  105. ['UserRateThrottle', 'limit', ''],
  106. ['UserRateThrottle', 'period', ''],
  107. ]);
  108. $this->limiter
  109. ->expects($this->never())
  110. ->method('registerUserRequest');
  111. $this->limiter
  112. ->expects($this->once())
  113. ->method('registerAnonRequest')
  114. ->with(get_class($controller) . '::testMethod', '100', '10', '127.0.0.1');
  115. $this->rateLimitingMiddleware->beforeController($controller, 'testMethod');
  116. }
  117. public function testBeforeControllerForLoggedIn() {
  118. /** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */
  119. $controller = $this->createMock(Controller::class);
  120. /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */
  121. $user = $this->createMock(IUser::class);
  122. $this->userSession
  123. ->expects($this->once())
  124. ->method('isLoggedIn')
  125. ->willReturn(true);
  126. $this->userSession
  127. ->expects($this->once())
  128. ->method('getUser')
  129. ->willReturn($user);
  130. $this->reflector
  131. ->expects($this->exactly(4))
  132. ->method('getAnnotationParameter')
  133. ->withConsecutive(
  134. ['AnonRateThrottle', 'limit'],
  135. ['AnonRateThrottle', 'period'],
  136. ['UserRateThrottle', 'limit'],
  137. ['UserRateThrottle', 'period']
  138. )
  139. ->willReturnMap([
  140. ['AnonRateThrottle', 'limit', ''],
  141. ['AnonRateThrottle', 'period', ''],
  142. ['UserRateThrottle', 'limit', '100'],
  143. ['UserRateThrottle', 'period', '10'],
  144. ]);
  145. $this->limiter
  146. ->expects($this->never())
  147. ->method('registerAnonRequest');
  148. $this->limiter
  149. ->expects($this->once())
  150. ->method('registerUserRequest')
  151. ->with(get_class($controller) . '::testMethod', '100', '10', $user);
  152. $this->rateLimitingMiddleware->beforeController($controller, 'testMethod');
  153. }
  154. public function testBeforeControllerAnonWithFallback() {
  155. /** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */
  156. $controller = $this->createMock(Controller::class);
  157. $this->request
  158. ->expects($this->once())
  159. ->method('getRemoteAddress')
  160. ->willReturn('127.0.0.1');
  161. $this->userSession
  162. ->expects($this->once())
  163. ->method('isLoggedIn')
  164. ->willReturn(false);
  165. $this->reflector
  166. ->expects($this->exactly(4))
  167. ->method('getAnnotationParameter')
  168. ->withConsecutive(
  169. ['AnonRateThrottle', 'limit'],
  170. ['AnonRateThrottle', 'period'],
  171. ['UserRateThrottle', 'limit'],
  172. ['UserRateThrottle', 'period']
  173. )
  174. ->willReturnMap([
  175. ['AnonRateThrottle', 'limit', '200'],
  176. ['AnonRateThrottle', 'period', '20'],
  177. ['UserRateThrottle', 'limit', '100'],
  178. ['UserRateThrottle', 'period', '10'],
  179. ]);
  180. $this->limiter
  181. ->expects($this->never())
  182. ->method('registerUserRequest');
  183. $this->limiter
  184. ->expects($this->once())
  185. ->method('registerAnonRequest')
  186. ->with(get_class($controller) . '::testMethod', '200', '20', '127.0.0.1');
  187. $this->rateLimitingMiddleware->beforeController($controller, 'testMethod');
  188. }
  189. public function testAfterExceptionWithOtherException() {
  190. $this->expectException(\Exception::class);
  191. $this->expectExceptionMessage('My test exception');
  192. /** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */
  193. $controller = $this->createMock(Controller::class);
  194. $this->rateLimitingMiddleware->afterException($controller, 'testMethod', new \Exception('My test exception'));
  195. }
  196. public function testAfterExceptionWithJsonBody() {
  197. /** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */
  198. $controller = $this->createMock(Controller::class);
  199. $this->request
  200. ->expects($this->once())
  201. ->method('getHeader')
  202. ->with('Accept')
  203. ->willReturn('JSON');
  204. $result = $this->rateLimitingMiddleware->afterException($controller, 'testMethod', new RateLimitExceededException());
  205. $expected = new DataResponse([], 429
  206. );
  207. $this->assertEquals($expected, $result);
  208. }
  209. public function testAfterExceptionWithHtmlBody() {
  210. /** @var Controller|\PHPUnit\Framework\MockObject\MockObject $controller */
  211. $controller = $this->createMock(Controller::class);
  212. $this->request
  213. ->expects($this->once())
  214. ->method('getHeader')
  215. ->with('Accept')
  216. ->willReturn('html');
  217. $result = $this->rateLimitingMiddleware->afterException($controller, 'testMethod', new RateLimitExceededException());
  218. $expected = new TemplateResponse(
  219. 'core',
  220. '429',
  221. [],
  222. TemplateResponse::RENDER_AS_GUEST
  223. );
  224. $expected->setStatus(429);
  225. $this->assertEquals($expected, $result);
  226. $this->assertIsString($result->render());
  227. }
  228. }