SecurityComponentTest.php 56 KB

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