SecurityComponentTest.php 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398
  1. <?php
  2. /**
  3. * SecurityComponentTest file
  4. *
  5. * CakePHP(tm) Tests <http://book.cakephp.org/2.0/en/development/testing.html>
  6. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  7. *
  8. * Licensed under The MIT License
  9. * For full copyright and license information, please see the LICENSE.txt
  10. * Redistributions of files must retain the above copyright notice
  11. *
  12. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  13. * @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
  14. * @package Cake.Test.Case.Controller.Component
  15. * @since CakePHP(tm) v 1.2.0.5435
  16. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  17. */
  18. App::uses('SecurityComponent', 'Controller/Component');
  19. App::uses('Controller', 'Controller');
  20. /**
  21. * TestSecurityComponent
  22. *
  23. * @package Cake.Test.Case.Controller.Component
  24. */
  25. class TestSecurityComponent extends SecurityComponent {
  26. /**
  27. * validatePost method
  28. *
  29. * @param Controller $controller
  30. * @return boolean
  31. */
  32. public function validatePost(Controller $controller) {
  33. return $this->_validatePost($controller);
  34. }
  35. }
  36. /**
  37. * SecurityTestController
  38. *
  39. * @package Cake.Test.Case.Controller.Component
  40. */
  41. class SecurityTestController extends Controller {
  42. /**
  43. * components property
  44. *
  45. * @var array
  46. */
  47. public $components = array('Session', 'TestSecurity');
  48. /**
  49. * failed property
  50. *
  51. * @var boolean false
  52. */
  53. public $failed = false;
  54. /**
  55. * Used for keeping track of headers in test
  56. *
  57. * @var array
  58. */
  59. public $testHeaders = array();
  60. /**
  61. * fail method
  62. *
  63. * @return void
  64. */
  65. public function fail() {
  66. $this->failed = true;
  67. }
  68. /**
  69. * redirect method
  70. *
  71. * @param string|array $url
  72. * @param mixed $code
  73. * @param mixed $exit
  74. * @return void
  75. */
  76. public function redirect($url, $status = null, $exit = true) {
  77. return $status;
  78. }
  79. /**
  80. * Convenience method for header()
  81. *
  82. * @param string $status
  83. * @return void
  84. */
  85. public function header($status) {
  86. $this->testHeaders[] = $status;
  87. }
  88. }
  89. class BrokenCallbackController extends Controller {
  90. public $name = 'UncallableCallback';
  91. public $components = array('Session', 'TestSecurity');
  92. public function index() {
  93. }
  94. protected function _fail() {
  95. }
  96. }
  97. /**
  98. * SecurityComponentTest class
  99. *
  100. * @package Cake.Test.Case.Controller.Component
  101. */
  102. class SecurityComponentTest extends CakeTestCase {
  103. /**
  104. * Controller property
  105. *
  106. * @var SecurityTestController
  107. */
  108. public $Controller;
  109. /**
  110. * oldSalt property
  111. *
  112. * @var string
  113. */
  114. public $oldSalt;
  115. /**
  116. * setUp method
  117. *
  118. * @return void
  119. */
  120. public function setUp() {
  121. parent::setUp();
  122. $request = new CakeRequest('posts/index', false);
  123. $request->addParams(array('controller' => 'posts', 'action' => 'index'));
  124. $this->Controller = new SecurityTestController($request);
  125. $this->Controller->Components->init($this->Controller);
  126. $this->Controller->Security = $this->Controller->TestSecurity;
  127. $this->Controller->Security->blackHoleCallback = 'fail';
  128. $this->Security = $this->Controller->Security;
  129. $this->Security->csrfCheck = false;
  130. Configure::write('Security.salt', 'foo!');
  131. }
  132. /**
  133. * Tear-down method. Resets environment state.
  134. *
  135. * @return void
  136. */
  137. public function tearDown() {
  138. parent::tearDown();
  139. $this->Controller->Session->delete('_Token');
  140. unset($this->Controller->Security);
  141. unset($this->Controller->Component);
  142. unset($this->Controller);
  143. }
  144. /**
  145. * Test that requests are still blackholed when controller has incorrect
  146. * visibility keyword in the blackhole callback
  147. *
  148. * @expectedException BadRequestException
  149. * @return void
  150. */
  151. public function testBlackholeWithBrokenCallback() {
  152. $request = new CakeRequest('posts/index', false);
  153. $request->addParams(array(
  154. 'controller' => 'posts', 'action' => 'index')
  155. );
  156. $this->Controller = new BrokenCallbackController($request);
  157. $this->Controller->Components->init($this->Controller);
  158. $this->Controller->Security = $this->Controller->TestSecurity;
  159. $this->Controller->Security->blackHoleCallback = '_fail';
  160. $this->Controller->Security->startup($this->Controller);
  161. $this->Controller->Security->blackHole($this->Controller, 'csrf');
  162. }
  163. /**
  164. * Ensure that directly requesting the blackholeCallback as the controller
  165. * action results in an exception.
  166. *
  167. * @return void
  168. */
  169. public function testExceptionWhenActionIsBlackholeCallback() {
  170. $this->Controller->request->addParams(array(
  171. 'controller' => 'posts',
  172. 'action' => 'fail'
  173. ));
  174. $this->assertFalse($this->Controller->failed);
  175. $this->Controller->Security->startup($this->Controller);
  176. $this->assertTrue($this->Controller->failed, 'Request was blackholed.');
  177. }
  178. /**
  179. * test that initialize can set properties.
  180. *
  181. * @return void
  182. */
  183. public function testConstructorSettingProperties() {
  184. $settings = array(
  185. 'requirePost' => array('edit', 'update'),
  186. 'requireSecure' => array('update_account'),
  187. 'requireGet' => array('index'),
  188. 'validatePost' => false,
  189. );
  190. $Security = new SecurityComponent($this->Controller->Components, $settings);
  191. $this->Controller->Security->initialize($this->Controller, $settings);
  192. $this->assertEquals($Security->requirePost, $settings['requirePost']);
  193. $this->assertEquals($Security->requireSecure, $settings['requireSecure']);
  194. $this->assertEquals($Security->requireGet, $settings['requireGet']);
  195. $this->assertEquals($Security->validatePost, $settings['validatePost']);
  196. }
  197. /**
  198. * testStartup method
  199. *
  200. * @return void
  201. */
  202. public function testStartup() {
  203. $this->Controller->Security->startup($this->Controller);
  204. $result = $this->Controller->params['_Token']['key'];
  205. $this->assertNotNull($result);
  206. $this->assertTrue($this->Controller->Session->check('_Token'));
  207. }
  208. /**
  209. * testRequirePostFail method
  210. *
  211. * @return void
  212. */
  213. public function testRequirePostFail() {
  214. $_SERVER['REQUEST_METHOD'] = 'GET';
  215. $this->Controller->request['action'] = 'posted';
  216. $this->Controller->Security->requirePost(array('posted'));
  217. $this->Controller->Security->startup($this->Controller);
  218. $this->assertTrue($this->Controller->failed);
  219. }
  220. /**
  221. * testRequirePostSucceed method
  222. *
  223. * @return void
  224. */
  225. public function testRequirePostSucceed() {
  226. $_SERVER['REQUEST_METHOD'] = 'POST';
  227. $this->Controller->request['action'] = 'posted';
  228. $this->Controller->Security->requirePost('posted');
  229. $this->Security->startup($this->Controller);
  230. $this->assertFalse($this->Controller->failed);
  231. }
  232. /**
  233. * testRequireSecureFail method
  234. *
  235. * @return void
  236. */
  237. public function testRequireSecureFail() {
  238. $_SERVER['HTTPS'] = 'off';
  239. $_SERVER['REQUEST_METHOD'] = 'POST';
  240. $this->Controller->request['action'] = 'posted';
  241. $this->Controller->Security->requireSecure(array('posted'));
  242. $this->Controller->Security->startup($this->Controller);
  243. $this->assertTrue($this->Controller->failed);
  244. }
  245. /**
  246. * testRequireSecureSucceed method
  247. *
  248. * @return void
  249. */
  250. public function testRequireSecureSucceed() {
  251. $_SERVER['REQUEST_METHOD'] = 'Secure';
  252. $this->Controller->request['action'] = 'posted';
  253. $_SERVER['HTTPS'] = 'on';
  254. $this->Controller->Security->requireSecure('posted');
  255. $this->Controller->Security->startup($this->Controller);
  256. $this->assertFalse($this->Controller->failed);
  257. }
  258. /**
  259. * testRequireAuthFail method
  260. *
  261. * @return void
  262. */
  263. public function testRequireAuthFail() {
  264. $_SERVER['REQUEST_METHOD'] = 'AUTH';
  265. $this->Controller->request['action'] = 'posted';
  266. $this->Controller->request->data = array('username' => 'willy', 'password' => 'somePass');
  267. $this->Controller->Security->requireAuth(array('posted'));
  268. $this->Controller->Security->startup($this->Controller);
  269. $this->assertTrue($this->Controller->failed);
  270. $this->Controller->Session->write('_Token', array('allowedControllers' => array()));
  271. $this->Controller->request->data = array('username' => 'willy', 'password' => 'somePass');
  272. $this->Controller->request['action'] = 'posted';
  273. $this->Controller->Security->requireAuth('posted');
  274. $this->Controller->Security->startup($this->Controller);
  275. $this->assertTrue($this->Controller->failed);
  276. $this->Controller->Session->write('_Token', array(
  277. 'allowedControllers' => array('SecurityTest'), 'allowedActions' => array('posted2')
  278. ));
  279. $this->Controller->request->data = array('username' => 'willy', 'password' => 'somePass');
  280. $this->Controller->request['action'] = 'posted';
  281. $this->Controller->Security->requireAuth('posted');
  282. $this->Controller->Security->startup($this->Controller);
  283. $this->assertTrue($this->Controller->failed);
  284. }
  285. /**
  286. * testRequireAuthSucceed method
  287. *
  288. * @return void
  289. */
  290. public function testRequireAuthSucceed() {
  291. $_SERVER['REQUEST_METHOD'] = 'AUTH';
  292. $this->Controller->request['action'] = 'posted';
  293. $this->Controller->Security->requireAuth('posted');
  294. $this->Controller->Security->startup($this->Controller);
  295. $this->assertFalse($this->Controller->failed);
  296. $this->Controller->Security->Session->write('_Token', array(
  297. 'allowedControllers' => array('SecurityTest'), 'allowedActions' => array('posted')
  298. ));
  299. $this->Controller->request['controller'] = 'SecurityTest';
  300. $this->Controller->request['action'] = 'posted';
  301. $this->Controller->request->data = array(
  302. 'username' => 'willy', 'password' => 'somePass', '_Token' => ''
  303. );
  304. $this->Controller->action = 'posted';
  305. $this->Controller->Security->requireAuth('posted');
  306. $this->Controller->Security->startup($this->Controller);
  307. $this->assertFalse($this->Controller->failed);
  308. }
  309. /**
  310. * testRequirePostSucceedWrongMethod method
  311. *
  312. * @return void
  313. */
  314. public function testRequirePostSucceedWrongMethod() {
  315. $_SERVER['REQUEST_METHOD'] = 'GET';
  316. $this->Controller->request['action'] = 'getted';
  317. $this->Controller->Security->requirePost('posted');
  318. $this->Controller->Security->startup($this->Controller);
  319. $this->assertFalse($this->Controller->failed);
  320. }
  321. /**
  322. * testRequireGetFail method
  323. *
  324. * @return void
  325. */
  326. public function testRequireGetFail() {
  327. $_SERVER['REQUEST_METHOD'] = 'POST';
  328. $this->Controller->request['action'] = 'getted';
  329. $this->Controller->Security->requireGet(array('getted'));
  330. $this->Controller->Security->startup($this->Controller);
  331. $this->assertTrue($this->Controller->failed);
  332. }
  333. /**
  334. * testRequireGetSucceed method
  335. *
  336. * @return void
  337. */
  338. public function testRequireGetSucceed() {
  339. $_SERVER['REQUEST_METHOD'] = 'GET';
  340. $this->Controller->request['action'] = 'getted';
  341. $this->Controller->Security->requireGet('getted');
  342. $this->Controller->Security->startup($this->Controller);
  343. $this->assertFalse($this->Controller->failed);
  344. }
  345. /**
  346. * testRequireGetSucceedWrongMethod method
  347. *
  348. * @return void
  349. */
  350. public function testRequireGetSucceedWrongMethod() {
  351. $_SERVER['REQUEST_METHOD'] = 'POST';
  352. $this->Controller->request['action'] = 'posted';
  353. $this->Security->requireGet('getted');
  354. $this->Security->startup($this->Controller);
  355. $this->assertFalse($this->Controller->failed);
  356. }
  357. /**
  358. * testRequirePutFail method
  359. *
  360. * @return void
  361. */
  362. public function testRequirePutFail() {
  363. $_SERVER['REQUEST_METHOD'] = 'POST';
  364. $this->Controller->request['action'] = 'putted';
  365. $this->Controller->Security->requirePut(array('putted'));
  366. $this->Controller->Security->startup($this->Controller);
  367. $this->assertTrue($this->Controller->failed);
  368. }
  369. /**
  370. * testRequirePutSucceed method
  371. *
  372. * @return void
  373. */
  374. public function testRequirePutSucceed() {
  375. $_SERVER['REQUEST_METHOD'] = 'PUT';
  376. $this->Controller->request['action'] = 'putted';
  377. $this->Controller->Security->requirePut('putted');
  378. $this->Controller->Security->startup($this->Controller);
  379. $this->assertFalse($this->Controller->failed);
  380. }
  381. /**
  382. * testRequirePutSucceedWrongMethod method
  383. *
  384. * @return void
  385. */
  386. public function testRequirePutSucceedWrongMethod() {
  387. $_SERVER['REQUEST_METHOD'] = 'POST';
  388. $this->Controller->request['action'] = 'posted';
  389. $this->Controller->Security->requirePut('putted');
  390. $this->Controller->Security->startup($this->Controller);
  391. $this->assertFalse($this->Controller->failed);
  392. }
  393. /**
  394. * testRequireDeleteFail method
  395. *
  396. * @return void
  397. */
  398. public function testRequireDeleteFail() {
  399. $_SERVER['REQUEST_METHOD'] = 'POST';
  400. $this->Controller->request['action'] = 'deleted';
  401. $this->Controller->Security->requireDelete(array('deleted', 'other_method'));
  402. $this->Controller->Security->startup($this->Controller);
  403. $this->assertTrue($this->Controller->failed);
  404. }
  405. /**
  406. * testRequireDeleteSucceed method
  407. *
  408. * @return void
  409. */
  410. public function testRequireDeleteSucceed() {
  411. $_SERVER['REQUEST_METHOD'] = 'DELETE';
  412. $this->Controller->request['action'] = 'deleted';
  413. $this->Controller->Security->requireDelete('deleted');
  414. $this->Controller->Security->startup($this->Controller);
  415. $this->assertFalse($this->Controller->failed);
  416. }
  417. /**
  418. * testRequireDeleteSucceedWrongMethod method
  419. *
  420. * @return void
  421. */
  422. public function testRequireDeleteSucceedWrongMethod() {
  423. $_SERVER['REQUEST_METHOD'] = 'POST';
  424. $this->Controller->request['action'] = 'posted';
  425. $this->Controller->Security->requireDelete('deleted');
  426. $this->Controller->Security->startup($this->Controller);
  427. $this->assertFalse($this->Controller->failed);
  428. }
  429. /**
  430. * Simple hash validation test
  431. *
  432. * @return void
  433. */
  434. public function testValidatePost() {
  435. $this->Controller->Security->startup($this->Controller);
  436. $key = $this->Controller->request->params['_Token']['key'];
  437. $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877%3AModel.valid';
  438. $unlocked = '';
  439. $this->Controller->request->data = array(
  440. 'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'),
  441. '_Token' => compact('key', 'fields', 'unlocked')
  442. );
  443. $this->assertTrue($this->Controller->Security->validatePost($this->Controller));
  444. }
  445. /**
  446. * Test that validatePost fails if you are missing the session information.
  447. *
  448. * @return void
  449. */
  450. public function testValidatePostNoSession() {
  451. $this->Controller->Security->startup($this->Controller);
  452. $this->Controller->Session->delete('_Token');
  453. $key = $this->Controller->params['_Token']['key'];
  454. $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877%3AModel.valid';
  455. $this->Controller->data = array(
  456. 'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'),
  457. '_Token' => compact('key', 'fields')
  458. );
  459. $this->assertFalse($this->Controller->Security->validatePost($this->Controller));
  460. }
  461. /**
  462. * test that validatePost fails if any of its required fields are missing.
  463. *
  464. * @return void
  465. */
  466. public function testValidatePostFormHacking() {
  467. $this->Controller->Security->startup($this->Controller);
  468. $key = $this->Controller->params['_Token']['key'];
  469. $unlocked = '';
  470. $this->Controller->request->data = array(
  471. 'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'),
  472. '_Token' => compact('key', 'unlocked')
  473. );
  474. $result = $this->Controller->Security->validatePost($this->Controller);
  475. $this->assertFalse($result, 'validatePost passed when fields were missing. %s');
  476. }
  477. /**
  478. * Test that objects can't be passed into the serialized string. This was a vector for RFI and LFI
  479. * attacks. Thanks to Felix Wilhelm
  480. *
  481. * @return void
  482. */
  483. public function testValidatePostObjectDeserialize() {
  484. $this->Controller->Security->startup($this->Controller);
  485. $key = $this->Controller->request->params['_Token']['key'];
  486. $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877';
  487. $unlocked = '';
  488. // a corrupted serialized object, so we can see if it ever gets to deserialize
  489. $attack = 'O:3:"App":1:{s:5:"__map";a:1:{s:3:"foo";s:7:"Hacked!";s:1:"fail"}}';
  490. $fields .= urlencode(':' . str_rot13($attack));
  491. $this->Controller->request->data = array(
  492. 'Model' => array('username' => 'mark', 'password' => 'foo', 'valid' => '0'),
  493. '_Token' => compact('key', 'fields', 'unlocked')
  494. );
  495. $result = $this->Controller->Security->validatePost($this->Controller);
  496. $this->assertFalse($result, 'validatePost passed when key was missing. %s');
  497. }
  498. /**
  499. * Tests validation of checkbox arrays
  500. *
  501. * @return void
  502. */
  503. public function testValidatePostArray() {
  504. $this->Controller->Security->startup($this->Controller);
  505. $key = $this->Controller->request->params['_Token']['key'];
  506. $fields = 'f7d573650a295b94e0938d32b323fde775e5f32b%3A';
  507. $unlocked = '';
  508. $this->Controller->request->data = array(
  509. 'Model' => array('multi_field' => array('1', '3')),
  510. '_Token' => compact('key', 'fields', 'unlocked')
  511. );
  512. $this->assertTrue($this->Controller->Security->validatePost($this->Controller));
  513. }
  514. /**
  515. * testValidatePostNoModel method
  516. *
  517. * @return void
  518. */
  519. public function testValidatePostNoModel() {
  520. $this->Controller->Security->startup($this->Controller);
  521. $key = $this->Controller->request->params['_Token']['key'];
  522. $fields = '540ac9c60d323c22bafe997b72c0790f39a8bdef%3A';
  523. $unlocked = '';
  524. $this->Controller->request->data = array(
  525. 'anything' => 'some_data',
  526. '_Token' => compact('key', 'fields', 'unlocked')
  527. );
  528. $result = $this->Controller->Security->validatePost($this->Controller);
  529. $this->assertTrue($result);
  530. }
  531. /**
  532. * testValidatePostSimple method
  533. *
  534. * @return void
  535. */
  536. public function testValidatePostSimple() {
  537. $this->Controller->Security->startup($this->Controller);
  538. $key = $this->Controller->request->params['_Token']['key'];
  539. $fields = '69f493434187b867ea14b901fdf58b55d27c935d%3A';
  540. $unlocked = '';
  541. $this->Controller->request->data = array(
  542. 'Model' => array('username' => '', 'password' => ''),
  543. '_Token' => compact('key', 'fields', 'unlocked')
  544. );
  545. $result = $this->Controller->Security->validatePost($this->Controller);
  546. $this->assertTrue($result);
  547. }
  548. /**
  549. * Tests hash validation for multiple records, including locked fields
  550. *
  551. * @return void
  552. */
  553. public function testValidatePostComplex() {
  554. $this->Controller->Security->startup($this->Controller);
  555. $key = $this->Controller->request->params['_Token']['key'];
  556. $fields = 'c9118120e680a7201b543f562e5301006ccfcbe2%3AAddresses.0.id%7CAddresses.1.id';
  557. $unlocked = '';
  558. $this->Controller->request->data = array(
  559. 'Addresses' => array(
  560. '0' => array(
  561. 'id' => '123456', 'title' => '', 'first_name' => '', 'last_name' => '',
  562. 'address' => '', 'city' => '', 'phone' => '', 'primary' => ''
  563. ),
  564. '1' => array(
  565. 'id' => '654321', 'title' => '', 'first_name' => '', 'last_name' => '',
  566. 'address' => '', 'city' => '', 'phone' => '', 'primary' => ''
  567. )
  568. ),
  569. '_Token' => compact('key', 'fields', 'unlocked')
  570. );
  571. $result = $this->Controller->Security->validatePost($this->Controller);
  572. $this->assertTrue($result);
  573. }
  574. /**
  575. * test ValidatePost with multiple select elements.
  576. *
  577. * @return void
  578. */
  579. public function testValidatePostMultipleSelect() {
  580. $this->Controller->Security->startup($this->Controller);
  581. $key = $this->Controller->request->params['_Token']['key'];
  582. $fields = '422cde416475abc171568be690a98cad20e66079%3A';
  583. $unlocked = '';
  584. $this->Controller->request->data = array(
  585. 'Tag' => array('Tag' => array(1, 2)),
  586. '_Token' => compact('key', 'fields', 'unlocked'),
  587. );
  588. $result = $this->Controller->Security->validatePost($this->Controller);
  589. $this->assertTrue($result);
  590. $this->Controller->request->data = array(
  591. 'Tag' => array('Tag' => array(1, 2, 3)),
  592. '_Token' => compact('key', 'fields', 'unlocked'),
  593. );
  594. $result = $this->Controller->Security->validatePost($this->Controller);
  595. $this->assertTrue($result);
  596. $this->Controller->request->data = array(
  597. 'Tag' => array('Tag' => array(1, 2, 3, 4)),
  598. '_Token' => compact('key', 'fields', 'unlocked'),
  599. );
  600. $result = $this->Controller->Security->validatePost($this->Controller);
  601. $this->assertTrue($result);
  602. $fields = '19464422eafe977ee729c59222af07f983010c5f%3A';
  603. $this->Controller->request->data = array(
  604. 'User.password' => 'bar', 'User.name' => 'foo', 'User.is_valid' => '1',
  605. 'Tag' => array('Tag' => array(1)),
  606. '_Token' => compact('key', 'fields', 'unlocked'),
  607. );
  608. $result = $this->Controller->Security->validatePost($this->Controller);
  609. $this->assertTrue($result);
  610. }
  611. /**
  612. * testValidatePostCheckbox method
  613. *
  614. * First block tests un-checked checkbox
  615. * Second block tests checked checkbox
  616. *
  617. * @return void
  618. */
  619. public function testValidatePostCheckbox() {
  620. $this->Controller->Security->startup($this->Controller);
  621. $key = $this->Controller->request->params['_Token']['key'];
  622. $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877%3AModel.valid';
  623. $unlocked = '';
  624. $this->Controller->request->data = array(
  625. 'Model' => array('username' => '', 'password' => '', 'valid' => '0'),
  626. '_Token' => compact('key', 'fields', 'unlocked')
  627. );
  628. $result = $this->Controller->Security->validatePost($this->Controller);
  629. $this->assertTrue($result);
  630. $fields = '874439ca69f89b4c4a5f50fb9c36ff56a28f5d42%3A';
  631. $this->Controller->request->data = array(
  632. 'Model' => array('username' => '', 'password' => '', 'valid' => '0'),
  633. '_Token' => compact('key', 'fields', 'unlocked')
  634. );
  635. $result = $this->Controller->Security->validatePost($this->Controller);
  636. $this->assertTrue($result);
  637. $this->Controller->request->data = array();
  638. $this->Controller->Security->startup($this->Controller);
  639. $key = $this->Controller->request->params['_Token']['key'];
  640. $this->Controller->request->data = array(
  641. 'Model' => array('username' => '', 'password' => '', 'valid' => '0'),
  642. '_Token' => compact('key', 'fields', 'unlocked')
  643. );
  644. $result = $this->Controller->Security->validatePost($this->Controller);
  645. $this->assertTrue($result);
  646. }
  647. /**
  648. * testValidatePostHidden method
  649. *
  650. * @return void
  651. */
  652. public function testValidatePostHidden() {
  653. $this->Controller->Security->startup($this->Controller);
  654. $key = $this->Controller->request->params['_Token']['key'];
  655. $fields = '51ccd8cb0997c7b3d4523ecde5a109318405ef8c%3AModel.hidden%7CModel.other_hidden';
  656. $unlocked = '';
  657. $this->Controller->request->data = array(
  658. 'Model' => array(
  659. 'username' => '', 'password' => '', 'hidden' => '0',
  660. 'other_hidden' => 'some hidden value'
  661. ),
  662. '_Token' => compact('key', 'fields', 'unlocked')
  663. );
  664. $result = $this->Controller->Security->validatePost($this->Controller);
  665. $this->assertTrue($result);
  666. }
  667. /**
  668. * testValidatePostWithDisabledFields method
  669. *
  670. * @return void
  671. */
  672. public function testValidatePostWithDisabledFields() {
  673. $this->Controller->Security->disabledFields = array('Model.username', 'Model.password');
  674. $this->Controller->Security->startup($this->Controller);
  675. $key = $this->Controller->request->params['_Token']['key'];
  676. $fields = 'ef1082968c449397bcd849f963636864383278b1%3AModel.hidden';
  677. $unlocked = '';
  678. $this->Controller->request->data = array(
  679. 'Model' => array(
  680. 'username' => '', 'password' => '', 'hidden' => '0'
  681. ),
  682. '_Token' => compact('fields', 'key', 'unlocked')
  683. );
  684. $result = $this->Controller->Security->validatePost($this->Controller);
  685. $this->assertTrue($result);
  686. }
  687. /**
  688. * test validating post data with posted unlocked fields.
  689. *
  690. * @return void
  691. */
  692. public function testValidatePostDisabledFieldsInData() {
  693. $this->Controller->Security->startup($this->Controller);
  694. $key = $this->Controller->request->params['_Token']['key'];
  695. $unlocked = 'Model.username';
  696. $fields = array('Model.hidden', 'Model.password');
  697. $fields = urlencode(Security::hash(serialize($fields) . $unlocked . Configure::read('Security.salt')));
  698. $this->Controller->request->data = array(
  699. 'Model' => array(
  700. 'username' => 'mark',
  701. 'password' => 'sekret',
  702. 'hidden' => '0'
  703. ),
  704. '_Token' => compact('fields', 'key', 'unlocked')
  705. );
  706. $result = $this->Controller->Security->validatePost($this->Controller);
  707. $this->assertTrue($result);
  708. }
  709. /**
  710. * test that missing 'unlocked' input causes failure
  711. *
  712. * @return void
  713. */
  714. public function testValidatePostFailNoDisabled() {
  715. $this->Controller->Security->startup($this->Controller);
  716. $key = $this->Controller->request->params['_Token']['key'];
  717. $fields = array('Model.hidden', 'Model.password', 'Model.username');
  718. $fields = urlencode(Security::hash(serialize($fields) . Configure::read('Security.salt')));
  719. $this->Controller->request->data = array(
  720. 'Model' => array(
  721. 'username' => 'mark',
  722. 'password' => 'sekret',
  723. 'hidden' => '0'
  724. ),
  725. '_Token' => compact('fields', 'key')
  726. );
  727. $result = $this->Controller->Security->validatePost($this->Controller);
  728. $this->assertFalse($result);
  729. }
  730. /**
  731. * Test that validatePost fails when unlocked fields are changed.
  732. *
  733. * @return void
  734. */
  735. public function testValidatePostFailDisabledFieldTampering() {
  736. $this->Controller->Security->startup($this->Controller);
  737. $key = $this->Controller->request->params['_Token']['key'];
  738. $unlocked = 'Model.username';
  739. $fields = array('Model.hidden', 'Model.password');
  740. $fields = urlencode(Security::hash(serialize($fields) . $unlocked . Configure::read('Security.salt')));
  741. // Tamper the values.
  742. $unlocked = 'Model.username|Model.password';
  743. $this->Controller->request->data = array(
  744. 'Model' => array(
  745. 'username' => 'mark',
  746. 'password' => 'sekret',
  747. 'hidden' => '0'
  748. ),
  749. '_Token' => compact('fields', 'key', 'unlocked')
  750. );
  751. $result = $this->Controller->Security->validatePost($this->Controller);
  752. $this->assertFalse($result);
  753. }
  754. /**
  755. * testValidateHiddenMultipleModel method
  756. *
  757. * @return void
  758. */
  759. public function testValidateHiddenMultipleModel() {
  760. $this->Controller->Security->startup($this->Controller);
  761. $key = $this->Controller->request->params['_Token']['key'];
  762. $fields = 'a2d01072dc4660eea9d15007025f35a7a5b58e18%3AModel.valid%7CModel2.valid%7CModel3.valid';
  763. $unlocked = '';
  764. $this->Controller->request->data = array(
  765. 'Model' => array('username' => '', 'password' => '', 'valid' => '0'),
  766. 'Model2' => array('valid' => '0'),
  767. 'Model3' => array('valid' => '0'),
  768. '_Token' => compact('key', 'fields', 'unlocked')
  769. );
  770. $result = $this->Controller->Security->validatePost($this->Controller);
  771. $this->assertTrue($result);
  772. }
  773. /**
  774. * testValidateHasManyModel method
  775. *
  776. * @return void
  777. */
  778. public function testValidateHasManyModel() {
  779. $this->Controller->Security->startup($this->Controller);
  780. $key = $this->Controller->request->params['_Token']['key'];
  781. $fields = '51e3b55a6edd82020b3f29c9ae200e14bbeb7ee5%3AModel.0.hidden%7CModel.0.valid';
  782. $fields .= '%7CModel.1.hidden%7CModel.1.valid';
  783. $unlocked = '';
  784. $this->Controller->request->data = array(
  785. 'Model' => array(
  786. array(
  787. 'username' => 'username', 'password' => 'password',
  788. 'hidden' => 'value', 'valid' => '0'
  789. ),
  790. array(
  791. 'username' => 'username', 'password' => 'password',
  792. 'hidden' => 'value', 'valid' => '0'
  793. )
  794. ),
  795. '_Token' => compact('key', 'fields', 'unlocked')
  796. );
  797. $result = $this->Controller->Security->validatePost($this->Controller);
  798. $this->assertTrue($result);
  799. }
  800. /**
  801. * testValidateHasManyRecordsPass method
  802. *
  803. * @return void
  804. */
  805. public function testValidateHasManyRecordsPass() {
  806. $this->Controller->Security->startup($this->Controller);
  807. $key = $this->Controller->request->params['_Token']['key'];
  808. $fields = '7a203edb3d345bbf38fe0dccae960da8842e11d7%3AAddress.0.id%7CAddress.0.primary%7C';
  809. $fields .= 'Address.1.id%7CAddress.1.primary';
  810. $unlocked = '';
  811. $this->Controller->request->data = array(
  812. 'Address' => array(
  813. 0 => array(
  814. 'id' => '123',
  815. 'title' => 'home',
  816. 'first_name' => 'Bilbo',
  817. 'last_name' => 'Baggins',
  818. 'address' => '23 Bag end way',
  819. 'city' => 'the shire',
  820. 'phone' => 'N/A',
  821. 'primary' => '1',
  822. ),
  823. 1 => array(
  824. 'id' => '124',
  825. 'title' => 'home',
  826. 'first_name' => 'Frodo',
  827. 'last_name' => 'Baggins',
  828. 'address' => '50 Bag end way',
  829. 'city' => 'the shire',
  830. 'phone' => 'N/A',
  831. 'primary' => '1'
  832. )
  833. ),
  834. '_Token' => compact('key', 'fields', 'unlocked')
  835. );
  836. $result = $this->Controller->Security->validatePost($this->Controller);
  837. $this->assertTrue($result);
  838. }
  839. /**
  840. * Test that values like Foo.0.1
  841. *
  842. * @return void
  843. */
  844. public function testValidateNestedNumericSets() {
  845. $this->Controller->Security->startup($this->Controller);
  846. $key = $this->Controller->request->params['_Token']['key'];
  847. $unlocked = '';
  848. $hashFields = array('TaxonomyData');
  849. $fields = urlencode(Security::hash(serialize($hashFields) . $unlocked . Configure::read('Security.salt')));
  850. $this->Controller->request->data = array(
  851. 'TaxonomyData' => array(
  852. 1 => array(array(2)),
  853. 2 => array(array(3))
  854. ),
  855. '_Token' => compact('key', 'fields', 'unlocked')
  856. );
  857. $result = $this->Controller->Security->validatePost($this->Controller);
  858. $this->assertTrue($result);
  859. }
  860. /**
  861. * testValidateHasManyRecords method
  862. *
  863. * validatePost should fail, hidden fields have been changed.
  864. *
  865. * @return void
  866. */
  867. public function testValidateHasManyRecordsFail() {
  868. $this->Controller->Security->startup($this->Controller);
  869. $key = $this->Controller->request->params['_Token']['key'];
  870. $fields = '7a203edb3d345bbf38fe0dccae960da8842e11d7%3AAddress.0.id%7CAddress.0.primary%7C';
  871. $fields .= 'Address.1.id%7CAddress.1.primary';
  872. $unlocked = '';
  873. $this->Controller->request->data = array(
  874. 'Address' => array(
  875. 0 => array(
  876. 'id' => '123',
  877. 'title' => 'home',
  878. 'first_name' => 'Bilbo',
  879. 'last_name' => 'Baggins',
  880. 'address' => '23 Bag end way',
  881. 'city' => 'the shire',
  882. 'phone' => 'N/A',
  883. 'primary' => '5',
  884. ),
  885. 1 => array(
  886. 'id' => '124',
  887. 'title' => 'home',
  888. 'first_name' => 'Frodo',
  889. 'last_name' => 'Baggins',
  890. 'address' => '50 Bag end way',
  891. 'city' => 'the shire',
  892. 'phone' => 'N/A',
  893. 'primary' => '1'
  894. )
  895. ),
  896. '_Token' => compact('key', 'fields', 'unlocked')
  897. );
  898. $result = $this->Controller->Security->validatePost($this->Controller);
  899. $this->assertFalse($result);
  900. }
  901. /**
  902. * testFormDisabledFields method
  903. *
  904. * @return void
  905. */
  906. public function testFormDisabledFields() {
  907. $this->Controller->Security->startup($this->Controller);
  908. $key = $this->Controller->request->params['_Token']['key'];
  909. $fields = '11842060341b9d0fc3808b90ba29fdea7054d6ad%3An%3A0%3A%7B%7D';
  910. $unlocked = '';
  911. $this->Controller->request->data = array(
  912. 'MyModel' => array('name' => 'some data'),
  913. '_Token' => compact('key', 'fields', 'unlocked')
  914. );
  915. $result = $this->Controller->Security->validatePost($this->Controller);
  916. $this->assertFalse($result);
  917. $this->Controller->Security->startup($this->Controller);
  918. $this->Controller->Security->disabledFields = array('MyModel.name');
  919. $key = $this->Controller->request->params['_Token']['key'];
  920. $this->Controller->request->data = array(
  921. 'MyModel' => array('name' => 'some data'),
  922. '_Token' => compact('key', 'fields', 'unlocked')
  923. );
  924. $result = $this->Controller->Security->validatePost($this->Controller);
  925. $this->assertTrue($result);
  926. }
  927. /**
  928. * testRadio method
  929. *
  930. * @return void
  931. */
  932. public function testRadio() {
  933. $this->Controller->Security->startup($this->Controller);
  934. $key = $this->Controller->request->params['_Token']['key'];
  935. $fields = '575ef54ca4fc8cab468d6d898e9acd3a9671c17e%3An%3A0%3A%7B%7D';
  936. $unlocked = '';
  937. $this->Controller->request->data = array(
  938. '_Token' => compact('key', 'fields', 'unlocked')
  939. );
  940. $result = $this->Controller->Security->validatePost($this->Controller);
  941. $this->assertFalse($result);
  942. $this->Controller->request->data = array(
  943. '_Token' => compact('key', 'fields', 'unlocked'),
  944. 'Test' => array('test' => '')
  945. );
  946. $result = $this->Controller->Security->validatePost($this->Controller);
  947. $this->assertTrue($result);
  948. $this->Controller->request->data = array(
  949. '_Token' => compact('key', 'fields', 'unlocked'),
  950. 'Test' => array('test' => '1')
  951. );
  952. $result = $this->Controller->Security->validatePost($this->Controller);
  953. $this->assertTrue($result);
  954. $this->Controller->request->data = array(
  955. '_Token' => compact('key', 'fields', 'unlocked'),
  956. 'Test' => array('test' => '2')
  957. );
  958. $result = $this->Controller->Security->validatePost($this->Controller);
  959. $this->assertTrue($result);
  960. }
  961. /**
  962. * test that a requestAction's controller will have the _Token appended to
  963. * the params.
  964. *
  965. * @return void
  966. * @see https://cakephp.lighthouseapp.com/projects/42648/tickets/68
  967. */
  968. public function testSettingTokenForRequestAction() {
  969. $this->Controller->Security->startup($this->Controller);
  970. $key = $this->Controller->request->params['_Token']['key'];
  971. $this->Controller->params['requested'] = 1;
  972. unset($this->Controller->request->params['_Token']);
  973. $this->Controller->Security->startup($this->Controller);
  974. $this->assertEquals($this->Controller->request->params['_Token']['key'], $key);
  975. }
  976. /**
  977. * test that blackhole doesn't delete the _Token session key so repeat data submissions
  978. * stay blackholed.
  979. *
  980. * @link https://cakephp.lighthouseapp.com/projects/42648/tickets/214
  981. * @return void
  982. */
  983. public function testBlackHoleNotDeletingSessionInformation() {
  984. $this->Controller->Security->startup($this->Controller);
  985. $this->Controller->Security->blackHole($this->Controller, 'auth');
  986. $this->assertTrue($this->Controller->Security->Session->check('_Token'), '_Token was deleted by blackHole %s');
  987. }
  988. /**
  989. * test that csrf checks are skipped for request action.
  990. *
  991. * @return void
  992. */
  993. public function testCsrfSkipRequestAction() {
  994. $_SERVER['REQUEST_METHOD'] = 'POST';
  995. $this->Security->validatePost = false;
  996. $this->Security->csrfCheck = true;
  997. $this->Security->csrfExpires = '+10 minutes';
  998. $this->Controller->request->params['requested'] = 1;
  999. $this->Security->startup($this->Controller);
  1000. $this->assertFalse($this->Controller->failed, 'fail() was called.');
  1001. }
  1002. /**
  1003. * test setting
  1004. *
  1005. * @return void
  1006. */
  1007. public function testCsrfSettings() {
  1008. $this->Security->validatePost = false;
  1009. $this->Security->csrfCheck = true;
  1010. $this->Security->csrfExpires = '+10 minutes';
  1011. $this->Security->startup($this->Controller);
  1012. $token = $this->Security->Session->read('_Token');
  1013. $this->assertEquals(1, count($token['csrfTokens']), 'Missing the csrf token.');
  1014. $this->assertEquals(strtotime('+10 minutes'), current($token['csrfTokens']), 'Token expiry does not match');
  1015. $this->assertEquals(array('key', 'unlockedFields'), array_keys($this->Controller->request->params['_Token']), 'Keys don not match');
  1016. }
  1017. /**
  1018. * Test setting multiple nonces, when startup() is called more than once, (ie more than one request.)
  1019. *
  1020. * @return void
  1021. */
  1022. public function testCsrfSettingMultipleNonces() {
  1023. $this->Security->validatePost = false;
  1024. $this->Security->csrfCheck = true;
  1025. $this->Security->csrfExpires = '+10 minutes';
  1026. $csrfExpires = strtotime('+10 minutes');
  1027. $this->Security->startup($this->Controller);
  1028. $this->Security->startup($this->Controller);
  1029. $token = $this->Security->Session->read('_Token');
  1030. $this->assertEquals(2, count($token['csrfTokens']), 'Missing the csrf token.');
  1031. foreach ($token['csrfTokens'] as $expires) {
  1032. $diff = $csrfExpires - $expires;
  1033. $this->assertTrue($diff === 0 || $diff === 1, 'Token expiry does not match');
  1034. }
  1035. }
  1036. /**
  1037. * test that nonces are consumed by form submits.
  1038. *
  1039. * @return void
  1040. */
  1041. public function testCsrfNonceConsumption() {
  1042. $this->Security->validatePost = false;
  1043. $this->Security->csrfCheck = true;
  1044. $this->Security->csrfExpires = '+10 minutes';
  1045. $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('+10 minutes')));
  1046. $this->Controller->request = $this->getMock('CakeRequest', array('is'));
  1047. $this->Controller->request->expects($this->once())->method('is')
  1048. ->with(array('post', 'put'))
  1049. ->will($this->returnValue(true));
  1050. $this->Controller->request->params['action'] = 'index';
  1051. $this->Controller->request->data = array(
  1052. '_Token' => array(
  1053. 'key' => 'nonce1'
  1054. ),
  1055. 'Post' => array(
  1056. 'title' => 'Woot'
  1057. )
  1058. );
  1059. $this->Security->startup($this->Controller);
  1060. $token = $this->Security->Session->read('_Token');
  1061. $this->assertFalse(isset($token['csrfTokens']['nonce1']), 'Token was not consumed');
  1062. }
  1063. /**
  1064. * test that expired values in the csrfTokens are cleaned up.
  1065. *
  1066. * @return void
  1067. */
  1068. public function testCsrfNonceVacuum() {
  1069. $this->Security->validatePost = false;
  1070. $this->Security->csrfCheck = true;
  1071. $this->Security->csrfExpires = '+10 minutes';
  1072. $this->Security->Session->write('_Token.csrfTokens', array(
  1073. 'valid' => strtotime('+30 minutes'),
  1074. 'poof' => strtotime('-11 minutes'),
  1075. 'dust' => strtotime('-20 minutes')
  1076. ));
  1077. $this->Security->startup($this->Controller);
  1078. $tokens = $this->Security->Session->read('_Token.csrfTokens');
  1079. $this->assertEquals(2, count($tokens), 'Too many tokens left behind');
  1080. $this->assertNotEmpty('valid', $tokens, 'Valid token was removed.');
  1081. }
  1082. /**
  1083. * test that when the key is missing the request is blackHoled
  1084. *
  1085. * @return void
  1086. */
  1087. public function testCsrfBlackHoleOnKeyMismatch() {
  1088. $this->Security->validatePost = false;
  1089. $this->Security->csrfCheck = true;
  1090. $this->Security->csrfExpires = '+10 minutes';
  1091. $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('+10 minutes')));
  1092. $this->Controller->request = $this->getMock('CakeRequest', array('is'));
  1093. $this->Controller->request->expects($this->once())->method('is')
  1094. ->with(array('post', 'put'))
  1095. ->will($this->returnValue(true));
  1096. $this->Controller->request->params['action'] = 'index';
  1097. $this->Controller->request->data = array(
  1098. '_Token' => array(
  1099. 'key' => 'not the right value'
  1100. ),
  1101. 'Post' => array(
  1102. 'title' => 'Woot'
  1103. )
  1104. );
  1105. $this->Security->startup($this->Controller);
  1106. $this->assertTrue($this->Controller->failed, 'fail() was not called.');
  1107. }
  1108. /**
  1109. * test that when the key is missing the request is blackHoled
  1110. *
  1111. * @return void
  1112. */
  1113. public function testCsrfBlackHoleOnExpiredKey() {
  1114. $this->Security->validatePost = false;
  1115. $this->Security->csrfCheck = true;
  1116. $this->Security->csrfExpires = '+10 minutes';
  1117. $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('-5 minutes')));
  1118. $this->Controller->request = $this->getMock('CakeRequest', array('is'));
  1119. $this->Controller->request->expects($this->once())->method('is')
  1120. ->with(array('post', 'put'))
  1121. ->will($this->returnValue(true));
  1122. $this->Controller->request->params['action'] = 'index';
  1123. $this->Controller->request->data = array(
  1124. '_Token' => array(
  1125. 'key' => 'nonce1'
  1126. ),
  1127. 'Post' => array(
  1128. 'title' => 'Woot'
  1129. )
  1130. );
  1131. $this->Security->startup($this->Controller);
  1132. $this->assertTrue($this->Controller->failed, 'fail() was not called.');
  1133. }
  1134. /**
  1135. * test that csrfUseOnce = false works.
  1136. *
  1137. * @return void
  1138. */
  1139. public function testCsrfNotUseOnce() {
  1140. $this->Security->validatePost = false;
  1141. $this->Security->csrfCheck = true;
  1142. $this->Security->csrfUseOnce = false;
  1143. $this->Security->csrfExpires = '+10 minutes';
  1144. // Generate one token
  1145. $this->Security->startup($this->Controller);
  1146. $token = $this->Security->Session->read('_Token.csrfTokens');
  1147. $this->assertEquals(1, count($token), 'Should only be one token.');
  1148. $this->Security->startup($this->Controller);
  1149. $tokenTwo = $this->Security->Session->read('_Token.csrfTokens');
  1150. $this->assertEquals(1, count($tokenTwo), 'Should only be one token.');
  1151. $this->assertEquals($token, $tokenTwo, 'Tokens should not be different.');
  1152. $key = $this->Controller->request->params['_Token']['key'];
  1153. $this->assertEquals(array($key), array_keys($token), '_Token.key and csrfToken do not match request will blackhole.');
  1154. }
  1155. /**
  1156. * ensure that longer session tokens are not consumed
  1157. *
  1158. * @return void
  1159. */
  1160. public function testCsrfNotUseOnceValidationLeavingToken() {
  1161. $this->Security->validatePost = false;
  1162. $this->Security->csrfCheck = true;
  1163. $this->Security->csrfUseOnce = false;
  1164. $this->Security->csrfExpires = '+10 minutes';
  1165. $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('+10 minutes')));
  1166. $this->Controller->request = $this->getMock('CakeRequest', array('is'));
  1167. $this->Controller->request->expects($this->once())->method('is')
  1168. ->with(array('post', 'put'))
  1169. ->will($this->returnValue(true));
  1170. $this->Controller->request->params['action'] = 'index';
  1171. $this->Controller->request->data = array(
  1172. '_Token' => array(
  1173. 'key' => 'nonce1'
  1174. ),
  1175. 'Post' => array(
  1176. 'title' => 'Woot'
  1177. )
  1178. );
  1179. $this->Security->startup($this->Controller);
  1180. $token = $this->Security->Session->read('_Token');
  1181. $this->assertTrue(isset($token['csrfTokens']['nonce1']), 'Token was consumed');
  1182. }
  1183. /**
  1184. * Test generateToken()
  1185. *
  1186. * @return void
  1187. */
  1188. public function testGenerateToken() {
  1189. $request = $this->Controller->request;
  1190. $this->Security->generateToken($request);
  1191. $this->assertNotEmpty($request->params['_Token']);
  1192. $this->assertTrue(isset($request->params['_Token']['unlockedFields']));
  1193. $this->assertTrue(isset($request->params['_Token']['key']));
  1194. }
  1195. /**
  1196. * Test the limiting of CSRF tokens.
  1197. *
  1198. * @return void
  1199. */
  1200. public function testCsrfLimit() {
  1201. $this->Security->csrfLimit = 3;
  1202. $time = strtotime('+10 minutes');
  1203. $tokens = array(
  1204. '1' => $time,
  1205. '2' => $time,
  1206. '3' => $time,
  1207. '4' => $time,
  1208. '5' => $time,
  1209. );
  1210. $this->Security->Session->write('_Token', array('csrfTokens' => $tokens));
  1211. $this->Security->generateToken($this->Controller->request);
  1212. $result = $this->Security->Session->read('_Token.csrfTokens');
  1213. $this->assertFalse(isset($result['1']));
  1214. $this->assertFalse(isset($result['2']));
  1215. $this->assertFalse(isset($result['3']));
  1216. $this->assertTrue(isset($result['4']));
  1217. $this->assertTrue(isset($result['5']));
  1218. }
  1219. /**
  1220. * Test unlocked actions
  1221. *
  1222. * @return void
  1223. */
  1224. public function testUnlockedActions() {
  1225. $_SERVER['REQUEST_METHOD'] = 'POST';
  1226. $this->Controller->request->data = array('data');
  1227. $this->Controller->Security->unlockedActions = 'index';
  1228. $this->Controller->Security->blackHoleCallback = null;
  1229. $result = $this->Controller->Security->startup($this->Controller);
  1230. $this->assertNull($result);
  1231. }
  1232. }