SecurityComponentTest.php 30 KB

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