PageRenderTime 67ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

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

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