PageRenderTime 54ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/TestCase/Controller/Component/SecurityComponentTest.php

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