SecurityComponentTest.php 41 KB

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