SecurityComponentTest.php 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  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://cakephp.org CakePHP(tm) Project
  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\Controller\Exception\SecurityException;
  19. use Cake\Core\Configure;
  20. use Cake\Event\Event;
  21. use Cake\Network\Request;
  22. use Cake\Network\Session;
  23. use Cake\TestSuite\TestCase;
  24. use Cake\Utility\Security;
  25. /**
  26. * TestSecurityComponent
  27. */
  28. class TestSecurityComponent extends SecurityComponent
  29. {
  30. /**
  31. * validatePost method
  32. *
  33. * @param Controller $controller
  34. * @return bool
  35. */
  36. public function validatePost(Controller $controller)
  37. {
  38. return $this->_validatePost($controller);
  39. }
  40. /**
  41. * authRequired method
  42. *
  43. * @param Controller $controller
  44. * @return bool
  45. */
  46. public function authRequired(Controller $controller)
  47. {
  48. return $this->_authRequired($controller);
  49. }
  50. }
  51. /**
  52. * SecurityTestController
  53. */
  54. class SecurityTestController extends Controller
  55. {
  56. /**
  57. * components property
  58. *
  59. * @var array
  60. */
  61. public $components = [
  62. 'TestSecurity' => ['className' => 'Cake\Test\TestCase\Controller\Component\TestSecurityComponent']
  63. ];
  64. /**
  65. * failed property
  66. *
  67. * @var bool
  68. */
  69. public $failed = false;
  70. /**
  71. * Used for keeping track of headers in test
  72. *
  73. * @var array
  74. */
  75. public $testHeaders = [];
  76. /**
  77. * fail method
  78. *
  79. * @return void
  80. */
  81. public function fail()
  82. {
  83. $this->failed = true;
  84. }
  85. /**
  86. * redirect method
  87. *
  88. * @param string|array $url
  89. * @param mixed $status
  90. * @param mixed $exit
  91. * @return void
  92. */
  93. public function redirect($url, $status = null, $exit = true)
  94. {
  95. return $status;
  96. }
  97. /**
  98. * Convenience method for header()
  99. *
  100. * @param string $status
  101. * @return void
  102. */
  103. public function header($status)
  104. {
  105. $this->testHeaders[] = $status;
  106. }
  107. }
  108. /**
  109. * SecurityComponentTest class
  110. *
  111. * @property SecurityComponent Security
  112. * @property SecurityTestController Controller
  113. */
  114. class SecurityComponentTest extends TestCase
  115. {
  116. /**
  117. * SERVER variable backup.
  118. *
  119. * @var array
  120. */
  121. protected $server = [];
  122. /**
  123. * Controller property
  124. *
  125. * @var SecurityTestController
  126. */
  127. public $Controller;
  128. /**
  129. * oldSalt property
  130. *
  131. * @var string
  132. */
  133. public $oldSalt;
  134. /**
  135. * setUp method
  136. *
  137. * Initializes environment state.
  138. *
  139. * @return void
  140. */
  141. public function setUp()
  142. {
  143. parent::setUp();
  144. $this->server = $_SERVER;
  145. $session = new Session();
  146. $request = $this->getMockBuilder('Cake\Network\Request')
  147. ->setMethods(['here'])
  148. ->setConstructorArgs(['posts/index'])
  149. ->getMock();
  150. $request->addParams(['controller' => 'posts', 'action' => 'index']);
  151. $request->session($session);
  152. $request->expects($this->any())
  153. ->method('here')
  154. ->will($this->returnValue('/articles/index'));
  155. $this->Controller = new SecurityTestController($request);
  156. $this->Controller->Security = $this->Controller->TestSecurity;
  157. $this->Controller->Security->config('blackHoleCallback', 'fail');
  158. $this->Security = $this->Controller->Security;
  159. $this->Security->session = $session;
  160. Security::salt('foo!');
  161. }
  162. /**
  163. * tearDown method
  164. *
  165. * Resets environment state.
  166. *
  167. * @return void
  168. */
  169. public function tearDown()
  170. {
  171. parent::tearDown();
  172. $_SERVER = $this->server;
  173. $this->Security->session->delete('_Token');
  174. unset($this->Controller->Security);
  175. unset($this->Controller->Component);
  176. unset($this->Controller);
  177. }
  178. public function validatePost($expectedException = null, $expectedExceptionMessage = null)
  179. {
  180. try {
  181. return $this->Controller->Security->validatePost($this->Controller);
  182. } catch (SecurityException $ex) {
  183. $this->assertInstanceOf('Cake\\Controller\\Exception\\' . $expectedException, $ex);
  184. $this->assertEquals($expectedExceptionMessage, $ex->getMessage());
  185. return false;
  186. }
  187. }
  188. /**
  189. * testBlackholeWithBrokenCallback method
  190. *
  191. * Test that requests are still blackholed when controller has incorrect
  192. * visibility keyword in the blackhole callback.
  193. *
  194. * @expectedException \Cake\Network\Exception\BadRequestException
  195. * @return void
  196. * @triggers Controller.startup $Controller, $this->Controller
  197. */
  198. public function testBlackholeWithBrokenCallback()
  199. {
  200. $request = new Request([
  201. 'url' => 'posts/index',
  202. 'session' => $this->Security->session
  203. ]);
  204. $request->addParams([
  205. 'controller' => 'posts',
  206. 'action' => 'index'
  207. ]);
  208. $Controller = new \TestApp\Controller\SomePagesController($request);
  209. $event = new Event('Controller.startup', $Controller);
  210. $Security = new SecurityComponent($Controller->components());
  211. $Security->config('blackHoleCallback', '_fail');
  212. $Security->startup($event);
  213. $Security->blackHole($Controller, 'csrf');
  214. }
  215. /**
  216. * testExceptionWhenActionIsBlackholeCallback method
  217. *
  218. * Ensure that directly requesting the blackholeCallback as the controller
  219. * action results in an exception.
  220. *
  221. * @return void
  222. * @triggers Controller.startup $this->Controller
  223. */
  224. public function testExceptionWhenActionIsBlackholeCallback()
  225. {
  226. $this->Controller->request->addParams([
  227. 'controller' => 'posts',
  228. 'action' => 'fail'
  229. ]);
  230. $event = new Event('Controller.startup', $this->Controller);
  231. $this->assertFalse($this->Controller->failed);
  232. $this->Controller->Security->startup($event);
  233. $this->assertTrue($this->Controller->failed, 'Request was blackholed.');
  234. }
  235. /**
  236. * testConstructorSettingProperties method
  237. *
  238. * Test that initialize can set properties.
  239. *
  240. * @return void
  241. */
  242. public function testConstructorSettingProperties()
  243. {
  244. $settings = [
  245. 'requireSecure' => ['update_account'],
  246. 'validatePost' => false,
  247. ];
  248. $Security = new SecurityComponent($this->Controller->components(), $settings);
  249. $this->assertEquals($Security->validatePost, $settings['validatePost']);
  250. }
  251. /**
  252. * testStartup method
  253. *
  254. * @return void
  255. * @triggers Controller.startup $this->Controller
  256. */
  257. public function testStartup()
  258. {
  259. $event = new Event('Controller.startup', $this->Controller);
  260. $this->Controller->Security->startup($event);
  261. $this->assertTrue($this->Security->session->check('_Token'));
  262. }
  263. /**
  264. * testRequireSecureFail method
  265. *
  266. * @return void
  267. * @triggers Controller.startup $this->Controller
  268. */
  269. public function testRequireSecureFail()
  270. {
  271. $_SERVER['HTTPS'] = 'off';
  272. $_SERVER['REQUEST_METHOD'] = 'POST';
  273. $this->Controller->request['action'] = 'posted';
  274. $event = new Event('Controller.startup', $this->Controller);
  275. $this->Controller->Security->requireSecure(['posted']);
  276. $this->Controller->Security->startup($event);
  277. $this->assertTrue($this->Controller->failed);
  278. }
  279. /**
  280. * testRequireSecureSucceed method
  281. *
  282. * @return void
  283. * @triggers Controller.startup $this->Controller
  284. */
  285. public function testRequireSecureSucceed()
  286. {
  287. $_SERVER['HTTPS'] = 'on';
  288. $_SERVER['REQUEST_METHOD'] = 'Secure';
  289. $this->Controller->request['action'] = 'posted';
  290. $event = new Event('Controller.startup', $this->Controller);
  291. $this->Controller->Security->requireSecure('posted');
  292. $this->Controller->Security->startup($event);
  293. $this->assertFalse($this->Controller->failed);
  294. }
  295. /**
  296. * testRequireSecureEmptyFail method
  297. *
  298. * @return void
  299. * @triggers Controller.startup $this->Controller
  300. */
  301. public function testRequireSecureEmptyFail()
  302. {
  303. $_SERVER['HTTPS'] = 'off';
  304. $_SERVER['REQUEST_METHOD'] = 'POST';
  305. $this->Controller->request['action'] = 'posted';
  306. $event = new Event('Controller.startup', $this->Controller);
  307. $this->Controller->Security->requireSecure();
  308. $this->Controller->Security->startup($event);
  309. $this->assertTrue($this->Controller->failed);
  310. }
  311. /**
  312. * testRequireSecureEmptySucceed method
  313. *
  314. * @return void
  315. * @triggers Controller.startup $this->Controller
  316. */
  317. public function testRequireSecureEmptySucceed()
  318. {
  319. $_SERVER['HTTPS'] = 'on';
  320. $_SERVER['REQUEST_METHOD'] = 'Secure';
  321. $this->Controller->request['action'] = 'posted';
  322. $event = new Event('Controller.startup', $this->Controller);
  323. $this->Controller->Security->requireSecure();
  324. $this->Controller->Security->startup($event);
  325. $this->assertFalse($this->Controller->failed);
  326. }
  327. /**
  328. * testRequireAuthFail method
  329. *
  330. * @return void
  331. * @triggers Controller.startup $this->Controller
  332. */
  333. public function testRequireAuthFail()
  334. {
  335. $event = new Event('Controller.startup', $this->Controller);
  336. $_SERVER['REQUEST_METHOD'] = 'AUTH';
  337. $this->Controller->request['action'] = 'posted';
  338. $this->Controller->request->data = ['username' => 'willy', 'password' => 'somePass'];
  339. $this->Security->requireAuth(['posted']);
  340. $this->Security->startup($event);
  341. $this->assertTrue($this->Controller->failed);
  342. $this->Security->session->write('_Token', ['allowedControllers' => []]);
  343. $this->Controller->request->data = ['username' => 'willy', 'password' => 'somePass'];
  344. $this->Controller->request['action'] = 'posted';
  345. $this->Security->requireAuth('posted');
  346. $this->Security->startup($event);
  347. $this->assertTrue($this->Controller->failed);
  348. $this->Security->session->write('_Token', [
  349. 'allowedControllers' => ['SecurityTest'], 'allowedActions' => ['posted2']
  350. ]);
  351. $this->Controller->request->data = ['username' => 'willy', 'password' => 'somePass'];
  352. $this->Controller->request['action'] = 'posted';
  353. $this->Security->requireAuth('posted');
  354. $this->Security->startup($event);
  355. $this->assertTrue($this->Controller->failed);
  356. }
  357. /**
  358. * testRequireAuthSucceed method
  359. *
  360. * @return void
  361. * @triggers Controller.startup $this->Controller
  362. */
  363. public function testRequireAuthSucceed()
  364. {
  365. $_SERVER['REQUEST_METHOD'] = 'AUTH';
  366. $this->Controller->Security->config('validatePost', false);
  367. $event = new Event('Controller.startup', $this->Controller);
  368. $this->Controller->request->addParams([
  369. 'action' => 'posted'
  370. ]);
  371. $this->Security->requireAuth('posted');
  372. $this->Security->startup($event);
  373. $this->assertFalse($this->Controller->failed);
  374. $this->Controller->Security->session->write('_Token', [
  375. 'allowedControllers' => ['SecurityTest'],
  376. 'allowedActions' => ['posted'],
  377. ]);
  378. $this->Controller->request->addParams([
  379. 'controller' => 'SecurityTest',
  380. 'action' => 'posted'
  381. ]);
  382. $this->Controller->request->data = [
  383. 'username' => 'willy',
  384. 'password' => 'somePass',
  385. '_Token' => ''
  386. ];
  387. $this->Controller->action = 'posted';
  388. $this->Controller->Security->requireAuth('posted');
  389. $this->Controller->Security->startup($event);
  390. $this->assertFalse($this->Controller->failed);
  391. }
  392. /**
  393. * testValidatePost method
  394. *
  395. * Simple hash validation test
  396. *
  397. * @return void
  398. * @triggers Controller.startup $this->Controller
  399. */
  400. public function testValidatePost()
  401. {
  402. $event = new Event('Controller.startup', $this->Controller);
  403. $this->Security->startup($event);
  404. $fields = '68730b0747d4889ec2766f9117405f9635f5fd5e%3AModel.valid';
  405. $unlocked = '';
  406. $debug = '';
  407. $this->Controller->request->data = [
  408. 'Model' => ['username' => 'nate', 'password' => 'foo', 'valid' => '0'],
  409. '_Token' => compact('fields', 'unlocked', 'debug'),
  410. ];
  411. $this->assertTrue($this->validatePost());
  412. }
  413. /**
  414. * testValidatePostOnGetWithData method
  415. *
  416. * Test that validatePost fires on GET with request data.
  417. * This could happen when method overriding is used.
  418. *
  419. * @return void
  420. * @triggers Controller.startup $this->Controller
  421. */
  422. public function testValidatePostOnGetWithData()
  423. {
  424. $event = new Event('Controller.startup', $this->Controller);
  425. $this->Security->startup($event);
  426. $fields = 'an-invalid-token';
  427. $unlocked = '';
  428. $debug = urlencode(json_encode([
  429. 'some-action',
  430. [],
  431. []
  432. ]));
  433. $this->Controller->request->env('REQUEST_METHOD', 'GET');
  434. $this->Controller->request->data = [
  435. 'Model' => ['username' => 'nate', 'password' => 'foo', 'valid' => '0'],
  436. '_Token' => compact('fields', 'unlocked', 'debug')
  437. ];
  438. $this->Security->startup($event);
  439. $this->assertTrue($this->Controller->failed);
  440. }
  441. /**
  442. * testValidatePostNoSession method
  443. *
  444. * Test that validatePost fails if you are missing the session information.
  445. *
  446. * @return void
  447. * @triggers Controller.startup $this->Controller
  448. */
  449. public function testValidatePostNoSession()
  450. {
  451. $event = new Event('Controller.startup', $this->Controller);
  452. $this->Security->startup($event);
  453. $this->Security->session->delete('_Token');
  454. $unlocked = '';
  455. $debug = urlencode(json_encode([
  456. '/articles/index',
  457. [],
  458. []
  459. ]));
  460. $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877%3AModel.valid';
  461. $this->Controller->request->data = [
  462. 'Model' => ['username' => 'nate', 'password' => 'foo', 'valid' => '0'],
  463. '_Token' => compact('fields', 'unlocked', 'debug')
  464. ];
  465. $this->assertFalse($this->validatePost('AuthSecurityException', 'Unexpected field \'Model.password\' in POST data, Unexpected field \'Model.username\' in POST data'));
  466. }
  467. /**
  468. * testValidatePostNoUnlockedInRequestData method
  469. *
  470. * Test that validatePost fails if you are missing unlocked in request data.
  471. *
  472. * @return void
  473. * @triggers Controller.startup $this->Controller
  474. */
  475. public function testValidatePostNoUnlockedInRequestData()
  476. {
  477. $event = new Event('Controller.startup', $this->Controller);
  478. $this->Security->startup($event);
  479. $this->Security->session->delete('_Token');
  480. $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877%3AModel.valid';
  481. $this->Controller->request->data = [
  482. 'Model' => ['username' => 'nate', 'password' => 'foo', 'valid' => '0'],
  483. '_Token' => compact('fields')
  484. ];
  485. $this->assertFalse($this->validatePost('AuthSecurityException', '\'_Token.unlocked\' was not found in request data.'));
  486. }
  487. /**
  488. * testValidatePostFormHacking method
  489. *
  490. * Test that validatePost fails if any of its required fields are missing.
  491. *
  492. * @return void
  493. * @triggers Controller.startup $this->Controller
  494. */
  495. public function testValidatePostFormHacking()
  496. {
  497. $event = new Event('Controller.startup', $this->Controller);
  498. $this->Security->startup($event);
  499. $unlocked = '';
  500. $this->Controller->request->data = [
  501. 'Model' => ['username' => 'nate', 'password' => 'foo', 'valid' => '0'],
  502. '_Token' => compact('unlocked')
  503. ];
  504. $result = $this->validatePost('AuthSecurityException', '\'_Token.fields\' was not found in request data.');
  505. $this->assertFalse($result, 'validatePost passed when fields were missing. %s');
  506. }
  507. /**
  508. * testValidatePostObjectDeserialize
  509. *
  510. * Test that objects can't be passed into the serialized string. This was a vector for RFI and LFI
  511. * attacks. Thanks to Felix Wilhelm
  512. *
  513. * @return void
  514. * @triggers Controller.startup $this->Controller
  515. */
  516. public function testValidatePostObjectDeserialize()
  517. {
  518. $event = new Event('Controller.startup', $this->Controller);
  519. $this->Security->startup($event);
  520. $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877';
  521. $unlocked = '';
  522. $debug = urlencode(json_encode([
  523. '/articles/index',
  524. ['Model.password', 'Model.username', 'Model.valid'],
  525. []
  526. ]));
  527. // a corrupted serialized object, so we can see if it ever gets to deserialize
  528. $attack = 'O:3:"App":1:{s:5:"__map";a:1:{s:3:"foo";s:7:"Hacked!";s:1:"fail"}}';
  529. $fields .= urlencode(':' . str_rot13($attack));
  530. $this->Controller->request->data = [
  531. 'Model' => ['username' => 'mark', 'password' => 'foo', 'valid' => '0'],
  532. '_Token' => compact('fields', 'unlocked', 'debug')
  533. ];
  534. $result = $this->validatePost('SecurityException', 'Bad Request');
  535. $this->assertFalse($result, 'validatePost passed when key was missing. %s');
  536. }
  537. /**
  538. * testValidatePostIgnoresCsrfToken method
  539. *
  540. * Tests validation post data ignores `_csrfToken`.
  541. *
  542. * @return void
  543. * @triggers Controller.startup $this->Controller
  544. */
  545. public function testValidatePostIgnoresCsrfToken()
  546. {
  547. $event = new Event('Controller.startup', $this->Controller);
  548. $this->Security->startup($event);
  549. $fields = '8e26ef05379e5402c2c619f37ee91152333a0264%3A';
  550. $unlocked = '';
  551. $debug = 'not used';
  552. $this->Controller->request->data = [
  553. '_csrfToken' => 'abc123',
  554. 'Model' => ['multi_field' => ['1', '3']],
  555. '_Token' => compact('fields', 'unlocked', 'debug')
  556. ];
  557. $this->assertTrue($this->validatePost());
  558. }
  559. /**
  560. * testValidatePostArray method
  561. *
  562. * Tests validation of checkbox arrays.
  563. *
  564. * @return void
  565. * @triggers Controller.startup $this->Controller
  566. */
  567. public function testValidatePostArray()
  568. {
  569. $event = new Event('Controller.startup', $this->Controller);
  570. $this->Security->startup($event);
  571. $fields = '8e26ef05379e5402c2c619f37ee91152333a0264%3A';
  572. $unlocked = '';
  573. $debug = urlencode(json_encode([
  574. 'some-action',
  575. [],
  576. []
  577. ]));
  578. $this->Controller->request->data = [
  579. 'Model' => ['multi_field' => ['1', '3']],
  580. '_Token' => compact('fields', 'unlocked', 'debug')
  581. ];
  582. $this->assertTrue($this->validatePost());
  583. $this->Controller->request->data = [
  584. 'Model' => ['multi_field' => [12 => '1', 20 => '3']],
  585. '_Token' => compact('fields', 'unlocked', 'debug')
  586. ];
  587. $this->assertTrue($this->validatePost());
  588. }
  589. /**
  590. * testValidateIntFieldName method
  591. *
  592. * Tests validation of integer field names.
  593. *
  594. * @return void
  595. */
  596. public function testValidateIntFieldName()
  597. {
  598. $event = new Event('Controller.startup', $this->Controller);
  599. $this->Security->startup($event);
  600. $fields = '4a221010dd7a23f7166cb10c38bc21d81341c387%3A';
  601. $unlocked = '';
  602. $debug = urlencode(json_encode([
  603. 'some-action',
  604. [],
  605. []
  606. ]));
  607. $this->Controller->request->data = [
  608. 1 => 'value,',
  609. '_Token' => compact('fields', 'unlocked', 'debug')
  610. ];
  611. $this->assertTrue($this->validatePost());
  612. }
  613. /**
  614. * testValidatePostNoModel method
  615. *
  616. * @return void
  617. * @triggers Controller.startup $this->Controller
  618. */
  619. public function testValidatePostNoModel()
  620. {
  621. $event = new Event('Controller.startup', $this->Controller);
  622. $this->Security->startup($event);
  623. $fields = 'a1c3724b7ba85e7022413611e30ba2c6181d5aba%3A';
  624. $unlocked = '';
  625. $debug = 'not used';
  626. $this->Controller->request->data = [
  627. 'anything' => 'some_data',
  628. '_Token' => compact('fields', 'unlocked', 'debug')
  629. ];
  630. $result = $this->validatePost();
  631. $this->assertTrue($result);
  632. }
  633. /**
  634. * testValidatePostSimple method
  635. *
  636. * @return void
  637. * @triggers Controller.startup $this->Controller
  638. */
  639. public function testValidatePostSimple()
  640. {
  641. $event = new Event('Controller.startup', $this->Controller);
  642. $this->Security->startup($event);
  643. $fields = 'b0914d06dfb04abf1fada53e16810e87d157950b%3A';
  644. $unlocked = '';
  645. $debug = 'not used';
  646. $this->Controller->request->data = [
  647. 'Model' => ['username' => '', 'password' => ''],
  648. '_Token' => compact('fields', 'unlocked', 'debug')
  649. ];
  650. $result = $this->validatePost();
  651. $this->assertTrue($result);
  652. }
  653. /**
  654. * testValidatePostComplex method
  655. *
  656. * Tests hash validation for multiple records, including locked fields.
  657. *
  658. * @return void
  659. * @triggers Controller.startup $this->Controller
  660. */
  661. public function testValidatePostComplex()
  662. {
  663. $event = new Event('Controller.startup', $this->Controller);
  664. $this->Security->startup($event);
  665. $fields = 'b65c7463e44a61d8d2eaecce2c265b406c9c4742%3AAddresses.0.id%7CAddresses.1.id';
  666. $unlocked = '';
  667. $debug = 'not used';
  668. $this->Controller->request->data = [
  669. 'Addresses' => [
  670. '0' => [
  671. 'id' => '123456', 'title' => '', 'first_name' => '', 'last_name' => '',
  672. 'address' => '', 'city' => '', 'phone' => '', 'primary' => ''
  673. ],
  674. '1' => [
  675. 'id' => '654321', 'title' => '', 'first_name' => '', 'last_name' => '',
  676. 'address' => '', 'city' => '', 'phone' => '', 'primary' => ''
  677. ]
  678. ],
  679. '_Token' => compact('fields', 'unlocked', 'debug')
  680. ];
  681. $result = $this->validatePost();
  682. $this->assertTrue($result);
  683. }
  684. /**
  685. * testValidatePostMultipleSelect method
  686. *
  687. * Test ValidatePost with multiple select elements.
  688. *
  689. * @return void
  690. * @triggers Controller.startup $this->Controller
  691. */
  692. public function testValidatePostMultipleSelect()
  693. {
  694. $event = new Event('Controller.startup', $this->Controller);
  695. $this->Security->startup($event);
  696. $fields = '8d8da68ba03b3d6e7e145b948abfe26741422169%3A';
  697. $unlocked = '';
  698. $debug = 'not used';
  699. $this->Controller->request->data = [
  700. 'Tag' => ['Tag' => [1, 2]],
  701. '_Token' => compact('fields', 'unlocked', 'debug'),
  702. ];
  703. $result = $this->validatePost();
  704. $this->assertTrue($result);
  705. $this->Controller->request->data = [
  706. 'Tag' => ['Tag' => [1, 2, 3]],
  707. '_Token' => compact('fields', 'unlocked', 'debug'),
  708. ];
  709. $result = $this->validatePost();
  710. $this->assertTrue($result);
  711. $this->Controller->request->data = [
  712. 'Tag' => ['Tag' => [1, 2, 3, 4]],
  713. '_Token' => compact('fields', 'unlocked', 'debug'),
  714. ];
  715. $result = $this->validatePost();
  716. $this->assertTrue($result);
  717. $fields = 'eae2adda1628b771a30cc133342d16220c6520fe%3A';
  718. $this->Controller->request->data = [
  719. 'User.password' => 'bar', 'User.name' => 'foo', 'User.is_valid' => '1',
  720. 'Tag' => ['Tag' => [1]],
  721. '_Token' => compact('fields', 'unlocked', 'debug'),
  722. ];
  723. $result = $this->validatePost();
  724. $this->assertTrue($result);
  725. }
  726. /**
  727. * testValidatePostCheckbox method
  728. *
  729. * First block tests un-checked checkbox
  730. * Second block tests checked checkbox
  731. *
  732. * @return void
  733. * @triggers Controller.startup $this->Controller
  734. */
  735. public function testValidatePostCheckbox()
  736. {
  737. $event = new Event('Controller.startup', $this->Controller);
  738. $this->Security->startup($event);
  739. $fields = '68730b0747d4889ec2766f9117405f9635f5fd5e%3AModel.valid';
  740. $unlocked = '';
  741. $debug = 'not used';
  742. $this->Controller->request->data = [
  743. 'Model' => ['username' => '', 'password' => '', 'valid' => '0'],
  744. '_Token' => compact('fields', 'unlocked', 'debug'),
  745. ];
  746. $result = $this->validatePost();
  747. $this->assertTrue($result);
  748. $fields = 'f63e4a69b2edd31f064e8e602a04dd59307cfe9c%3A';
  749. $this->Controller->request->data = [
  750. 'Model' => ['username' => '', 'password' => '', 'valid' => '0'],
  751. '_Token' => compact('fields', 'unlocked', 'debug'),
  752. ];
  753. $result = $this->validatePost();
  754. $this->assertTrue($result);
  755. $this->Controller->request->data = [];
  756. $this->Security->startup($event);
  757. $this->Controller->request->data = [
  758. 'Model' => ['username' => '', 'password' => '', 'valid' => '0'],
  759. '_Token' => compact('fields', 'unlocked', 'debug'),
  760. ];
  761. $result = $this->validatePost();
  762. $this->assertTrue($result);
  763. }
  764. /**
  765. * testValidatePostHidden method
  766. *
  767. * @return void
  768. * @triggers Controller.startup $this->Controller
  769. */
  770. public function testValidatePostHidden()
  771. {
  772. $event = new Event('Controller.startup', $this->Controller);
  773. $this->Security->startup($event);
  774. $fields = '973a8939a68ac014cc6f7666cec9aa6268507350%3AModel.hidden%7CModel.other_hidden';
  775. $unlocked = '';
  776. $debug = 'not used';
  777. $this->Controller->request->data = [
  778. 'Model' => [
  779. 'username' => '', 'password' => '', 'hidden' => '0',
  780. 'other_hidden' => 'some hidden value'
  781. ],
  782. '_Token' => compact('fields', 'unlocked', 'debug'),
  783. ];
  784. $result = $this->validatePost();
  785. $this->assertTrue($result);
  786. }
  787. /**
  788. * testValidatePostWithDisabledFields method
  789. *
  790. * @return void
  791. * @triggers Controller.startup $this->Controller
  792. */
  793. public function testValidatePostWithDisabledFields()
  794. {
  795. $event = new Event('Controller.startup', $this->Controller);
  796. $this->Security->config('disabledFields', ['Model.username', 'Model.password']);
  797. $this->Security->startup($event);
  798. $fields = '1c59acfbca98bd870c11fb544d545cbf23215880%3AModel.hidden';
  799. $unlocked = '';
  800. $debug = 'not used';
  801. $this->Controller->request->data = [
  802. 'Model' => [
  803. 'username' => '', 'password' => '', 'hidden' => '0'
  804. ],
  805. '_Token' => compact('fields', 'unlocked', 'debug'),
  806. ];
  807. $result = $this->validatePost();
  808. $this->assertTrue($result);
  809. }
  810. /**
  811. * testValidatePostDisabledFieldsInData method
  812. *
  813. * Test validating post data with posted unlocked fields.
  814. *
  815. * @return void
  816. * @triggers Controller.startup $this->Controller
  817. */
  818. public function testValidatePostDisabledFieldsInData()
  819. {
  820. $event = new Event('Controller.startup', $this->Controller);
  821. $this->Security->startup($event);
  822. $unlocked = 'Model.username';
  823. $fields = ['Model.hidden', 'Model.password'];
  824. $fields = urlencode(Security::hash('/articles/index' . serialize($fields) . $unlocked . Security::salt()));
  825. $debug = 'not used';
  826. $this->Controller->request->data = [
  827. 'Model' => [
  828. 'username' => 'mark',
  829. 'password' => 'sekret',
  830. 'hidden' => '0'
  831. ],
  832. '_Token' => compact('fields', 'unlocked', 'debug'),
  833. ];
  834. $result = $this->validatePost();
  835. $this->assertTrue($result);
  836. }
  837. /**
  838. * testValidatePostFailNoDisabled method
  839. *
  840. * Test that missing 'unlocked' input causes failure.
  841. *
  842. * @return void
  843. * @triggers Controller.startup $this->Controller
  844. */
  845. public function testValidatePostFailNoDisabled()
  846. {
  847. $event = new Event('Controller.startup', $this->Controller);
  848. $this->Security->startup($event);
  849. $fields = ['Model.hidden', 'Model.password', 'Model.username'];
  850. $fields = urlencode(Security::hash(serialize($fields) . Security::salt()));
  851. $this->Controller->request->data = [
  852. 'Model' => [
  853. 'username' => 'mark',
  854. 'password' => 'sekret',
  855. 'hidden' => '0'
  856. ],
  857. '_Token' => compact('fields')
  858. ];
  859. $result = $this->validatePost('SecurityException', '\'_Token.unlocked\' was not found in request data.');
  860. $this->assertFalse($result);
  861. }
  862. /**
  863. * testValidatePostFailNoDebug method
  864. *
  865. * Test that missing 'debug' input causes failure.
  866. *
  867. * @return void
  868. * @triggers Controller.startup $this->Controller
  869. */
  870. public function testValidatePostFailNoDebug()
  871. {
  872. $event = new Event('Controller.startup', $this->Controller);
  873. $this->Security->startup($event);
  874. $fields = ['Model.hidden', 'Model.password', 'Model.username'];
  875. $fields = urlencode(Security::hash(serialize($fields) . Security::salt()));
  876. $unlocked = '';
  877. $this->Controller->request->data = [
  878. 'Model' => [
  879. 'username' => 'mark',
  880. 'password' => 'sekret',
  881. 'hidden' => '0'
  882. ],
  883. '_Token' => compact('fields', 'unlocked')
  884. ];
  885. $result = $this->validatePost('SecurityException', '\'_Token.debug\' was not found in request data.');
  886. $this->assertFalse($result);
  887. }
  888. /**
  889. * testValidatePostFailNoDebugMode method
  890. *
  891. * Test that missing 'debug' input is not the problem when debug mode disabled.
  892. *
  893. * @return void
  894. * @triggers Controller.startup $this->Controller
  895. */
  896. public function testValidatePostFailNoDebugMode()
  897. {
  898. $event = new Event('Controller.startup', $this->Controller);
  899. $this->Security->startup($event);
  900. $fields = ['Model.hidden', 'Model.password', 'Model.username'];
  901. $fields = urlencode(Security::hash(serialize($fields) . Security::salt()));
  902. $unlocked = '';
  903. $this->Controller->request->data = [
  904. 'Model' => [
  905. 'username' => 'mark',
  906. 'password' => 'sekret',
  907. 'hidden' => '0'
  908. ],
  909. '_Token' => compact('fields', 'unlocked')
  910. ];
  911. Configure::write('debug', false);
  912. $result = $this->validatePost('SecurityException', 'The request has been black-holed');
  913. }
  914. /**
  915. * testValidatePostFailDisabledFieldTampering method
  916. *
  917. * Test that validatePost fails when unlocked fields are changed.
  918. *
  919. * @return void
  920. * @triggers Controller.startup $this->Controller
  921. */
  922. public function testValidatePostFailDisabledFieldTampering()
  923. {
  924. $event = new Event('Controller.startup', $this->Controller);
  925. $this->Security->startup($event);
  926. $unlocked = 'Model.username';
  927. $fields = ['Model.hidden', 'Model.password'];
  928. $fields = urlencode(Security::hash(serialize($fields) . $unlocked . Security::salt()));
  929. $debug = urlencode(json_encode([
  930. '/articles/index',
  931. ['Model.hidden', 'Model.password'],
  932. ['Model.username']
  933. ]));
  934. // Tamper the values.
  935. $unlocked = 'Model.username|Model.password';
  936. $this->Controller->request->data = [
  937. 'Model' => [
  938. 'username' => 'mark',
  939. 'password' => 'sekret',
  940. 'hidden' => '0'
  941. ],
  942. '_Token' => compact('fields', 'unlocked', 'debug')
  943. ];
  944. $result = $this->validatePost('SecurityException', 'Missing field \'Model.password\' in POST data, Unexpected unlocked field \'Model.password\' in POST data');
  945. $this->assertFalse($result);
  946. }
  947. /**
  948. * testValidateHiddenMultipleModel method
  949. *
  950. * @return void
  951. * @triggers Controller.startup $this->Controller
  952. */
  953. public function testValidateHiddenMultipleModel()
  954. {
  955. $event = new Event('Controller.startup', $this->Controller);
  956. $this->Security->startup($event);
  957. $fields = '075ca6c26c38a09a78d871201df89faf52cbbeb8%3AModel.valid%7CModel2.valid%7CModel3.valid';
  958. $unlocked = '';
  959. $debug = 'not used';
  960. $this->Controller->request->data = [
  961. 'Model' => ['username' => '', 'password' => '', 'valid' => '0'],
  962. 'Model2' => ['valid' => '0'],
  963. 'Model3' => ['valid' => '0'],
  964. '_Token' => compact('fields', 'unlocked', 'debug'),
  965. ];
  966. $result = $this->validatePost();
  967. $this->assertTrue($result);
  968. }
  969. /**
  970. * testValidateHasManyModel method
  971. *
  972. * @return void
  973. * @triggers Controller.startup $this->Controller
  974. */
  975. public function testValidateHasManyModel()
  976. {
  977. $event = new Event('Controller.startup', $this->Controller);
  978. $this->Security->startup($event);
  979. $fields = '24a753fb62ef7839389987b58e3f7108f564e529%3AModel.0.hidden%7CModel.0.valid';
  980. $fields .= '%7CModel.1.hidden%7CModel.1.valid';
  981. $unlocked = '';
  982. $debug = 'not used';
  983. $this->Controller->request->data = [
  984. 'Model' => [
  985. [
  986. 'username' => 'username', 'password' => 'password',
  987. 'hidden' => 'value', 'valid' => '0'
  988. ],
  989. [
  990. 'username' => 'username', 'password' => 'password',
  991. 'hidden' => 'value', 'valid' => '0'
  992. ]
  993. ],
  994. '_Token' => compact('fields', 'unlocked', 'debug'),
  995. ];
  996. $result = $this->validatePost();
  997. $this->assertTrue($result);
  998. }
  999. /**
  1000. * testValidateHasManyRecordsPass method
  1001. *
  1002. * @return void
  1003. * @triggers Controller.startup $this->Controller
  1004. */
  1005. public function testValidateHasManyRecordsPass()
  1006. {
  1007. $event = new Event('Controller.startup', $this->Controller);
  1008. $this->Security->startup($event);
  1009. $fields = '8f7d82bf7656cf068822d9bdab109ebed1be1825%3AAddress.0.id%7CAddress.0.primary%7C';
  1010. $fields .= 'Address.1.id%7CAddress.1.primary';
  1011. $unlocked = '';
  1012. $debug = 'not used';
  1013. $this->Controller->request->data = [
  1014. 'Address' => [
  1015. 0 => [
  1016. 'id' => '123',
  1017. 'title' => 'home',
  1018. 'first_name' => 'Bilbo',
  1019. 'last_name' => 'Baggins',
  1020. 'address' => '23 Bag end way',
  1021. 'city' => 'the shire',
  1022. 'phone' => 'N/A',
  1023. 'primary' => '1',
  1024. ],
  1025. 1 => [
  1026. 'id' => '124',
  1027. 'title' => 'home',
  1028. 'first_name' => 'Frodo',
  1029. 'last_name' => 'Baggins',
  1030. 'address' => '50 Bag end way',
  1031. 'city' => 'the shire',
  1032. 'phone' => 'N/A',
  1033. 'primary' => '1'
  1034. ]
  1035. ],
  1036. '_Token' => compact('fields', 'unlocked', 'debug'),
  1037. ];
  1038. $result = $this->validatePost();
  1039. $this->assertTrue($result);
  1040. }
  1041. /**
  1042. * testValidateNestedNumericSets method
  1043. *
  1044. * Test that values like Foo.0.1
  1045. *
  1046. * @return void
  1047. * @triggers Controller.startup $this->Controller
  1048. */
  1049. public function testValidateNestedNumericSets()
  1050. {
  1051. $event = new Event('Controller.startup', $this->Controller);
  1052. $this->Security->startup($event);
  1053. $unlocked = '';
  1054. $hashFields = ['TaxonomyData'];
  1055. $fields = urlencode(Security::hash('/articles/index' . serialize($hashFields) . $unlocked . Security::salt()));
  1056. $debug = 'not used';
  1057. $this->Controller->request->data = [
  1058. 'TaxonomyData' => [
  1059. 1 => [[2]],
  1060. 2 => [[3]]
  1061. ],
  1062. '_Token' => compact('fields', 'unlocked', 'debug'),
  1063. ];
  1064. $result = $this->validatePost();
  1065. $this->assertTrue($result);
  1066. }
  1067. /**
  1068. * testValidateHasManyRecords method
  1069. *
  1070. * validatePost should fail, hidden fields have been changed.
  1071. *
  1072. * @return void
  1073. * @triggers Controller.startup $this->Controller
  1074. */
  1075. public function testValidateHasManyRecordsFail()
  1076. {
  1077. $event = new Event('Controller.startup', $this->Controller);
  1078. $this->Security->startup($event);
  1079. $fields = '7a203edb3d345bbf38fe0dccae960da8842e11d7%3AAddress.0.id%7CAddress.0.primary%7C';
  1080. $fields .= 'Address.1.id%7CAddress.1.primary';
  1081. $unlocked = '';
  1082. $debug = urlencode(json_encode([
  1083. '/articles/index',
  1084. [
  1085. 'Address.0.address',
  1086. 'Address.0.city',
  1087. 'Address.0.first_name',
  1088. 'Address.0.last_name',
  1089. 'Address.0.phone',
  1090. 'Address.0.title',
  1091. 'Address.1.address',
  1092. 'Address.1.city',
  1093. 'Address.1.first_name',
  1094. 'Address.1.last_name',
  1095. 'Address.1.phone',
  1096. 'Address.1.title',
  1097. 'Address.0.id' => '123',
  1098. 'Address.0.primary' => '5',
  1099. 'Address.1.id' => '124',
  1100. 'Address.1.primary' => '1'
  1101. ],
  1102. []
  1103. ]));
  1104. $this->Controller->request->data = [
  1105. 'Address' => [
  1106. 0 => [
  1107. 'id' => '123',
  1108. 'title' => 'home',
  1109. 'first_name' => 'Bilbo',
  1110. 'last_name' => 'Baggins',
  1111. 'address' => '23 Bag end way',
  1112. 'city' => 'the shire',
  1113. 'phone' => 'N/A',
  1114. 'primary' => '5',
  1115. ],
  1116. 1 => [
  1117. 'id' => '124',
  1118. 'title' => 'home',
  1119. 'first_name' => 'Frodo',
  1120. 'last_name' => 'Baggins',
  1121. 'address' => '50 Bag end way',
  1122. 'city' => 'the shire',
  1123. 'phone' => 'N/A',
  1124. 'primary' => '1'
  1125. ]
  1126. ],
  1127. '_Token' => compact('fields', 'unlocked', 'debug'),
  1128. ];
  1129. $result = $this->validatePost('SecurityException', 'Bad Request');
  1130. $this->assertFalse($result);
  1131. }
  1132. /**
  1133. * testFormDisabledFields method
  1134. *
  1135. * @return void
  1136. * @triggers Controller.startup $this->Controller
  1137. */
  1138. public function testFormDisabledFields()
  1139. {
  1140. $event = new Event('Controller.startup', $this->Controller);
  1141. $this->Security->startup($event);
  1142. $fields = '9da2b3fa2b5b8ac0bfbc1bbce145e58059629125%3An%3A0%3A%7B%7D';
  1143. $unlocked = '';
  1144. $debug = urlencode(json_encode([
  1145. '/articles/index',
  1146. [],
  1147. []
  1148. ]));
  1149. $this->Controller->request->data = [
  1150. 'MyModel' => ['name' => 'some data'],
  1151. '_Token' => compact('fields', 'unlocked', 'debug'),
  1152. ];
  1153. $result = $this->validatePost('SecurityException', 'Unexpected field \'MyModel.name\' in POST data');
  1154. $this->assertFalse($result);
  1155. $this->Security->startup($event);
  1156. $this->Security->config('disabledFields', ['MyModel.name']);
  1157. $this->Controller->request->data = [
  1158. 'MyModel' => ['name' => 'some data'],
  1159. '_Token' => compact('fields', 'unlocked', 'debug'),
  1160. ];
  1161. $result = $this->validatePost();
  1162. $this->assertTrue($result);
  1163. }
  1164. /**
  1165. * testValidatePostRadio method
  1166. *
  1167. * Test validatePost with radio buttons.
  1168. *
  1169. * @return void
  1170. * @triggers Controller.startup $this->Controller
  1171. */
  1172. public function testValidatePostRadio()
  1173. {
  1174. $event = new Event('Controller.startup', $this->Controller);
  1175. $this->Security->startup($event);
  1176. $fields = 'c2226a8879c3f4b513691295fc2519a29c44c8bb%3An%3A0%3A%7B%7D';
  1177. $unlocked = '';
  1178. $debug = urlencode(json_encode([
  1179. '/articles/index',
  1180. [],
  1181. []
  1182. ]));
  1183. $this->Controller->request->data = [
  1184. '_Token' => compact('fields', 'unlocked', 'debug'),
  1185. ];
  1186. $result = $this->validatePost('SecurityException', 'Bad Request');
  1187. $this->assertFalse($result);
  1188. $this->Controller->request->data = [
  1189. '_Token' => compact('fields', 'unlocked', 'debug'),
  1190. 'Test' => ['test' => '']
  1191. ];
  1192. $result = $this->validatePost();
  1193. $this->assertTrue($result);
  1194. $this->Controller->request->data = [
  1195. '_Token' => compact('fields', 'unlocked', 'debug'),
  1196. 'Test' => ['test' => '1']
  1197. ];
  1198. $result = $this->validatePost();
  1199. $this->assertTrue($result);
  1200. $this->Controller->request->data = [
  1201. '_Token' => compact('fields', 'unlocked', 'debug'),
  1202. 'Test' => ['test' => '2']
  1203. ];
  1204. $result = $this->validatePost();
  1205. $this->assertTrue($result);
  1206. }
  1207. /**
  1208. * testValidatePostUrlAsHashInput method
  1209. *
  1210. * Test validatePost uses here() as a hash input.
  1211. *
  1212. * @return void
  1213. * @triggers Controller.startup $this->Controller
  1214. */
  1215. public function testValidatePostUrlAsHashInput()
  1216. {
  1217. $event = new Event('Controller.startup', $this->Controller);
  1218. $this->Security->startup($event);
  1219. $fields = 'b0914d06dfb04abf1fada53e16810e87d157950b%3A';
  1220. $unlocked = '';
  1221. $debug = urlencode(json_encode([
  1222. 'another-url',
  1223. ['Model.username', 'Model.password'],
  1224. []
  1225. ]));
  1226. $this->Controller->request->data = [
  1227. 'Model' => ['username' => '', 'password' => ''],
  1228. '_Token' => compact('fields', 'unlocked', 'debug')
  1229. ];
  1230. $this->assertTrue($this->validatePost());
  1231. $request = $this->getMockBuilder('Cake\Network\Request')
  1232. ->setMethods(['here'])
  1233. ->getMock();
  1234. $request->expects($this->at(0))
  1235. ->method('here')
  1236. ->will($this->returnValue('/posts/index?page=1'));
  1237. $request->expects($this->at(1))
  1238. ->method('here')
  1239. ->will($this->returnValue('/posts/edit/1'));
  1240. $request->data = $this->Controller->request->data;
  1241. $this->Controller->request = $request;
  1242. $this->assertFalse($this->validatePost('SecurityException', 'URL mismatch in POST data (expected \'another-url\' but found \'/posts/index?page=1\')'));
  1243. $this->assertFalse($this->validatePost('SecurityException', 'URL mismatch in POST data (expected \'another-url\' but found \'/posts/edit/1\')'));
  1244. }
  1245. /**
  1246. * testBlackHoleNotDeletingSessionInformation method
  1247. *
  1248. * Test that blackhole doesn't delete the _Token session key so repeat data submissions
  1249. * stay blackholed.
  1250. *
  1251. * @return void
  1252. * @triggers Controller.startup $this->Controller
  1253. */
  1254. public function testBlackHoleNotDeletingSessionInformation()
  1255. {
  1256. $event = new Event('Controller.startup', $this->Controller);
  1257. $this->Security->startup($event);
  1258. $this->Security->blackHole($this->Controller, 'auth');
  1259. $this->assertTrue($this->Controller->Security->session->check('_Token'), '_Token was deleted by blackHole %s');
  1260. }
  1261. /**
  1262. * testGenerateToken method
  1263. *
  1264. * Test generateToken().
  1265. *
  1266. * @return void
  1267. */
  1268. public function testGenerateToken()
  1269. {
  1270. $request = $this->Controller->request;
  1271. $this->Security->generateToken($request);
  1272. $this->assertNotEmpty($request->params['_Token']);
  1273. $this->assertTrue(isset($request->params['_Token']['unlockedFields']));
  1274. }
  1275. /**
  1276. * testUnlockedActions method
  1277. *
  1278. * Test unlocked actions.
  1279. *
  1280. * @return void
  1281. * @triggers Controller.startup $this->Controller
  1282. */
  1283. public function testUnlockedActions()
  1284. {
  1285. $_SERVER['REQUEST_METHOD'] = 'POST';
  1286. $event = new Event('Controller.startup', $this->Controller);
  1287. $this->Controller->request->data = ['data'];
  1288. $this->Security->unlockedActions = 'index';
  1289. $this->Security->blackHoleCallback = null;
  1290. $result = $this->Controller->Security->startup($event);
  1291. $this->assertNull($result);
  1292. }
  1293. /**
  1294. * testValidatePostDebugFormat method
  1295. *
  1296. * Test that debug token format is right.
  1297. *
  1298. * @return void
  1299. * @triggers Controller.startup $this->Controller
  1300. */
  1301. public function testValidatePostDebugFormat()
  1302. {
  1303. $event = new Event('Controller.startup', $this->Controller);
  1304. $this->Security->startup($event);
  1305. $unlocked = 'Model.username';
  1306. $fields = ['Model.hidden', 'Model.password'];
  1307. $fields = urlencode(Security::hash(serialize($fields) . $unlocked . Security::salt()));
  1308. $debug = urlencode(json_encode([
  1309. '/articles/index',
  1310. ['Model.hidden', 'Model.password'],
  1311. ['Model.username'],
  1312. ['not expected']
  1313. ]));
  1314. $this->Controller->request->data = [
  1315. 'Model' => [
  1316. 'username' => 'mark',
  1317. 'password' => 'sekret',
  1318. 'hidden' => '0'
  1319. ],
  1320. '_Token' => compact('fields', 'unlocked', 'debug')
  1321. ];
  1322. $result = $this->validatePost('SecurityException', 'Invalid security debug token.');
  1323. $this->assertFalse($result);
  1324. $debug = urlencode(json_encode('not an array'));
  1325. $result = $this->validatePost('SecurityException', 'Invalid security debug token.');
  1326. $this->assertFalse($result);
  1327. }
  1328. /**
  1329. * testBlackholeThrowsException method
  1330. *
  1331. * Test blackhole will now throw passed exception if debug enabled.
  1332. *
  1333. * @expectedException \Cake\Controller\Exception\SecurityException
  1334. * @expectedExceptionMessage error description
  1335. * @return void
  1336. */
  1337. public function testBlackholeThrowsException()
  1338. {
  1339. $this->Security->config('blackHoleCallback', '');
  1340. $this->Security->blackHole($this->Controller, 'auth', new SecurityException('error description'));
  1341. }
  1342. /**
  1343. * testBlackholeThrowsBadRequest method
  1344. *
  1345. * Test blackhole will throw BadRequest if debug disabled.
  1346. *
  1347. * @return void
  1348. */
  1349. public function testBlackholeThrowsBadRequest()
  1350. {
  1351. $this->Security->config('blackHoleCallback', '');
  1352. $message = '';
  1353. Configure::write('debug', false);
  1354. try {
  1355. $this->Security->blackHole($this->Controller, 'auth', new SecurityException('error description'));
  1356. } catch (SecurityException $ex) {
  1357. $message = $ex->getMessage();
  1358. $reason = $ex->getReason();
  1359. }
  1360. $this->assertEquals('The request has been black-holed', $message);
  1361. $this->assertEquals('error description', $reason);
  1362. }
  1363. /**
  1364. * testValidatePostFailTampering method
  1365. *
  1366. * Test that validatePost fails with tampered fields and explanation.
  1367. *
  1368. * @return void
  1369. * @triggers Controller.startup $this->Controller
  1370. */
  1371. public function testValidatePostFailTampering()
  1372. {
  1373. $event = new Event('Controller.startup', $this->Controller);
  1374. $this->Security->startup($event);
  1375. $unlocked = '';
  1376. $fields = ['Model.hidden' => 'value', 'Model.id' => '1'];
  1377. $debug = urlencode(json_encode([
  1378. '/articles/index',
  1379. $fields,
  1380. []
  1381. ]));
  1382. $fields = urlencode(Security::hash(serialize($fields) . $unlocked . Security::salt()));
  1383. $fields .= urlencode(':Model.hidden|Model.id');
  1384. $this->Controller->request->data = [
  1385. 'Model' => [
  1386. 'hidden' => 'tampered',
  1387. 'id' => '1',
  1388. ],
  1389. '_Token' => compact('fields', 'unlocked', 'debug')
  1390. ];
  1391. $result = $this->validatePost('SecurityException', 'Tampered field \'Model.hidden\' in POST data (expected value \'value\' but found \'tampered\')');
  1392. $this->assertFalse($result);
  1393. }
  1394. /**
  1395. * testValidatePostFailTamperingMutatedIntoArray method
  1396. *
  1397. * Test that validatePost fails with tampered fields and explanation.
  1398. *
  1399. * @return void
  1400. * @triggers Controller.startup $this->Controller
  1401. */
  1402. public function testValidatePostFailTamperingMutatedIntoArray()
  1403. {
  1404. $event = new Event('Controller.startup', $this->Controller);
  1405. $this->Security->startup($event);
  1406. $unlocked = '';
  1407. $fields = ['Model.hidden' => 'value', 'Model.id' => '1'];
  1408. $debug = urlencode(json_encode([
  1409. '/articles/index',
  1410. $fields,
  1411. []
  1412. ]));
  1413. $fields = urlencode(Security::hash(serialize($fields) . $unlocked . Security::salt()));
  1414. $fields .= urlencode(':Model.hidden|Model.id');
  1415. $this->Controller->request->data = [
  1416. 'Model' => [
  1417. 'hidden' => ['some-key' => 'some-value'],
  1418. 'id' => '1',
  1419. ],
  1420. '_Token' => compact('fields', 'unlocked', 'debug')
  1421. ];
  1422. $result = $this->validatePost('SecurityException', 'Unexpected field \'Model.hidden.some-key\' in POST data, Missing field \'Model.hidden\' in POST data');
  1423. $this->assertFalse($result);
  1424. }
  1425. /**
  1426. * testValidatePostUnexpectedDebugToken method
  1427. *
  1428. * Test that debug token should not be sent if debug is disabled.
  1429. *
  1430. * @return void
  1431. * @triggers Controller.startup $this->Controller
  1432. */
  1433. public function testValidatePostUnexpectedDebugToken()
  1434. {
  1435. $event = new Event('Controller.startup', $this->Controller);
  1436. $this->Security->startup($event);
  1437. $unlocked = '';
  1438. $fields = ['Model.hidden' => 'value', 'Model.id' => '1'];
  1439. $debug = urlencode(json_encode([
  1440. '/articles/index',
  1441. $fields,
  1442. []
  1443. ]));
  1444. $fields = urlencode(Security::hash(serialize($fields) . $unlocked . Security::salt()));
  1445. $fields .= urlencode(':Model.hidden|Model.id');
  1446. $this->Controller->request->data = [
  1447. 'Model' => [
  1448. 'hidden' => ['some-key' => 'some-value'],
  1449. 'id' => '1',
  1450. ],
  1451. '_Token' => compact('fields', 'unlocked', 'debug')
  1452. ];
  1453. Configure::write('debug', false);
  1454. $result = $this->validatePost('SecurityException', 'Unexpected \'_Token.debug\' found in request data');
  1455. $this->assertFalse($result);
  1456. }
  1457. /**
  1458. * testAuthRequiredThrowsExceptionTokenNotFoundPost method
  1459. *
  1460. * Auth required throws exception token not found.
  1461. *
  1462. * @return void
  1463. * @expectedException \Cake\Controller\Exception\AuthSecurityException
  1464. * @expectedExceptionMessage '_Token' was not found in request data.
  1465. * @triggers Controller.startup $this->Controller
  1466. */
  1467. public function testAuthRequiredThrowsExceptionTokenNotFoundPost()
  1468. {
  1469. $this->Security->config('requireAuth', ['protected']);
  1470. $this->Controller->request->params['action'] = 'protected';
  1471. $this->Controller->request->data = 'notEmpty';
  1472. $this->Security->authRequired($this->Controller);
  1473. }
  1474. /**
  1475. * testAuthRequiredThrowsExceptionTokenNotFoundSession method
  1476. *
  1477. * Auth required throws exception token not found in Session.
  1478. *
  1479. * @return void
  1480. * @expectedException \Cake\Controller\Exception\AuthSecurityException
  1481. * @expectedExceptionMessage '_Token' was not found in session.
  1482. * @triggers Controller.startup $this->Controller
  1483. */
  1484. public function testAuthRequiredThrowsExceptionTokenNotFoundSession()
  1485. {
  1486. $this->Security->config('requireAuth', ['protected']);
  1487. $this->Controller->request->params['action'] = 'protected';
  1488. $this->Controller->request->data = ['_Token' => 'not empty'];
  1489. $this->Security->authRequired($this->Controller);
  1490. }
  1491. /**
  1492. * testAuthRequiredThrowsExceptionControllerNotAllowed method
  1493. *
  1494. * Auth required throws exception controller not allowed.
  1495. *
  1496. * @return void
  1497. * @expectedException \Cake\Controller\Exception\AuthSecurityException
  1498. * @expectedExceptionMessage Controller 'NotAllowed' was not found in allowed controllers: 'Allowed, AnotherAllowed'.
  1499. * @triggers Controller.startup $this->Controller
  1500. */
  1501. public function testAuthRequiredThrowsExceptionControllerNotAllowed()
  1502. {
  1503. $this->Security->config('requireAuth', ['protected']);
  1504. $this->Controller->request->params['controller'] = 'NotAllowed';
  1505. $this->Controller->request->params['action'] = 'protected';
  1506. $this->Controller->request->data = ['_Token' => 'not empty'];
  1507. $this->Controller->request->session()->write('_Token', [
  1508. 'allowedControllers' => ['Allowed', 'AnotherAllowed']
  1509. ]);
  1510. $this->Security->authRequired($this->Controller);
  1511. }
  1512. /**
  1513. * testAuthRequiredThrowsExceptionActionNotAllowed method
  1514. *
  1515. * Auth required throws exception controller not allowed.
  1516. *
  1517. * @return void
  1518. * @expectedException \Cake\Controller\Exception\AuthSecurityException
  1519. * @expectedExceptionMessage Action 'NotAllowed::protected' was not found in allowed actions: 'index, view'.
  1520. * @triggers Controller.startup $this->Controller
  1521. */
  1522. public function testAuthRequiredThrowsExceptionActionNotAllowed()
  1523. {
  1524. $this->Security->config('requireAuth', ['protected']);
  1525. $this->Controller->request->params['controller'] = 'NotAllowed';
  1526. $this->Controller->request->params['action'] = 'protected';
  1527. $this->Controller->request->data = ['_Token' => 'not empty'];
  1528. $this->Controller->request->session()->write('_Token', [
  1529. 'allowedActions' => ['index', 'view']
  1530. ]);
  1531. $this->Security->authRequired($this->Controller);
  1532. }
  1533. /**
  1534. * testAuthRequired method
  1535. *
  1536. * Auth required throws exception controller not allowed.
  1537. *
  1538. * @return void
  1539. * @triggers Controller.startup $this->Controller
  1540. */
  1541. public function testAuthRequired()
  1542. {
  1543. $this->Security->config('requireAuth', ['protected']);
  1544. $this->Controller->request->params['controller'] = 'Allowed';
  1545. $this->Controller->request->params['action'] = 'protected';
  1546. $this->Controller->request->data = ['_Token' => 'not empty'];
  1547. $this->Controller->request->session()->write('_Token', [
  1548. 'allowedActions' => ['protected'],
  1549. 'allowedControllers' => ['Allowed'],
  1550. ]);
  1551. $this->assertTrue($this->Security->authRequired($this->Controller));
  1552. }
  1553. }