SecurityComponentTest.php 59 KB

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