PageRenderTime 59ms CodeModel.GetById 8ms RepoModel.GetById 1ms app.codeStats 0ms

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

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