SecurityComponentTest.php 27 KB

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