PageRenderTime 56ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/Cake/Test/Case/Controller/Component/SecurityComponentTest.php

https://bitbucket.org/gencer/cakephp
PHP | 1460 lines | 890 code | 175 blank | 395 comment | 0 complexity | 9de9f5cbec8269e77d15a782c8dfecb2 MD5 | raw file
  1. <?php
  2. /**
  3. * SecurityComponentTest file
  4. *
  5. * CakePHP(tm) Tests <http://book.cakephp.org/2.0/en/development/testing.html>
  6. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  7. *
  8. * Licensed under The MIT License
  9. * For full copyright and license information, please see the LICENSE.txt
  10. * Redistributions of files must retain the above copyright notice
  11. *
  12. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  13. * @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
  14. * @package Cake.Test.Case.Controller.Component
  15. * @since CakePHP(tm) v 1.2.0.5435
  16. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  17. */
  18. App::uses('SecurityComponent', 'Controller/Component');
  19. App::uses('Controller', 'Controller');
  20. /**
  21. * TestSecurityComponent
  22. *
  23. * @package Cake.Test.Case.Controller.Component
  24. */
  25. class TestSecurityComponent extends SecurityComponent {
  26. /**
  27. * validatePost method
  28. *
  29. * @param Controller $controller
  30. * @return bool
  31. */
  32. public function validatePost(Controller $controller) {
  33. return $this->_validatePost($controller);
  34. }
  35. }
  36. /**
  37. * SecurityTestController
  38. *
  39. * @package Cake.Test.Case.Controller.Component
  40. */
  41. class SecurityTestController extends Controller {
  42. /**
  43. * components property
  44. *
  45. * @var array
  46. */
  47. public $components = array('Session', 'TestSecurity');
  48. /**
  49. * failed property
  50. *
  51. * @var bool
  52. */
  53. public $failed = false;
  54. /**
  55. * Used for keeping track of headers in test
  56. *
  57. * @var array
  58. */
  59. public $testHeaders = array();
  60. /**
  61. * fail method
  62. *
  63. * @return void
  64. */
  65. public function fail() {
  66. $this->failed = true;
  67. }
  68. /**
  69. * redirect method
  70. *
  71. * @param string|array $url
  72. * @param mixed $code
  73. * @param mixed $exit
  74. * @return void
  75. */
  76. public function redirect($url, $status = null, $exit = true) {
  77. return $status;
  78. }
  79. /**
  80. * Convenience method for header()
  81. *
  82. * @param string $status
  83. * @return void
  84. */
  85. public function header($status) {
  86. $this->testHeaders[] = $status;
  87. }
  88. }
  89. class BrokenCallbackController extends Controller {
  90. public $name = 'UncallableCallback';
  91. public $components = array('Session', 'TestSecurity');
  92. public function index() {
  93. }
  94. protected function _fail() {
  95. }
  96. }
  97. /**
  98. * SecurityComponentTest class
  99. *
  100. * @package Cake.Test.Case.Controller.Component
  101. */
  102. class SecurityComponentTest extends CakeTestCase {
  103. /**
  104. * Controller property
  105. *
  106. * @var SecurityTestController
  107. */
  108. public $Controller;
  109. /**
  110. * oldSalt property
  111. *
  112. * @var string
  113. */
  114. public $oldSalt;
  115. /**
  116. * setUp method
  117. *
  118. * @return void
  119. */
  120. public function setUp() {
  121. parent::setUp();
  122. $request = $this->getMock('CakeRequest', array('here'), array('posts/index', false));
  123. $request->addParams(array('controller' => 'posts', 'action' => 'index'));
  124. $request->expects($this->any())
  125. ->method('here')
  126. ->will($this->returnValue('/posts/index'));
  127. $this->Controller = new SecurityTestController($request);
  128. $this->Controller->Components->init($this->Controller);
  129. $this->Controller->Security = $this->Controller->TestSecurity;
  130. $this->Controller->Security->blackHoleCallback = 'fail';
  131. $this->Security = $this->Controller->Security;
  132. $this->Security->csrfCheck = false;
  133. Configure::write('Security.salt', 'foo!');
  134. }
  135. /**
  136. * Tear-down method. Resets environment state.
  137. *
  138. * @return void
  139. */
  140. public function tearDown() {
  141. parent::tearDown();
  142. $this->Controller->Session->delete('_Token');
  143. unset($this->Controller->Security);
  144. unset($this->Controller->Component);
  145. unset($this->Controller);
  146. }
  147. /**
  148. * Test that requests are still blackholed when controller has incorrect
  149. * visibility keyword in the blackhole callback
  150. *
  151. * @expectedException BadRequestException
  152. * @return void
  153. */
  154. public function testBlackholeWithBrokenCallback() {
  155. $request = new CakeRequest('posts/index', false);
  156. $request->addParams(array(
  157. 'controller' => 'posts', 'action' => 'index')
  158. );
  159. $this->Controller = new BrokenCallbackController($request);
  160. $this->Controller->Components->init($this->Controller);
  161. $this->Controller->Security = $this->Controller->TestSecurity;
  162. $this->Controller->Security->blackHoleCallback = '_fail';
  163. $this->Controller->Security->startup($this->Controller);
  164. $this->Controller->Security->blackHole($this->Controller, 'csrf');
  165. }
  166. /**
  167. * Ensure that directly requesting the blackholeCallback as the controller
  168. * action results in an exception.
  169. *
  170. * @return void
  171. */
  172. public function testExceptionWhenActionIsBlackholeCallback() {
  173. $this->Controller->request->addParams(array(
  174. 'controller' => 'posts',
  175. 'action' => 'fail'
  176. ));
  177. $this->assertFalse($this->Controller->failed);
  178. $this->Controller->Security->startup($this->Controller);
  179. $this->assertTrue($this->Controller->failed, 'Request was blackholed.');
  180. }
  181. /**
  182. * test that initialize can set properties.
  183. *
  184. * @return void
  185. */
  186. public function testConstructorSettingProperties() {
  187. $settings = array(
  188. 'requirePost' => array('edit', 'update'),
  189. 'requireSecure' => array('update_account'),
  190. 'requireGet' => array('index'),
  191. 'validatePost' => false,
  192. );
  193. $Security = new SecurityComponent($this->Controller->Components, $settings);
  194. $this->Controller->Security->initialize($this->Controller, $settings);
  195. $this->assertEquals($Security->requirePost, $settings['requirePost']);
  196. $this->assertEquals($Security->requireSecure, $settings['requireSecure']);
  197. $this->assertEquals($Security->requireGet, $settings['requireGet']);
  198. $this->assertEquals($Security->validatePost, $settings['validatePost']);
  199. }
  200. /**
  201. * testStartup method
  202. *
  203. * @return void
  204. */
  205. public function testStartup() {
  206. $this->Controller->Security->startup($this->Controller);
  207. $result = $this->Controller->params['_Token']['key'];
  208. $this->assertNotNull($result);
  209. $this->assertTrue($this->Controller->Session->check('_Token'));
  210. }
  211. /**
  212. * testRequirePostFail method
  213. *
  214. * @return void
  215. */
  216. public function testRequirePostFail() {
  217. $_SERVER['REQUEST_METHOD'] = 'GET';
  218. $this->Controller->request['action'] = 'posted';
  219. $this->Controller->Security->requirePost(array('posted'));
  220. $this->Controller->Security->startup($this->Controller);
  221. $this->assertTrue($this->Controller->failed);
  222. }
  223. /**
  224. * testRequirePostSucceed method
  225. *
  226. * @return void
  227. */
  228. public function testRequirePostSucceed() {
  229. $_SERVER['REQUEST_METHOD'] = 'POST';
  230. $this->Controller->request['action'] = 'posted';
  231. $this->Controller->Security->requirePost('posted');
  232. $this->Security->startup($this->Controller);
  233. $this->assertFalse($this->Controller->failed);
  234. }
  235. /**
  236. * testRequireSecureFail method
  237. *
  238. * @return void
  239. */
  240. public function testRequireSecureFail() {
  241. $_SERVER['HTTPS'] = 'off';
  242. $_SERVER['REQUEST_METHOD'] = 'POST';
  243. $this->Controller->request['action'] = 'posted';
  244. $this->Controller->Security->requireSecure(array('posted'));
  245. $this->Controller->Security->startup($this->Controller);
  246. $this->assertTrue($this->Controller->failed);
  247. }
  248. /**
  249. * testRequireSecureSucceed method
  250. *
  251. * @return void
  252. */
  253. public function testRequireSecureSucceed() {
  254. $_SERVER['REQUEST_METHOD'] = 'Secure';
  255. $this->Controller->request['action'] = 'posted';
  256. $_SERVER['HTTPS'] = 'on';
  257. $this->Controller->Security->requireSecure('posted');
  258. $this->Controller->Security->startup($this->Controller);
  259. $this->assertFalse($this->Controller->failed);
  260. }
  261. /**
  262. * testRequireAuthFail method
  263. *
  264. * @return void
  265. */
  266. public function testRequireAuthFail() {
  267. $_SERVER['REQUEST_METHOD'] = 'AUTH';
  268. $this->Controller->request['action'] = 'posted';
  269. $this->Controller->request->data = array('username' => 'willy', 'password' => 'somePass');
  270. $this->Controller->Security->requireAuth(array('posted'));
  271. $this->Controller->Security->startup($this->Controller);
  272. $this->assertTrue($this->Controller->failed);
  273. $this->Controller->Session->write('_Token', array('allowedControllers' => array()));
  274. $this->Controller->request->data = array('username' => 'willy', 'password' => 'somePass');
  275. $this->Controller->request['action'] = 'posted';
  276. $this->Controller->Security->requireAuth('posted');
  277. $this->Controller->Security->startup($this->Controller);
  278. $this->assertTrue($this->Controller->failed);
  279. $this->Controller->Session->write('_Token', array(
  280. 'allowedControllers' => array('SecurityTest'), 'allowedActions' => array('posted2')
  281. ));
  282. $this->Controller->request->data = array('username' => 'willy', 'password' => 'somePass');
  283. $this->Controller->request['action'] = 'posted';
  284. $this->Controller->Security->requireAuth('posted');
  285. $this->Controller->Security->startup($this->Controller);
  286. $this->assertTrue($this->Controller->failed);
  287. }
  288. /**
  289. * testRequireAuthSucceed method
  290. *
  291. * @return void
  292. */
  293. public function testRequireAuthSucceed() {
  294. $_SERVER['REQUEST_METHOD'] = 'AUTH';
  295. $this->Controller->request['action'] = 'posted';
  296. $this->Controller->Security->requireAuth('posted');
  297. $this->Controller->Security->startup($this->Controller);
  298. $this->assertFalse($this->Controller->failed);
  299. $this->Controller->Security->Session->write('_Token', array(
  300. 'allowedControllers' => array('SecurityTest'), 'allowedActions' => array('posted')
  301. ));
  302. $this->Controller->request['controller'] = 'SecurityTest';
  303. $this->Controller->request['action'] = 'posted';
  304. $this->Controller->request->data = array(
  305. 'username' => 'willy', 'password' => 'somePass', '_Token' => ''
  306. );
  307. $this->Controller->action = 'posted';
  308. $this->Controller->Security->requireAuth('posted');
  309. $this->Controller->Security->startup($this->Controller);
  310. $this->assertFalse($this->Controller->failed);
  311. }
  312. /**
  313. * testRequirePostSucceedWrongMethod method
  314. *
  315. * @return void
  316. */
  317. public function testRequirePostSucceedWrongMethod() {
  318. $_SERVER['REQUEST_METHOD'] = 'GET';
  319. $this->Controller->request['action'] = 'getted';
  320. $this->Controller->Security->requirePost('posted');
  321. $this->Controller->Security->startup($this->Controller);
  322. $this->assertFalse($this->Controller->failed);
  323. }
  324. /**
  325. * testRequireGetFail method
  326. *
  327. * @return void
  328. */
  329. public function testRequireGetFail() {
  330. $_SERVER['REQUEST_METHOD'] = 'POST';
  331. $this->Controller->request['action'] = 'getted';
  332. $this->Controller->Security->requireGet(array('getted'));
  333. $this->Controller->Security->startup($this->Controller);
  334. $this->assertTrue($this->Controller->failed);
  335. }
  336. /**
  337. * testRequireGetSucceed method
  338. *
  339. * @return void
  340. */
  341. public function testRequireGetSucceed() {
  342. $_SERVER['REQUEST_METHOD'] = 'GET';
  343. $this->Controller->request['action'] = 'getted';
  344. $this->Controller->Security->requireGet('getted');
  345. $this->Controller->Security->startup($this->Controller);
  346. $this->assertFalse($this->Controller->failed);
  347. }
  348. /**
  349. * testRequireGetSucceedWrongMethod method
  350. *
  351. * @return void
  352. */
  353. public function testRequireGetSucceedWrongMethod() {
  354. $_SERVER['REQUEST_METHOD'] = 'POST';
  355. $this->Controller->request['action'] = 'posted';
  356. $this->Security->requireGet('getted');
  357. $this->Security->startup($this->Controller);
  358. $this->assertFalse($this->Controller->failed);
  359. }
  360. /**
  361. * testRequirePutFail method
  362. *
  363. * @return void
  364. */
  365. public function testRequirePutFail() {
  366. $_SERVER['REQUEST_METHOD'] = 'POST';
  367. $this->Controller->request['action'] = 'putted';
  368. $this->Controller->Security->requirePut(array('putted'));
  369. $this->Controller->Security->startup($this->Controller);
  370. $this->assertTrue($this->Controller->failed);
  371. }
  372. /**
  373. * testRequirePutSucceed method
  374. *
  375. * @return void
  376. */
  377. public function testRequirePutSucceed() {
  378. $_SERVER['REQUEST_METHOD'] = 'PUT';
  379. $this->Controller->request['action'] = 'putted';
  380. $this->Controller->Security->requirePut('putted');
  381. $this->Controller->Security->startup($this->Controller);
  382. $this->assertFalse($this->Controller->failed);
  383. }
  384. /**
  385. * testRequirePutSucceedWrongMethod method
  386. *
  387. * @return void
  388. */
  389. public function testRequirePutSucceedWrongMethod() {
  390. $_SERVER['REQUEST_METHOD'] = 'POST';
  391. $this->Controller->request['action'] = 'posted';
  392. $this->Controller->Security->requirePut('putted');
  393. $this->Controller->Security->startup($this->Controller);
  394. $this->assertFalse($this->Controller->failed);
  395. }
  396. /**
  397. * testRequireDeleteFail method
  398. *
  399. * @return void
  400. */
  401. public function testRequireDeleteFail() {
  402. $_SERVER['REQUEST_METHOD'] = 'POST';
  403. $this->Controller->request['action'] = 'deleted';
  404. $this->Controller->Security->requireDelete(array('deleted', 'other_method'));
  405. $this->Controller->Security->startup($this->Controller);
  406. $this->assertTrue($this->Controller->failed);
  407. }
  408. /**
  409. * testRequireDeleteSucceed method
  410. *
  411. * @return void
  412. */
  413. public function testRequireDeleteSucceed() {
  414. $_SERVER['REQUEST_METHOD'] = 'DELETE';
  415. $this->Controller->request['action'] = 'deleted';
  416. $this->Controller->Security->requireDelete('deleted');
  417. $this->Controller->Security->startup($this->Controller);
  418. $this->assertFalse($this->Controller->failed);
  419. }
  420. /**
  421. * testRequireDeleteSucceedWrongMethod method
  422. *
  423. * @return void
  424. */
  425. public function testRequireDeleteSucceedWrongMethod() {
  426. $_SERVER['REQUEST_METHOD'] = 'POST';
  427. $this->Controller->request['action'] = 'posted';
  428. $this->Controller->Security->requireDelete('deleted');
  429. $this->Controller->Security->startup($this->Controller);
  430. $this->assertFalse($this->Controller->failed);
  431. }
  432. /**
  433. * Simple hash validation test
  434. *
  435. * @return void
  436. */
  437. public function testValidatePost() {
  438. $this->Controller->Security->startup($this->Controller);
  439. $key = $this->Controller->request->params['_Token']['key'];
  440. $fields = '01c1f6dbba02ac6f21b229eab1cc666839b14303%3AModel.valid';
  441. $unlocked = '';
  442. $this->Controller->request->data = array(
  443. 'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'),
  444. '_Token' => compact('key', 'fields', 'unlocked')
  445. );
  446. $this->assertTrue($this->Controller->Security->validatePost($this->Controller));
  447. }
  448. /**
  449. * Test that validatePost fails if you are missing the session information.
  450. *
  451. * @return void
  452. */
  453. public function testValidatePostNoSession() {
  454. $this->Controller->Security->startup($this->Controller);
  455. $this->Controller->Session->delete('_Token');
  456. $key = $this->Controller->params['_Token']['key'];
  457. $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877%3AModel.valid';
  458. $this->Controller->data = array(
  459. 'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'),
  460. '_Token' => compact('key', 'fields')
  461. );
  462. $this->assertFalse($this->Controller->Security->validatePost($this->Controller));
  463. }
  464. /**
  465. * test that validatePost fails if any of its required fields are missing.
  466. *
  467. * @return void
  468. */
  469. public function testValidatePostFormHacking() {
  470. $this->Controller->Security->startup($this->Controller);
  471. $key = $this->Controller->params['_Token']['key'];
  472. $unlocked = '';
  473. $this->Controller->request->data = array(
  474. 'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'),
  475. '_Token' => compact('key', 'unlocked')
  476. );
  477. $result = $this->Controller->Security->validatePost($this->Controller);
  478. $this->assertFalse($result, 'validatePost passed when fields were missing. %s');
  479. }
  480. /**
  481. * Test that objects can't be passed into the serialized string. This was a vector for RFI and LFI
  482. * attacks. Thanks to Felix Wilhelm
  483. *
  484. * @return void
  485. */
  486. public function testValidatePostObjectDeserialize() {
  487. $this->Controller->Security->startup($this->Controller);
  488. $key = $this->Controller->request->params['_Token']['key'];
  489. $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877';
  490. $unlocked = '';
  491. // a corrupted serialized object, so we can see if it ever gets to deserialize
  492. $attack = 'O:3:"App":1:{s:5:"__map";a:1:{s:3:"foo";s:7:"Hacked!";s:1:"fail"}}';
  493. $fields .= urlencode(':' . str_rot13($attack));
  494. $this->Controller->request->data = array(
  495. 'Model' => array('username' => 'mark', 'password' => 'foo', 'valid' => '0'),
  496. '_Token' => compact('key', 'fields', 'unlocked')
  497. );
  498. $result = $this->Controller->Security->validatePost($this->Controller);
  499. $this->assertFalse($result, 'validatePost passed when key was missing. %s');
  500. }
  501. /**
  502. * Tests validation of checkbox arrays
  503. *
  504. * @return void
  505. */
  506. public function testValidatePostArray() {
  507. $this->Controller->Security->startup($this->Controller);
  508. $key = $this->Controller->request->params['_Token']['key'];
  509. $fields = '38504e4a341d4e6eadb437217efd91270e558d55%3A';
  510. $unlocked = '';
  511. $this->Controller->request->data = array(
  512. 'Model' => array('multi_field' => array('1', '3')),
  513. '_Token' => compact('key', 'fields', 'unlocked')
  514. );
  515. $this->assertTrue($this->Controller->Security->validatePost($this->Controller));
  516. }
  517. /**
  518. * testValidatePostNoModel method
  519. *
  520. * @return void
  521. */
  522. public function testValidatePostNoModel() {
  523. $this->Controller->Security->startup($this->Controller);
  524. $key = $this->Controller->request->params['_Token']['key'];
  525. $fields = 'c5bc49a6c938c820e7e538df3d8ab7bffbc97ef9%3A';
  526. $unlocked = '';
  527. $this->Controller->request->data = array(
  528. 'anything' => 'some_data',
  529. '_Token' => compact('key', 'fields', 'unlocked')
  530. );
  531. $result = $this->Controller->Security->validatePost($this->Controller);
  532. $this->assertTrue($result);
  533. }
  534. /**
  535. * testValidatePostSimple method
  536. *
  537. * @return void
  538. */
  539. public function testValidatePostSimple() {
  540. $this->Controller->Security->startup($this->Controller);
  541. $key = $this->Controller->request->params['_Token']['key'];
  542. $fields = '5415d31b4483c1e09ddb58d2a91ba9650b12aa83%3A';
  543. $unlocked = '';
  544. $this->Controller->request->data = array(
  545. 'Model' => array('username' => '', 'password' => ''),
  546. '_Token' => compact('key', 'fields', 'unlocked')
  547. );
  548. $result = $this->Controller->Security->validatePost($this->Controller);
  549. $this->assertTrue($result);
  550. }
  551. /**
  552. * Tests hash validation for multiple records, including locked fields
  553. *
  554. * @return void
  555. */
  556. public function testValidatePostComplex() {
  557. $this->Controller->Security->startup($this->Controller);
  558. $key = $this->Controller->request->params['_Token']['key'];
  559. $fields = 'b72a99e923687687bb5e64025d3cc65e1cecced4%3AAddresses.0.id%7CAddresses.1.id';
  560. $unlocked = '';
  561. $this->Controller->request->data = array(
  562. 'Addresses' => array(
  563. '0' => array(
  564. 'id' => '123456', 'title' => '', 'first_name' => '', 'last_name' => '',
  565. 'address' => '', 'city' => '', 'phone' => '', 'primary' => ''
  566. ),
  567. '1' => array(
  568. 'id' => '654321', 'title' => '', 'first_name' => '', 'last_name' => '',
  569. 'address' => '', 'city' => '', 'phone' => '', 'primary' => ''
  570. )
  571. ),
  572. '_Token' => compact('key', 'fields', 'unlocked')
  573. );
  574. $result = $this->Controller->Security->validatePost($this->Controller);
  575. $this->assertTrue($result);
  576. }
  577. /**
  578. * test ValidatePost with multiple select elements.
  579. *
  580. * @return void
  581. */
  582. public function testValidatePostMultipleSelect() {
  583. $this->Controller->Security->startup($this->Controller);
  584. $key = $this->Controller->request->params['_Token']['key'];
  585. $fields = '8a764bdb989132c1d46f9a45f64ce2da5f9eebb9%3A';
  586. $unlocked = '';
  587. $this->Controller->request->data = array(
  588. 'Tag' => array('Tag' => array(1, 2)),
  589. '_Token' => compact('key', 'fields', 'unlocked'),
  590. );
  591. $result = $this->Controller->Security->validatePost($this->Controller);
  592. $this->assertTrue($result);
  593. $this->Controller->request->data = array(
  594. 'Tag' => array('Tag' => array(1, 2, 3)),
  595. '_Token' => compact('key', 'fields', 'unlocked'),
  596. );
  597. $result = $this->Controller->Security->validatePost($this->Controller);
  598. $this->assertTrue($result);
  599. $this->Controller->request->data = array(
  600. 'Tag' => array('Tag' => array(1, 2, 3, 4)),
  601. '_Token' => compact('key', 'fields', 'unlocked'),
  602. );
  603. $result = $this->Controller->Security->validatePost($this->Controller);
  604. $this->assertTrue($result);
  605. $fields = '722de3615e63fdff899e86e85e6498b11c50bb66%3A';
  606. $this->Controller->request->data = array(
  607. 'User.password' => 'bar', 'User.name' => 'foo', 'User.is_valid' => '1',
  608. 'Tag' => array('Tag' => array(1)),
  609. '_Token' => compact('key', 'fields', 'unlocked'),
  610. );
  611. $result = $this->Controller->Security->validatePost($this->Controller);
  612. $this->assertTrue($result);
  613. }
  614. /**
  615. * testValidatePostCheckbox method
  616. *
  617. * First block tests un-checked checkbox
  618. * Second block tests checked checkbox
  619. *
  620. * @return void
  621. */
  622. public function testValidatePostCheckbox() {
  623. $this->Controller->Security->startup($this->Controller);
  624. $key = $this->Controller->request->params['_Token']['key'];
  625. $fields = '01c1f6dbba02ac6f21b229eab1cc666839b14303%3AModel.valid';
  626. $unlocked = '';
  627. $this->Controller->request->data = array(
  628. 'Model' => array('username' => '', 'password' => '', 'valid' => '0'),
  629. '_Token' => compact('key', 'fields', 'unlocked')
  630. );
  631. $result = $this->Controller->Security->validatePost($this->Controller);
  632. $this->assertTrue($result);
  633. $fields = 'efbcf463a2c31e97c85d95eedc41dff9e9c6a026%3A';
  634. $this->Controller->request->data = array(
  635. 'Model' => array('username' => '', 'password' => '', 'valid' => '0'),
  636. '_Token' => compact('key', 'fields', 'unlocked')
  637. );
  638. $result = $this->Controller->Security->validatePost($this->Controller);
  639. $this->assertTrue($result);
  640. $this->Controller->request->data = array();
  641. $this->Controller->Security->startup($this->Controller);
  642. $key = $this->Controller->request->params['_Token']['key'];
  643. $this->Controller->request->data = array(
  644. 'Model' => array('username' => '', 'password' => '', 'valid' => '0'),
  645. '_Token' => compact('key', 'fields', 'unlocked')
  646. );
  647. $result = $this->Controller->Security->validatePost($this->Controller);
  648. $this->assertTrue($result);
  649. }
  650. /**
  651. * testValidatePostHidden method
  652. *
  653. * @return void
  654. */
  655. public function testValidatePostHidden() {
  656. $this->Controller->Security->startup($this->Controller);
  657. $key = $this->Controller->request->params['_Token']['key'];
  658. $fields = 'baaf832a714b39a0618238ac89c7065fc8ec853e%3AModel.hidden%7CModel.other_hidden';
  659. $unlocked = '';
  660. $this->Controller->request->data = array(
  661. 'Model' => array(
  662. 'username' => '', 'password' => '', 'hidden' => '0',
  663. 'other_hidden' => 'some hidden value'
  664. ),
  665. '_Token' => compact('key', 'fields', 'unlocked')
  666. );
  667. $result = $this->Controller->Security->validatePost($this->Controller);
  668. $this->assertTrue($result);
  669. }
  670. /**
  671. * testValidatePostWithDisabledFields method
  672. *
  673. * @return void
  674. */
  675. public function testValidatePostWithDisabledFields() {
  676. $this->Controller->Security->disabledFields = array('Model.username', 'Model.password');
  677. $this->Controller->Security->startup($this->Controller);
  678. $key = $this->Controller->request->params['_Token']['key'];
  679. $fields = 'aa7f254ebd8bf2ef118bc5ca1e191d1ae96857f5%3AModel.hidden';
  680. $unlocked = '';
  681. $this->Controller->request->data = array(
  682. 'Model' => array(
  683. 'username' => '', 'password' => '', 'hidden' => '0'
  684. ),
  685. '_Token' => compact('fields', 'key', 'unlocked')
  686. );
  687. $result = $this->Controller->Security->validatePost($this->Controller);
  688. $this->assertTrue($result);
  689. }
  690. /**
  691. * test validating post data with posted unlocked fields.
  692. *
  693. * @return void
  694. */
  695. public function testValidatePostDisabledFieldsInData() {
  696. $this->Controller->Security->startup($this->Controller);
  697. $key = $this->Controller->request->params['_Token']['key'];
  698. $unlocked = 'Model.username';
  699. $fields = array('Model.hidden', 'Model.password');
  700. $fields = urlencode(Security::hash(
  701. '/posts/index' .
  702. serialize($fields) .
  703. $unlocked .
  704. Configure::read('Security.salt'))
  705. );
  706. $this->Controller->request->data = array(
  707. 'Model' => array(
  708. 'username' => 'mark',
  709. 'password' => 'sekret',
  710. 'hidden' => '0'
  711. ),
  712. '_Token' => compact('fields', 'key', 'unlocked')
  713. );
  714. $result = $this->Controller->Security->validatePost($this->Controller);
  715. $this->assertTrue($result);
  716. }
  717. /**
  718. * test that missing 'unlocked' input causes failure
  719. *
  720. * @return void
  721. */
  722. public function testValidatePostFailNoDisabled() {
  723. $this->Controller->Security->startup($this->Controller);
  724. $key = $this->Controller->request->params['_Token']['key'];
  725. $fields = array('Model.hidden', 'Model.password', 'Model.username');
  726. $fields = urlencode(Security::hash(serialize($fields) . Configure::read('Security.salt')));
  727. $this->Controller->request->data = array(
  728. 'Model' => array(
  729. 'username' => 'mark',
  730. 'password' => 'sekret',
  731. 'hidden' => '0'
  732. ),
  733. '_Token' => compact('fields', 'key')
  734. );
  735. $result = $this->Controller->Security->validatePost($this->Controller);
  736. $this->assertFalse($result);
  737. }
  738. /**
  739. * Test that validatePost fails when unlocked fields are changed.
  740. *
  741. * @return void
  742. */
  743. public function testValidatePostFailDisabledFieldTampering() {
  744. $this->Controller->Security->startup($this->Controller);
  745. $key = $this->Controller->request->params['_Token']['key'];
  746. $unlocked = 'Model.username';
  747. $fields = array('Model.hidden', 'Model.password');
  748. $fields = urlencode(Security::hash(serialize($fields) . $unlocked . Configure::read('Security.salt')));
  749. // Tamper the values.
  750. $unlocked = 'Model.username|Model.password';
  751. $this->Controller->request->data = array(
  752. 'Model' => array(
  753. 'username' => 'mark',
  754. 'password' => 'sekret',
  755. 'hidden' => '0'
  756. ),
  757. '_Token' => compact('fields', 'key', 'unlocked')
  758. );
  759. $result = $this->Controller->Security->validatePost($this->Controller);
  760. $this->assertFalse($result);
  761. }
  762. /**
  763. * testValidateHiddenMultipleModel method
  764. *
  765. * @return void
  766. */
  767. public function testValidateHiddenMultipleModel() {
  768. $this->Controller->Security->startup($this->Controller);
  769. $key = $this->Controller->request->params['_Token']['key'];
  770. $fields = '38dd8a37bbb52e67ee4eb812bf1725a6a18b989b%3AModel.valid%7CModel2.valid%7CModel3.valid';
  771. $unlocked = '';
  772. $this->Controller->request->data = array(
  773. 'Model' => array('username' => '', 'password' => '', 'valid' => '0'),
  774. 'Model2' => array('valid' => '0'),
  775. 'Model3' => array('valid' => '0'),
  776. '_Token' => compact('key', 'fields', 'unlocked')
  777. );
  778. $result = $this->Controller->Security->validatePost($this->Controller);
  779. $this->assertTrue($result);
  780. }
  781. /**
  782. * testValidateHasManyModel method
  783. *
  784. * @return void
  785. */
  786. public function testValidateHasManyModel() {
  787. $this->Controller->Security->startup($this->Controller);
  788. $key = $this->Controller->request->params['_Token']['key'];
  789. $fields = 'dcef68de6634c60d2e60484ad0e2faec003456e6%3AModel.0.hidden%7CModel.0.valid';
  790. $fields .= '%7CModel.1.hidden%7CModel.1.valid';
  791. $unlocked = '';
  792. $this->Controller->request->data = array(
  793. 'Model' => array(
  794. array(
  795. 'username' => 'username', 'password' => 'password',
  796. 'hidden' => 'value', 'valid' => '0'
  797. ),
  798. array(
  799. 'username' => 'username', 'password' => 'password',
  800. 'hidden' => 'value', 'valid' => '0'
  801. )
  802. ),
  803. '_Token' => compact('key', 'fields', 'unlocked')
  804. );
  805. $result = $this->Controller->Security->validatePost($this->Controller);
  806. $this->assertTrue($result);
  807. }
  808. /**
  809. * testValidateHasManyRecordsPass method
  810. *
  811. * @return void
  812. */
  813. public function testValidateHasManyRecordsPass() {
  814. $this->Controller->Security->startup($this->Controller);
  815. $key = $this->Controller->request->params['_Token']['key'];
  816. $fields = '8b6880fbbd4b69279155f899652ecffdd9b4c5a1%3AAddress.0.id%7CAddress.0.primary%7C';
  817. $fields .= 'Address.1.id%7CAddress.1.primary';
  818. $unlocked = '';
  819. $this->Controller->request->data = array(
  820. 'Address' => array(
  821. 0 => array(
  822. 'id' => '123',
  823. 'title' => 'home',
  824. 'first_name' => 'Bilbo',
  825. 'last_name' => 'Baggins',
  826. 'address' => '23 Bag end way',
  827. 'city' => 'the shire',
  828. 'phone' => 'N/A',
  829. 'primary' => '1',
  830. ),
  831. 1 => array(
  832. 'id' => '124',
  833. 'title' => 'home',
  834. 'first_name' => 'Frodo',
  835. 'last_name' => 'Baggins',
  836. 'address' => '50 Bag end way',
  837. 'city' => 'the shire',
  838. 'phone' => 'N/A',
  839. 'primary' => '1'
  840. )
  841. ),
  842. '_Token' => compact('key', 'fields', 'unlocked')
  843. );
  844. $result = $this->Controller->Security->validatePost($this->Controller);
  845. $this->assertTrue($result);
  846. }
  847. /**
  848. * Test that values like Foo.0.1
  849. *
  850. * @return void
  851. */
  852. public function testValidateNestedNumericSets() {
  853. $this->Controller->Security->startup($this->Controller);
  854. $key = $this->Controller->request->params['_Token']['key'];
  855. $unlocked = '';
  856. $hashFields = array('TaxonomyData');
  857. $fields = urlencode(
  858. Security::hash(
  859. '/posts/index' .
  860. serialize($hashFields) .
  861. $unlocked .
  862. Configure::read('Security.salt'), 'sha1')
  863. );
  864. $this->Controller->request->data = array(
  865. 'TaxonomyData' => array(
  866. 1 => array(array(2)),
  867. 2 => array(array(3))
  868. ),
  869. '_Token' => compact('key', 'fields', 'unlocked')
  870. );
  871. $result = $this->Controller->Security->validatePost($this->Controller);
  872. $this->assertTrue($result);
  873. }
  874. /**
  875. * testValidateHasManyRecords method
  876. *
  877. * validatePost should fail, hidden fields have been changed.
  878. *
  879. * @return void
  880. */
  881. public function testValidateHasManyRecordsFail() {
  882. $this->Controller->Security->startup($this->Controller);
  883. $key = $this->Controller->request->params['_Token']['key'];
  884. $fields = '7a203edb3d345bbf38fe0dccae960da8842e11d7%3AAddress.0.id%7CAddress.0.primary%7C';
  885. $fields .= 'Address.1.id%7CAddress.1.primary';
  886. $unlocked = '';
  887. $this->Controller->request->data = array(
  888. 'Address' => array(
  889. 0 => array(
  890. 'id' => '123',
  891. 'title' => 'home',
  892. 'first_name' => 'Bilbo',
  893. 'last_name' => 'Baggins',
  894. 'address' => '23 Bag end way',
  895. 'city' => 'the shire',
  896. 'phone' => 'N/A',
  897. 'primary' => '5',
  898. ),
  899. 1 => array(
  900. 'id' => '124',
  901. 'title' => 'home',
  902. 'first_name' => 'Frodo',
  903. 'last_name' => 'Baggins',
  904. 'address' => '50 Bag end way',
  905. 'city' => 'the shire',
  906. 'phone' => 'N/A',
  907. 'primary' => '1'
  908. )
  909. ),
  910. '_Token' => compact('key', 'fields', 'unlocked')
  911. );
  912. $result = $this->Controller->Security->validatePost($this->Controller);
  913. $this->assertFalse($result);
  914. }
  915. /**
  916. * testFormDisabledFields method
  917. *
  918. * @return void
  919. */
  920. public function testFormDisabledFields() {
  921. $this->Controller->Security->startup($this->Controller);
  922. $key = $this->Controller->request->params['_Token']['key'];
  923. $fields = '216ee717efd1a251a6d6e9efbb96005a9d09f1eb%3An%3A0%3A%7B%7D';
  924. $unlocked = '';
  925. $this->Controller->request->data = array(
  926. 'MyModel' => array('name' => 'some data'),
  927. '_Token' => compact('key', 'fields', 'unlocked')
  928. );
  929. $result = $this->Controller->Security->validatePost($this->Controller);
  930. $this->assertFalse($result);
  931. $this->Controller->Security->startup($this->Controller);
  932. $this->Controller->Security->disabledFields = array('MyModel.name');
  933. $key = $this->Controller->request->params['_Token']['key'];
  934. $this->Controller->request->data = array(
  935. 'MyModel' => array('name' => 'some data'),
  936. '_Token' => compact('key', 'fields', 'unlocked')
  937. );
  938. $result = $this->Controller->Security->validatePost($this->Controller);
  939. $this->assertTrue($result);
  940. }
  941. /**
  942. * testRadio method
  943. *
  944. * @return void
  945. */
  946. public function testValidatePostRadio() {
  947. $this->Controller->Security->startup($this->Controller);
  948. $key = $this->Controller->request->params['_Token']['key'];
  949. $fields = '3be63770e7953c6d2119f5377a9303372040f66f%3An%3A0%3A%7B%7D';
  950. $unlocked = '';
  951. $this->Controller->request->data = array(
  952. '_Token' => compact('key', 'fields', 'unlocked')
  953. );
  954. $result = $this->Controller->Security->validatePost($this->Controller);
  955. $this->assertFalse($result);
  956. $this->Controller->request->data = array(
  957. '_Token' => compact('key', 'fields', 'unlocked'),
  958. 'Test' => array('test' => '')
  959. );
  960. $result = $this->Controller->Security->validatePost($this->Controller);
  961. $this->assertTrue($result);
  962. $this->Controller->request->data = array(
  963. '_Token' => compact('key', 'fields', 'unlocked'),
  964. 'Test' => array('test' => '1')
  965. );
  966. $result = $this->Controller->Security->validatePost($this->Controller);
  967. $this->assertTrue($result);
  968. $this->Controller->request->data = array(
  969. '_Token' => compact('key', 'fields', 'unlocked'),
  970. 'Test' => array('test' => '2')
  971. );
  972. $result = $this->Controller->Security->validatePost($this->Controller);
  973. $this->assertTrue($result);
  974. }
  975. /**
  976. * test validatePost uses here() as a hash input.
  977. *
  978. * @return void
  979. */
  980. public function testValidatePostUrlAsHashInput() {
  981. $this->Controller->Security->startup($this->Controller);
  982. $key = $this->Controller->request->params['_Token']['key'];
  983. $fields = '5415d31b4483c1e09ddb58d2a91ba9650b12aa83%3A';
  984. $unlocked = '';
  985. $this->Controller->request->data = array(
  986. 'Model' => array('username' => '', 'password' => ''),
  987. '_Token' => compact('key', 'fields', 'unlocked')
  988. );
  989. $this->assertTrue($this->Controller->Security->validatePost($this->Controller));
  990. $request = $this->getMock('CakeRequest', array('here'), array('articles/edit/1', false));
  991. $request->expects($this->at(0))
  992. ->method('here')
  993. ->will($this->returnValue('/posts/index?page=1'));
  994. $request->expects($this->at(1))
  995. ->method('here')
  996. ->will($this->returnValue('/posts/edit/1'));
  997. $this->Controller->Security->request = $request;
  998. $this->assertFalse($this->Controller->Security->validatePost($this->Controller));
  999. $this->assertFalse($this->Controller->Security->validatePost($this->Controller));
  1000. }
  1001. /**
  1002. * test that a requestAction's controller will have the _Token appended to
  1003. * the params.
  1004. *
  1005. * @return void
  1006. * @see https://cakephp.lighthouseapp.com/projects/42648/tickets/68
  1007. */
  1008. public function testSettingTokenForRequestAction() {
  1009. $this->Controller->Security->startup($this->Controller);
  1010. $key = $this->Controller->request->params['_Token']['key'];
  1011. $this->Controller->params['requested'] = 1;
  1012. unset($this->Controller->request->params['_Token']);
  1013. $this->Controller->Security->startup($this->Controller);
  1014. $this->assertEquals($this->Controller->request->params['_Token']['key'], $key);
  1015. }
  1016. /**
  1017. * test that blackhole doesn't delete the _Token session key so repeat data submissions
  1018. * stay blackholed.
  1019. *
  1020. * @link https://cakephp.lighthouseapp.com/projects/42648/tickets/214
  1021. * @return void
  1022. */
  1023. public function testBlackHoleNotDeletingSessionInformation() {
  1024. $this->Controller->Security->startup($this->Controller);
  1025. $this->Controller->Security->blackHole($this->Controller, 'auth');
  1026. $this->assertTrue($this->Controller->Security->Session->check('_Token'), '_Token was deleted by blackHole %s');
  1027. }
  1028. /**
  1029. * test that csrf checks are skipped for request action.
  1030. *
  1031. * @return void
  1032. */
  1033. public function testCsrfSkipRequestAction() {
  1034. $_SERVER['REQUEST_METHOD'] = 'POST';
  1035. $this->Security->validatePost = false;
  1036. $this->Security->csrfCheck = true;
  1037. $this->Security->csrfExpires = '+10 minutes';
  1038. $this->Controller->request->params['requested'] = 1;
  1039. $this->Security->startup($this->Controller);
  1040. $this->assertFalse($this->Controller->failed, 'fail() was called.');
  1041. }
  1042. /**
  1043. * test setting
  1044. *
  1045. * @return void
  1046. */
  1047. public function testCsrfSettings() {
  1048. $this->Security->validatePost = false;
  1049. $this->Security->csrfCheck = true;
  1050. $this->Security->csrfExpires = '+10 minutes';
  1051. $this->Security->startup($this->Controller);
  1052. $token = $this->Security->Session->read('_Token');
  1053. $this->assertEquals(1, count($token['csrfTokens']), 'Missing the csrf token.');
  1054. $this->assertEquals(strtotime('+10 minutes'), current($token['csrfTokens']), 'Token expiry does not match');
  1055. $this->assertEquals(array('key', 'unlockedFields'), array_keys($this->Controller->request->params['_Token']), 'Keys don not match');
  1056. }
  1057. /**
  1058. * Test setting multiple nonces, when startup() is called more than once, (ie more than one request.)
  1059. *
  1060. * @return void
  1061. */
  1062. public function testCsrfSettingMultipleNonces() {
  1063. $this->Security->validatePost = false;
  1064. $this->Security->csrfCheck = true;
  1065. $this->Security->csrfExpires = '+10 minutes';
  1066. $csrfExpires = strtotime('+10 minutes');
  1067. $this->Security->startup($this->Controller);
  1068. $this->Security->startup($this->Controller);
  1069. $token = $this->Security->Session->read('_Token');
  1070. $this->assertEquals(2, count($token['csrfTokens']), 'Missing the csrf token.');
  1071. foreach ($token['csrfTokens'] as $expires) {
  1072. $this->assertWithinMargin($expires, $csrfExpires, 2, 'Token expiry does not match');
  1073. }
  1074. }
  1075. /**
  1076. * test that nonces are consumed by form submits.
  1077. *
  1078. * @return void
  1079. */
  1080. public function testCsrfNonceConsumption() {
  1081. $this->Security->validatePost = false;
  1082. $this->Security->csrfCheck = true;
  1083. $this->Security->csrfExpires = '+10 minutes';
  1084. $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('+10 minutes')));
  1085. $this->Controller->request = $this->getMock('CakeRequest', array('is'));
  1086. $this->Controller->request->expects($this->once())->method('is')
  1087. ->with(array('post', 'put'))
  1088. ->will($this->returnValue(true));
  1089. $this->Controller->request->params['action'] = 'index';
  1090. $this->Controller->request->data = array(
  1091. '_Token' => array(
  1092. 'key' => 'nonce1'
  1093. ),
  1094. 'Post' => array(
  1095. 'title' => 'Woot'
  1096. )
  1097. );
  1098. $this->Security->startup($this->Controller);
  1099. $token = $this->Security->Session->read('_Token');
  1100. $this->assertFalse(isset($token['csrfTokens']['nonce1']), 'Token was not consumed');
  1101. }
  1102. /**
  1103. * tests that reusable CSRF-token expiry is renewed
  1104. */
  1105. public function testCsrfReusableTokenRenewal() {
  1106. $this->Security->validatePost = false;
  1107. $this->Security->csrfCheck = true;
  1108. $this->Security->csrfUseOnce = false;
  1109. $csrfExpires = '+10 minutes';
  1110. $this->Security->csrfExpires = $csrfExpires;
  1111. $this->Security->Session->write('_Token.csrfTokens', array('token' => strtotime('+1 minutes')));
  1112. $this->Security->startup($this->Controller);
  1113. $tokens = $this->Security->Session->read('_Token.csrfTokens');
  1114. $this->assertWithinMargin($tokens['token'], strtotime($csrfExpires), 2, 'Token expiry was not renewed');
  1115. }
  1116. /**
  1117. * test that expired values in the csrfTokens are cleaned up.
  1118. *
  1119. * @return void
  1120. */
  1121. public function testCsrfNonceVacuum() {
  1122. $this->Security->validatePost = false;
  1123. $this->Security->csrfCheck = true;
  1124. $this->Security->csrfExpires = '+10 minutes';
  1125. $this->Security->Session->write('_Token.csrfTokens', array(
  1126. 'valid' => strtotime('+30 minutes'),
  1127. 'poof' => strtotime('-11 minutes'),
  1128. 'dust' => strtotime('-20 minutes')
  1129. ));
  1130. $this->Security->startup($this->Controller);
  1131. $tokens = $this->Security->Session->read('_Token.csrfTokens');
  1132. $this->assertEquals(2, count($tokens), 'Too many tokens left behind');
  1133. $this->assertNotEmpty('valid', $tokens, 'Valid token was removed.');
  1134. }
  1135. /**
  1136. * test that when the key is missing the request is blackHoled
  1137. *
  1138. * @return void
  1139. */
  1140. public function testCsrfBlackHoleOnKeyMismatch() {
  1141. $this->Security->validatePost = false;
  1142. $this->Security->csrfCheck = true;
  1143. $this->Security->csrfExpires = '+10 minutes';
  1144. $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('+10 minutes')));
  1145. $this->Controller->request = $this->getMock('CakeRequest', array('is'));
  1146. $this->Controller->request->expects($this->once())->method('is')
  1147. ->with(array('post', 'put'))
  1148. ->will($this->returnValue(true));
  1149. $this->Controller->request->params['action'] = 'index';
  1150. $this->Controller->request->data = array(
  1151. '_Token' => array(
  1152. 'key' => 'not the right value'
  1153. ),
  1154. 'Post' => array(
  1155. 'title' => 'Woot'
  1156. )
  1157. );
  1158. $this->Security->startup($this->Controller);
  1159. $this->assertTrue($this->Controller->failed, 'fail() was not called.');
  1160. }
  1161. /**
  1162. * test that when the key is missing the request is blackHoled
  1163. *
  1164. * @return void
  1165. */
  1166. public function testCsrfBlackHoleOnExpiredKey() {
  1167. $this->Security->validatePost = false;
  1168. $this->Security->csrfCheck = true;
  1169. $this->Security->csrfExpires = '+10 minutes';
  1170. $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('-5 minutes')));
  1171. $this->Controller->request = $this->getMock('CakeRequest', array('is'));
  1172. $this->Controller->request->expects($this->once())->method('is')
  1173. ->with(array('post', 'put'))
  1174. ->will($this->returnValue(true));
  1175. $this->Controller->request->params['action'] = 'index';
  1176. $this->Controller->request->data = array(
  1177. '_Token' => array(
  1178. 'key' => 'nonce1'
  1179. ),
  1180. 'Post' => array(
  1181. 'title' => 'Woot'
  1182. )
  1183. );
  1184. $this->Security->startup($this->Controller);
  1185. $this->assertTrue($this->Controller->failed, 'fail() was not called.');
  1186. }
  1187. /**
  1188. * test that csrfUseOnce = false works.
  1189. *
  1190. * @return void
  1191. */
  1192. public function testCsrfNotUseOnce() {
  1193. $this->Security->validatePost = false;
  1194. $this->Security->csrfCheck = true;
  1195. $this->Security->csrfUseOnce = false;
  1196. $this->Security->csrfExpires = '+10 minutes';
  1197. // Generate one token
  1198. $this->Security->startup($this->Controller);
  1199. $token = $this->Security->Session->read('_Token.csrfTokens');
  1200. $this->assertEquals(1, count($token), 'Should only be one token.');
  1201. $this->Security->startup($this->Controller);
  1202. $tokenTwo = $this->Security->Session->read('_Token.csrfTokens');
  1203. $this->assertEquals(1, count($tokenTwo), 'Should only be one token.');
  1204. $this->assertEquals($token, $tokenTwo, 'Tokens should not be different.');
  1205. $key = $this->Controller->request->params['_Token']['key'];
  1206. $this->assertEquals(array($key), array_keys($token), '_Token.key and csrfToken do not match request will blackhole.');
  1207. }
  1208. /**
  1209. * ensure that longer session tokens are not consumed
  1210. *
  1211. * @return void
  1212. */
  1213. public function testCsrfNotUseOnceValidationLeavingToken() {
  1214. $this->Security->validatePost = false;
  1215. $this->Security->csrfCheck = true;
  1216. $this->Security->csrfUseOnce = false;
  1217. $this->Security->csrfExpires = '+10 minutes';
  1218. $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('+10 minutes')));
  1219. $this->Controller->request = $this->getMock('CakeRequest', array('is'));
  1220. $this->Controller->request->expects($this->once())->method('is')
  1221. ->with(array('post', 'put'))
  1222. ->will($this->returnValue(true));
  1223. $this->Controller->request->params['action'] = 'index';
  1224. $this->Controller->request->data = array(
  1225. '_Token' => array(
  1226. 'key' => 'nonce1'
  1227. ),
  1228. 'Post' => array(
  1229. 'title' => 'Woot'
  1230. )
  1231. );
  1232. $this->Security->startup($this->Controller);
  1233. $token = $this->Security->Session->read('_Token');
  1234. $this->assertTrue(isset($token['csrfTokens']['nonce1']), 'Token was consumed');
  1235. }
  1236. /**
  1237. * Test generateToken()
  1238. *
  1239. * @return void
  1240. */
  1241. public function testGenerateToken() {
  1242. $request = $this->Controller->request;
  1243. $this->Security->generateToken($request);
  1244. $this->assertNotEmpty($request->params['_Token']);
  1245. $this->assertTrue(isset($request->params['_Token']['unlockedFields']));
  1246. $this->assertTrue(isset($request->params['_Token']['key']));
  1247. }
  1248. /**
  1249. * Test the limiting of CSRF tokens.
  1250. *
  1251. * @return void
  1252. */
  1253. public function testCsrfLimit() {
  1254. $this->Security->csrfLimit = 3;
  1255. $time = strtotime('+10 minutes');
  1256. $tokens = array(
  1257. '1' => $time,
  1258. '2' => $time,
  1259. '3' => $time,
  1260. '4' => $time,
  1261. '5' => $time,
  1262. );
  1263. $this->Security->Session->write('_Token', array('csrfTokens' => $tokens));
  1264. $this->Security->generateToken($this->Controller->request);
  1265. $result = $this->Security->Session->read('_Token.csrfTokens');
  1266. $this->assertFalse(isset($result['1']));
  1267. $this->assertFalse(isset($result['2']));
  1268. $this->assertFalse(isset($result['3']));
  1269. $this->assertTrue(isset($result['4']));
  1270. $this->assertTrue(isset($result['5']));
  1271. }
  1272. /**
  1273. * Test unlocked actions
  1274. *
  1275. * @return void
  1276. */
  1277. public function testUnlockedActions() {
  1278. $_SERVER['REQUEST_METHOD'] = 'POST';
  1279. $this->Controller->request->data = array('data');
  1280. $this->Controller->Security->unlockedActions = 'index';
  1281. $this->Controller->Security->blackHoleCallback = null;
  1282. $result = $this->Controller->Security->startup($this->Controller);
  1283. $this->assertNull($result);
  1284. }
  1285. }