CORSMiddlewareTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. <?php
  2. /**
  3. * ownCloud - App Framework
  4. *
  5. * This file is licensed under the Affero General Public License version 3 or
  6. * later. See the COPYING file.
  7. *
  8. * @author Bernhard Posselt <dev@bernhard-posselt.com>
  9. * @copyright Bernhard Posselt 2014
  10. */
  11. namespace Test\AppFramework\Middleware\Security;
  12. use OC\AppFramework\Http\Request;
  13. use OC\AppFramework\Middleware\Security\CORSMiddleware;
  14. use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
  15. use OC\AppFramework\Utility\ControllerMethodReflector;
  16. use OC\User\Session;
  17. use OCP\AppFramework\Http\JSONResponse;
  18. use OCP\AppFramework\Http\Response;
  19. use OCP\IConfig;
  20. use OCP\IRequest;
  21. use OCP\IRequestId;
  22. use OCP\Security\Bruteforce\IThrottler;
  23. use PHPUnit\Framework\MockObject\MockObject;
  24. use Test\AppFramework\Middleware\Security\Mock\CORSMiddlewareController;
  25. class CORSMiddlewareTest extends \Test\TestCase {
  26. /** @var ControllerMethodReflector */
  27. private $reflector;
  28. /** @var Session|MockObject */
  29. private $session;
  30. /** @var IThrottler|MockObject */
  31. private $throttler;
  32. /** @var CORSMiddlewareController */
  33. private $controller;
  34. protected function setUp(): void {
  35. parent::setUp();
  36. $this->reflector = new ControllerMethodReflector();
  37. $this->session = $this->createMock(Session::class);
  38. $this->throttler = $this->createMock(IThrottler::class);
  39. $this->controller = new CORSMiddlewareController(
  40. 'test',
  41. $this->createMock(IRequest::class)
  42. );
  43. }
  44. public function dataSetCORSAPIHeader(): array {
  45. return [
  46. ['testSetCORSAPIHeader'],
  47. ['testSetCORSAPIHeaderAttribute'],
  48. ];
  49. }
  50. /**
  51. * @dataProvider dataSetCORSAPIHeader
  52. */
  53. public function testSetCORSAPIHeader(string $method): void {
  54. $request = new Request(
  55. [
  56. 'server' => [
  57. 'HTTP_ORIGIN' => 'test'
  58. ]
  59. ],
  60. $this->createMock(IRequestId::class),
  61. $this->createMock(IConfig::class)
  62. );
  63. $this->reflector->reflect($this->controller, $method);
  64. $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
  65. $response = $middleware->afterController($this->controller, $method, new Response());
  66. $headers = $response->getHeaders();
  67. $this->assertEquals('test', $headers['Access-Control-Allow-Origin']);
  68. }
  69. public function testNoAnnotationNoCORSHEADER(): void {
  70. $request = new Request(
  71. [
  72. 'server' => [
  73. 'HTTP_ORIGIN' => 'test'
  74. ]
  75. ],
  76. $this->createMock(IRequestId::class),
  77. $this->createMock(IConfig::class)
  78. );
  79. $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
  80. $response = $middleware->afterController($this->controller, __FUNCTION__, new Response());
  81. $headers = $response->getHeaders();
  82. $this->assertFalse(array_key_exists('Access-Control-Allow-Origin', $headers));
  83. }
  84. public function dataNoOriginHeaderNoCORSHEADER(): array {
  85. return [
  86. ['testNoOriginHeaderNoCORSHEADER'],
  87. ['testNoOriginHeaderNoCORSHEADERAttribute'],
  88. ];
  89. }
  90. /**
  91. * @dataProvider dataNoOriginHeaderNoCORSHEADER
  92. */
  93. public function testNoOriginHeaderNoCORSHEADER(string $method): void {
  94. $request = new Request(
  95. [],
  96. $this->createMock(IRequestId::class),
  97. $this->createMock(IConfig::class)
  98. );
  99. $this->reflector->reflect($this->controller, $method);
  100. $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
  101. $response = $middleware->afterController($this->controller, $method, new Response());
  102. $headers = $response->getHeaders();
  103. $this->assertFalse(array_key_exists('Access-Control-Allow-Origin', $headers));
  104. }
  105. public function dataCorsIgnoredIfWithCredentialsHeaderPresent(): array {
  106. return [
  107. ['testCorsIgnoredIfWithCredentialsHeaderPresent'],
  108. ['testCorsAttributeIgnoredIfWithCredentialsHeaderPresent'],
  109. ];
  110. }
  111. /**
  112. * @dataProvider dataCorsIgnoredIfWithCredentialsHeaderPresent
  113. */
  114. public function testCorsIgnoredIfWithCredentialsHeaderPresent(string $method): void {
  115. $this->expectException(\OC\AppFramework\Middleware\Security\Exceptions\SecurityException::class);
  116. $request = new Request(
  117. [
  118. 'server' => [
  119. 'HTTP_ORIGIN' => 'test'
  120. ]
  121. ],
  122. $this->createMock(IRequestId::class),
  123. $this->createMock(IConfig::class)
  124. );
  125. $this->reflector->reflect($this->controller, $method);
  126. $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
  127. $response = new Response();
  128. $response->addHeader('AcCess-control-Allow-Credentials ', 'TRUE');
  129. $middleware->afterController($this->controller, $method, $response);
  130. }
  131. public function dataNoCORSOnAnonymousPublicPage(): array {
  132. return [
  133. ['testNoCORSOnAnonymousPublicPage'],
  134. ['testNoCORSOnAnonymousPublicPageAttribute'],
  135. ['testNoCORSAttributeOnAnonymousPublicPage'],
  136. ['testNoCORSAttributeOnAnonymousPublicPageAttribute'],
  137. ];
  138. }
  139. /**
  140. * @dataProvider dataNoCORSOnAnonymousPublicPage
  141. */
  142. public function testNoCORSOnAnonymousPublicPage(string $method): void {
  143. $request = new Request(
  144. [],
  145. $this->createMock(IRequestId::class),
  146. $this->createMock(IConfig::class)
  147. );
  148. $this->reflector->reflect($this->controller, $method);
  149. $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
  150. $this->session->expects($this->once())
  151. ->method('isLoggedIn')
  152. ->willReturn(false);
  153. $this->session->expects($this->never())
  154. ->method('logout');
  155. $this->session->expects($this->never())
  156. ->method('logClientIn')
  157. ->with($this->equalTo('user'), $this->equalTo('pass'))
  158. ->willReturn(true);
  159. $this->reflector->reflect($this->controller, $method);
  160. $middleware->beforeController($this->controller, $method);
  161. }
  162. public function dataCORSShouldNeverAllowCookieAuth(): array {
  163. return [
  164. ['testCORSShouldNeverAllowCookieAuth'],
  165. ['testCORSShouldNeverAllowCookieAuthAttribute'],
  166. ['testCORSAttributeShouldNeverAllowCookieAuth'],
  167. ['testCORSAttributeShouldNeverAllowCookieAuthAttribute'],
  168. ];
  169. }
  170. /**
  171. * @dataProvider dataCORSShouldNeverAllowCookieAuth
  172. */
  173. public function testCORSShouldNeverAllowCookieAuth(string $method): void {
  174. $request = new Request(
  175. [],
  176. $this->createMock(IRequestId::class),
  177. $this->createMock(IConfig::class)
  178. );
  179. $this->reflector->reflect($this->controller, $method);
  180. $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
  181. $this->session->expects($this->once())
  182. ->method('isLoggedIn')
  183. ->willReturn(true);
  184. $this->session->expects($this->once())
  185. ->method('logout');
  186. $this->session->expects($this->never())
  187. ->method('logClientIn')
  188. ->with($this->equalTo('user'), $this->equalTo('pass'))
  189. ->willReturn(true);
  190. $this->expectException(SecurityException::class);
  191. $middleware->beforeController($this->controller, $method);
  192. }
  193. public function dataCORSShouldRelogin(): array {
  194. return [
  195. ['testCORSShouldRelogin'],
  196. ['testCORSAttributeShouldRelogin'],
  197. ];
  198. }
  199. /**
  200. * @dataProvider dataCORSShouldRelogin
  201. */
  202. public function testCORSShouldRelogin(string $method): void {
  203. $request = new Request(
  204. ['server' => [
  205. 'PHP_AUTH_USER' => 'user',
  206. 'PHP_AUTH_PW' => 'pass'
  207. ]],
  208. $this->createMock(IRequestId::class),
  209. $this->createMock(IConfig::class)
  210. );
  211. $this->session->expects($this->once())
  212. ->method('logout');
  213. $this->session->expects($this->once())
  214. ->method('logClientIn')
  215. ->with($this->equalTo('user'), $this->equalTo('pass'))
  216. ->willReturn(true);
  217. $this->reflector->reflect($this->controller, $method);
  218. $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
  219. $middleware->beforeController($this->controller, $method);
  220. }
  221. public function dataCORSShouldFailIfPasswordLoginIsForbidden(): array {
  222. return [
  223. ['testCORSShouldFailIfPasswordLoginIsForbidden'],
  224. ['testCORSAttributeShouldFailIfPasswordLoginIsForbidden'],
  225. ];
  226. }
  227. /**
  228. * @dataProvider dataCORSShouldFailIfPasswordLoginIsForbidden
  229. */
  230. public function testCORSShouldFailIfPasswordLoginIsForbidden(string $method): void {
  231. $this->expectException(\OC\AppFramework\Middleware\Security\Exceptions\SecurityException::class);
  232. $request = new Request(
  233. ['server' => [
  234. 'PHP_AUTH_USER' => 'user',
  235. 'PHP_AUTH_PW' => 'pass'
  236. ]],
  237. $this->createMock(IRequestId::class),
  238. $this->createMock(IConfig::class)
  239. );
  240. $this->session->expects($this->once())
  241. ->method('logout');
  242. $this->session->expects($this->once())
  243. ->method('logClientIn')
  244. ->with($this->equalTo('user'), $this->equalTo('pass'))
  245. ->will($this->throwException(new \OC\Authentication\Exceptions\PasswordLoginForbiddenException));
  246. $this->reflector->reflect($this->controller, $method);
  247. $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
  248. $middleware->beforeController($this->controller, $method);
  249. }
  250. public function dataCORSShouldNotAllowCookieAuth(): array {
  251. return [
  252. ['testCORSShouldNotAllowCookieAuth'],
  253. ['testCORSAttributeShouldNotAllowCookieAuth'],
  254. ];
  255. }
  256. /**
  257. * @dataProvider dataCORSShouldNotAllowCookieAuth
  258. */
  259. public function testCORSShouldNotAllowCookieAuth(string $method): void {
  260. $this->expectException(\OC\AppFramework\Middleware\Security\Exceptions\SecurityException::class);
  261. $request = new Request(
  262. ['server' => [
  263. 'PHP_AUTH_USER' => 'user',
  264. 'PHP_AUTH_PW' => 'pass'
  265. ]],
  266. $this->createMock(IRequestId::class),
  267. $this->createMock(IConfig::class)
  268. );
  269. $this->session->expects($this->once())
  270. ->method('logout');
  271. $this->session->expects($this->once())
  272. ->method('logClientIn')
  273. ->with($this->equalTo('user'), $this->equalTo('pass'))
  274. ->willReturn(false);
  275. $this->reflector->reflect($this->controller, $method);
  276. $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
  277. $middleware->beforeController($this->controller, $method);
  278. }
  279. public function testAfterExceptionWithSecurityExceptionNoStatus() {
  280. $request = new Request(
  281. ['server' => [
  282. 'PHP_AUTH_USER' => 'user',
  283. 'PHP_AUTH_PW' => 'pass'
  284. ]],
  285. $this->createMock(IRequestId::class),
  286. $this->createMock(IConfig::class)
  287. );
  288. $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
  289. $response = $middleware->afterException($this->controller, __FUNCTION__, new SecurityException('A security exception'));
  290. $expected = new JSONResponse(['message' => 'A security exception'], 500);
  291. $this->assertEquals($expected, $response);
  292. }
  293. public function testAfterExceptionWithSecurityExceptionWithStatus() {
  294. $request = new Request(
  295. ['server' => [
  296. 'PHP_AUTH_USER' => 'user',
  297. 'PHP_AUTH_PW' => 'pass'
  298. ]],
  299. $this->createMock(IRequestId::class),
  300. $this->createMock(IConfig::class)
  301. );
  302. $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
  303. $response = $middleware->afterException($this->controller, __FUNCTION__, new SecurityException('A security exception', 501));
  304. $expected = new JSONResponse(['message' => 'A security exception'], 501);
  305. $this->assertEquals($expected, $response);
  306. }
  307. public function testAfterExceptionWithRegularException() {
  308. $this->expectException(\Exception::class);
  309. $this->expectExceptionMessage('A regular exception');
  310. $request = new Request(
  311. ['server' => [
  312. 'PHP_AUTH_USER' => 'user',
  313. 'PHP_AUTH_PW' => 'pass'
  314. ]],
  315. $this->createMock(IRequestId::class),
  316. $this->createMock(IConfig::class)
  317. );
  318. $middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
  319. $middleware->afterException($this->controller, __FUNCTION__, new \Exception('A regular exception'));
  320. }
  321. }