SecurityComponentTest.php 32 KB

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