SecurityComponentTest.php 27 KB

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