Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

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