PageRenderTime 37ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

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

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