CsrfProtectionMiddlewareTest.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.5.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\Http\Middleware;
  16. use Cake\Http\Middleware\CsrfProtectionMiddleware;
  17. use Cake\Http\Response;
  18. use Cake\Http\ServerRequest;
  19. use Cake\I18n\Time;
  20. use Cake\TestSuite\TestCase;
  21. /**
  22. * Test for CsrfProtection
  23. */
  24. class CsrfProtectionMiddlewareTest extends TestCase
  25. {
  26. /**
  27. * Data provider for HTTP method tests.
  28. *
  29. * HEAD and GET do not populate $_POST or request->data.
  30. *
  31. * @return array
  32. */
  33. public static function safeHttpMethodProvider()
  34. {
  35. return [
  36. ['GET'],
  37. ['HEAD'],
  38. ];
  39. }
  40. /**
  41. * Data provider for HTTP methods that can contain request bodies.
  42. *
  43. * @return array
  44. */
  45. public static function httpMethodProvider()
  46. {
  47. return [
  48. ['OPTIONS'], ['PATCH'], ['PUT'], ['POST'], ['DELETE'], ['PURGE'], ['INVALIDMETHOD']
  49. ];
  50. }
  51. /**
  52. * Provides the callback for the next middleware
  53. *
  54. * @return callable
  55. */
  56. protected function _getNextClosure()
  57. {
  58. return function ($request, $response) {
  59. return $response;
  60. };
  61. }
  62. /**
  63. * Test setting the cookie value
  64. *
  65. * @return void
  66. */
  67. public function testSettingCookie()
  68. {
  69. $request = new ServerRequest([
  70. 'environment' => ['REQUEST_METHOD' => 'GET'],
  71. 'webroot' => '/dir/',
  72. ]);
  73. $response = new Response();
  74. $closure = function ($request, $response) {
  75. $cookie = $response->cookie('csrfToken');
  76. $this->assertNotEmpty($cookie, 'Should set a token.');
  77. $this->assertRegExp('/^[a-f0-9]+$/', $cookie['value'], 'Should look like a hash.');
  78. $this->assertEquals(0, $cookie['expire'], 'session duration.');
  79. $this->assertEquals('/dir/', $cookie['path'], 'session path.');
  80. $this->assertEquals($cookie['value'], $request->params['_csrfToken']);
  81. };
  82. $middleware = new CsrfProtectionMiddleware();
  83. $middleware($request, $response, $closure);
  84. }
  85. /**
  86. * Test that the CSRF tokens are not required for idempotent operations
  87. *
  88. * @dataProvider safeHttpMethodProvider
  89. * @return void
  90. */
  91. public function testSafeMethodNoCsrfRequired($method)
  92. {
  93. $request = new ServerRequest([
  94. 'environment' => [
  95. 'REQUEST_METHOD' => $method,
  96. 'HTTP_X_CSRF_TOKEN' => 'nope',
  97. ],
  98. 'cookies' => ['csrfToken' => 'testing123']
  99. ]);
  100. $response = new Response();
  101. // No exception means the test is valid
  102. $middleware = new CsrfProtectionMiddleware();
  103. $response = $middleware($request, $response, $this->_getNextClosure());
  104. $this->assertInstanceOf(Response::class, $response);
  105. }
  106. /**
  107. * Test that the X-CSRF-Token works with the various http methods.
  108. *
  109. * @dataProvider httpMethodProvider
  110. * @return void
  111. */
  112. public function testValidTokenInHeader($method)
  113. {
  114. $request = new ServerRequest([
  115. 'environment' => [
  116. 'REQUEST_METHOD' => $method,
  117. 'HTTP_X_CSRF_TOKEN' => 'testing123',
  118. ],
  119. 'post' => ['a' => 'b'],
  120. 'cookies' => ['csrfToken' => 'testing123']
  121. ]);
  122. $response = new Response();
  123. // No exception means the test is valid
  124. $middleware = new CsrfProtectionMiddleware();
  125. $response = $middleware($request, $response, $this->_getNextClosure());
  126. $this->assertInstanceOf(Response::class, $response);
  127. }
  128. /**
  129. * Test that the X-CSRF-Token works with the various http methods.
  130. *
  131. * @dataProvider httpMethodProvider
  132. * @return void
  133. */
  134. public function testInvalidTokenInHeader($method)
  135. {
  136. $this->expectException(\Cake\Network\Exception\InvalidCsrfTokenException::class);
  137. $request = new ServerRequest([
  138. 'environment' => [
  139. 'REQUEST_METHOD' => $method,
  140. 'HTTP_X_CSRF_TOKEN' => 'nope',
  141. ],
  142. 'post' => ['a' => 'b'],
  143. 'cookies' => ['csrfToken' => 'testing123']
  144. ]);
  145. $response = new Response();
  146. $middleware = new CsrfProtectionMiddleware();
  147. $middleware($request, $response, $this->_getNextClosure());
  148. }
  149. /**
  150. * Test that request data works with the various http methods.
  151. *
  152. * @dataProvider httpMethodProvider
  153. * @return void
  154. */
  155. public function testValidTokenRequestData($method)
  156. {
  157. $request = new ServerRequest([
  158. 'environment' => [
  159. 'REQUEST_METHOD' => $method,
  160. ],
  161. 'post' => ['_csrfToken' => 'testing123'],
  162. 'cookies' => ['csrfToken' => 'testing123']
  163. ]);
  164. $response = new Response();
  165. $closure = function ($request, $response) {
  166. $this->assertNull($request->getData('_csrfToken'));
  167. };
  168. // No exception means everything is OK
  169. $middleware = new CsrfProtectionMiddleware();
  170. $middleware($request, $response, $closure);
  171. }
  172. /**
  173. * Test that request data works with the various http methods.
  174. *
  175. * @dataProvider httpMethodProvider
  176. * @return void
  177. */
  178. public function testInvalidTokenRequestData($method)
  179. {
  180. $this->expectException(\Cake\Network\Exception\InvalidCsrfTokenException::class);
  181. $request = new ServerRequest([
  182. 'environment' => [
  183. 'REQUEST_METHOD' => $method,
  184. ],
  185. 'post' => ['_csrfToken' => 'nope'],
  186. 'cookies' => ['csrfToken' => 'testing123']
  187. ]);
  188. $response = new Response();
  189. $middleware = new CsrfProtectionMiddleware();
  190. $middleware($request, $response, $this->_getNextClosure());
  191. }
  192. /**
  193. * Test that missing post field fails
  194. *
  195. * @return void
  196. */
  197. public function testInvalidTokenRequestDataMissing()
  198. {
  199. $this->expectException(\Cake\Network\Exception\InvalidCsrfTokenException::class);
  200. $request = new ServerRequest([
  201. 'environment' => [
  202. 'REQUEST_METHOD' => 'POST',
  203. ],
  204. 'post' => [],
  205. 'cookies' => ['csrfToken' => 'testing123']
  206. ]);
  207. $response = new Response();
  208. $middleware = new CsrfProtectionMiddleware();
  209. $middleware($request, $response, $this->_getNextClosure());
  210. }
  211. /**
  212. * Test that missing header and cookie fails
  213. *
  214. * @dataProvider httpMethodProvider
  215. * @return void
  216. */
  217. public function testInvalidTokenMissingCookie($method)
  218. {
  219. $this->expectException(\Cake\Network\Exception\InvalidCsrfTokenException::class);
  220. $request = new ServerRequest([
  221. 'environment' => [
  222. 'REQUEST_METHOD' => $method
  223. ],
  224. 'post' => ['_csrfToken' => 'could-be-valid'],
  225. 'cookies' => []
  226. ]);
  227. $response = new Response();
  228. $middleware = new CsrfProtectionMiddleware();
  229. $middleware($request, $response, $this->_getNextClosure());
  230. }
  231. /**
  232. * Test that the configuration options work.
  233. *
  234. * @return void
  235. */
  236. public function testConfigurationCookieCreate()
  237. {
  238. $request = new ServerRequest([
  239. 'environment' => ['REQUEST_METHOD' => 'GET'],
  240. 'webroot' => '/dir/'
  241. ]);
  242. $response = new Response();
  243. $closure = function ($request, $response) {
  244. $this->assertEmpty($response->cookie('csrfToken'));
  245. $cookie = $response->cookie('token');
  246. $this->assertNotEmpty($cookie, 'Should set a token.');
  247. $this->assertRegExp('/^[a-f0-9]+$/', $cookie['value'], 'Should look like a hash.');
  248. $this->assertWithinRange((new Time('+1 hour'))->format('U'), $cookie['expire'], 1, 'session duration.');
  249. $this->assertEquals('/dir/', $cookie['path'], 'session path.');
  250. $this->assertTrue($cookie['secure'], 'cookie security flag missing');
  251. $this->assertTrue($cookie['httpOnly'], 'cookie httpOnly flag missing');
  252. };
  253. $middleware = new CsrfProtectionMiddleware([
  254. 'cookieName' => 'token',
  255. 'expiry' => '+1 hour',
  256. 'secure' => true,
  257. 'httpOnly' => true
  258. ]);
  259. $middleware($request, $response, $closure);
  260. }
  261. /**
  262. * Test that the configuration options work.
  263. *
  264. * There should be no exception thrown.
  265. *
  266. * @return void
  267. */
  268. public function testConfigurationValidate()
  269. {
  270. $request = new ServerRequest([
  271. 'environment' => ['REQUEST_METHOD' => 'POST'],
  272. 'cookies' => ['csrfToken' => 'nope', 'token' => 'yes'],
  273. 'post' => ['_csrfToken' => 'no match', 'token' => 'yes'],
  274. ]);
  275. $response = new Response();
  276. $middleware = new CsrfProtectionMiddleware([
  277. 'cookieName' => 'token',
  278. 'field' => 'token',
  279. 'expiry' => 90,
  280. ]);
  281. $response = $middleware($request, $response, $this->_getNextClosure());
  282. $this->assertInstanceOf(Response::class, $response);
  283. }
  284. }