統計
| ブランチ: | リビジョン:

pictcode / lib / Cake / Test / Case / Controller / Component / SecurityComponentTest.php @ 635eef61

履歴 | 表示 | アノテート | ダウンロード (42.957 KB)

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->Security->unlockedActions = array('posted');
334
                $this->Controller->request['action'] = 'posted';
335
                $this->Controller->Security->requireAuth('posted');
336
                $this->Controller->Security->startup($this->Controller);
337
                $this->assertFalse($this->Controller->failed);
338

    
339
                $this->Controller->Security->Session->write('_Token', array(
340
                        'allowedControllers' => array('SecurityTest'),
341
                        'allowedActions' => array('posted')
342
                ));
343
                $this->Controller->request['controller'] = 'SecurityTest';
344
                $this->Controller->request['action'] = 'posted';
345

    
346
                $this->Controller->request->data = array(
347
                        'username' => 'willy',
348
                        'password' => 'somePass',
349
                        '_Token' => ''
350
                );
351
                $this->Controller->action = 'posted';
352
                $this->Controller->Security->requireAuth('posted');
353
                $this->Controller->Security->startup($this->Controller);
354
                $this->assertFalse($this->Controller->failed);
355
        }
356

    
357
/**
358
 * testRequirePostSucceedWrongMethod method
359
 *
360
 * @return void
361
 */
362
        public function testRequirePostSucceedWrongMethod() {
363
                $_SERVER['REQUEST_METHOD'] = 'GET';
364
                $this->Controller->request['action'] = 'getted';
365
                $this->Controller->Security->requirePost('posted');
366
                $this->Controller->Security->startup($this->Controller);
367
                $this->assertFalse($this->Controller->failed);
368
        }
369

    
370
/**
371
 * testRequireGetFail method
372
 *
373
 * @return void
374
 */
375
        public function testRequireGetFail() {
376
                $_SERVER['REQUEST_METHOD'] = 'POST';
377
                $this->Controller->request['action'] = 'getted';
378
                $this->Controller->Security->requireGet(array('getted'));
379
                $this->Controller->Security->startup($this->Controller);
380
                $this->assertTrue($this->Controller->failed);
381
        }
382

    
383
/**
384
 * testRequireGetSucceed method
385
 *
386
 * @return void
387
 */
388
        public function testRequireGetSucceed() {
389
                $_SERVER['REQUEST_METHOD'] = 'GET';
390
                $this->Controller->request['action'] = 'getted';
391
                $this->Controller->Security->requireGet('getted');
392
                $this->Controller->Security->startup($this->Controller);
393
                $this->assertFalse($this->Controller->failed);
394
        }
395

    
396
/**
397
 * testRequireGetSucceedWrongMethod method
398
 *
399
 * @return void
400
 */
401
        public function testRequireGetSucceedWrongMethod() {
402
                $_SERVER['REQUEST_METHOD'] = 'POST';
403
                $this->Controller->request['action'] = 'posted';
404
                $this->Security->requireGet('getted');
405
                $this->Security->startup($this->Controller);
406
                $this->assertFalse($this->Controller->failed);
407
        }
408

    
409
/**
410
 * testRequirePutFail method
411
 *
412
 * @return void
413
 */
414
        public function testRequirePutFail() {
415
                $_SERVER['REQUEST_METHOD'] = 'POST';
416
                $this->Controller->request['action'] = 'putted';
417
                $this->Controller->Security->requirePut(array('putted'));
418
                $this->Controller->Security->startup($this->Controller);
419
                $this->assertTrue($this->Controller->failed);
420
        }
421

    
422
/**
423
 * testRequirePutSucceed method
424
 *
425
 * @return void
426
 */
427
        public function testRequirePutSucceed() {
428
                $_SERVER['REQUEST_METHOD'] = 'PUT';
429
                $this->Controller->request['action'] = 'putted';
430
                $this->Controller->Security->requirePut('putted');
431
                $this->Controller->Security->startup($this->Controller);
432
                $this->assertFalse($this->Controller->failed);
433
        }
434

    
435
/**
436
 * testRequirePutSucceedWrongMethod method
437
 *
438
 * @return void
439
 */
440
        public function testRequirePutSucceedWrongMethod() {
441
                $_SERVER['REQUEST_METHOD'] = 'POST';
442
                $this->Controller->request['action'] = 'posted';
443
                $this->Controller->Security->requirePut('putted');
444
                $this->Controller->Security->startup($this->Controller);
445
                $this->assertFalse($this->Controller->failed);
446
        }
447

    
448
/**
449
 * testRequireDeleteFail method
450
 *
451
 * @return void
452
 */
453
        public function testRequireDeleteFail() {
454
                $_SERVER['REQUEST_METHOD'] = 'POST';
455
                $this->Controller->request['action'] = 'deleted';
456
                $this->Controller->Security->requireDelete(array('deleted', 'other_method'));
457
                $this->Controller->Security->startup($this->Controller);
458
                $this->assertTrue($this->Controller->failed);
459
        }
460

    
461
/**
462
 * testRequireDeleteSucceed method
463
 *
464
 * @return void
465
 */
466
        public function testRequireDeleteSucceed() {
467
                $_SERVER['REQUEST_METHOD'] = 'DELETE';
468
                $this->Controller->request['action'] = 'deleted';
469
                $this->Controller->Security->requireDelete('deleted');
470
                $this->Controller->Security->startup($this->Controller);
471
                $this->assertFalse($this->Controller->failed);
472
        }
473

    
474
/**
475
 * testRequireDeleteSucceedWrongMethod method
476
 *
477
 * @return void
478
 */
479
        public function testRequireDeleteSucceedWrongMethod() {
480
                $_SERVER['REQUEST_METHOD'] = 'POST';
481
                $this->Controller->request['action'] = 'posted';
482
                $this->Controller->Security->requireDelete('deleted');
483
                $this->Controller->Security->startup($this->Controller);
484
                $this->assertFalse($this->Controller->failed);
485
        }
486

    
487
/**
488
 * Test that validatePost fires on GET with request data.
489
 * This could happen when method overriding is used.
490
 *
491
 * @return void
492
 * @triggers Controller.startup $this->Controller
493
 */
494
        public function testValidatePostOnGetWithData() {
495
                $_SERVER['REQUEST_METHOD'] = 'GET';
496
                $this->Controller->Security->startup($this->Controller);
497

    
498
                $fields = 'an-invalid-token';
499
                $unlocked = '';
500

    
501
                $this->Controller->request->data = [
502
                        'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'),
503
                        '_Token' => compact('fields', 'unlocked')
504
                ];
505
                $this->assertFalse($this->Controller->failed, 'Should not be failed yet');
506
                $this->Controller->Security->startup($this->Controller);
507
                $this->assertTrue($this->Controller->failed, 'Should fail because of validatePost.');
508
        }
509

    
510
/**
511
 * Simple hash validation test
512
 *
513
 * @return void
514
 */
515
        public function testValidatePost() {
516
                $this->Controller->Security->startup($this->Controller);
517

    
518
                $key = $this->Controller->request->params['_Token']['key'];
519
                $fields = '01c1f6dbba02ac6f21b229eab1cc666839b14303%3AModel.valid';
520
                $unlocked = '';
521

    
522
                $this->Controller->request->data = array(
523
                        'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'),
524
                        '_Token' => compact('key', 'fields', 'unlocked')
525
                );
526
                $this->assertTrue($this->Controller->Security->validatePost($this->Controller));
527
        }
528

    
529
/**
530
 * Test that validatePost fails if you are missing the session information.
531
 *
532
 * @return void
533
 */
534
        public function testValidatePostNoSession() {
535
                $this->Controller->Security->startup($this->Controller);
536
                $this->Controller->Session->delete('_Token');
537

    
538
                $key = $this->Controller->params['_Token']['key'];
539
                $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877%3AModel.valid';
540

    
541
                $this->Controller->data = array(
542
                        'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'),
543
                        '_Token' => compact('key', 'fields')
544
                );
545
                $this->assertFalse($this->Controller->Security->validatePost($this->Controller));
546
        }
547

    
548
/**
549
 * test that validatePost fails if any of its required fields are missing.
550
 *
551
 * @return void
552
 */
553
        public function testValidatePostFormHacking() {
554
                $this->Controller->Security->startup($this->Controller);
555
                $key = $this->Controller->params['_Token']['key'];
556
                $unlocked = '';
557

    
558
                $this->Controller->request->data = array(
559
                        'Model' => array('username' => 'nate', 'password' => 'foo', 'valid' => '0'),
560
                        '_Token' => compact('key', 'unlocked')
561
                );
562
                $result = $this->Controller->Security->validatePost($this->Controller);
563
                $this->assertFalse($result, 'validatePost passed when fields were missing. %s');
564
        }
565

    
566
/**
567
 * Test that objects can't be passed into the serialized string. This was a vector for RFI and LFI
568
 * attacks. Thanks to Felix Wilhelm
569
 *
570
 * @return void
571
 */
572
        public function testValidatePostObjectDeserialize() {
573
                $this->Controller->Security->startup($this->Controller);
574
                $key = $this->Controller->request->params['_Token']['key'];
575
                $fields = 'a5475372b40f6e3ccbf9f8af191f20e1642fd877';
576
                $unlocked = '';
577

    
578
                // a corrupted serialized object, so we can see if it ever gets to deserialize
579
                $attack = 'O:3:"App":1:{s:5:"__map";a:1:{s:3:"foo";s:7:"Hacked!";s:1:"fail"}}';
580
                $fields .= urlencode(':' . str_rot13($attack));
581

    
582
                $this->Controller->request->data = array(
583
                        'Model' => array('username' => 'mark', 'password' => 'foo', 'valid' => '0'),
584
                        '_Token' => compact('key', 'fields', 'unlocked')
585
                );
586
                $result = $this->Controller->Security->validatePost($this->Controller);
587
                $this->assertFalse($result, 'validatePost passed when key was missing. %s');
588
        }
589

    
590
/**
591
 * Tests validation of checkbox arrays
592
 *
593
 * @return void
594
 */
595
        public function testValidatePostArray() {
596
                $this->Controller->Security->startup($this->Controller);
597

    
598
                $key = $this->Controller->request->params['_Token']['key'];
599
                $fields = '38504e4a341d4e6eadb437217efd91270e558d55%3A';
600
                $unlocked = '';
601

    
602
                $this->Controller->request->data = array(
603
                        'Model' => array('multi_field' => array('1', '3')),
604
                        '_Token' => compact('key', 'fields', 'unlocked')
605
                );
606
                $this->assertTrue($this->Controller->Security->validatePost($this->Controller));
607
        }
608

    
609
/**
610
 * testValidatePostNoModel method
611
 *
612
 * @return void
613
 */
614
        public function testValidatePostNoModel() {
615
                $this->Controller->Security->startup($this->Controller);
616

    
617
                $key = $this->Controller->request->params['_Token']['key'];
618
                $fields = 'c5bc49a6c938c820e7e538df3d8ab7bffbc97ef9%3A';
619
                $unlocked = '';
620

    
621
                $this->Controller->request->data = array(
622
                        'anything' => 'some_data',
623
                        '_Token' => compact('key', 'fields', 'unlocked')
624
                );
625

    
626
                $result = $this->Controller->Security->validatePost($this->Controller);
627
                $this->assertTrue($result);
628
        }
629

    
630
/**
631
 * testValidatePostSimple method
632
 *
633
 * @return void
634
 */
635
        public function testValidatePostSimple() {
636
                $this->Controller->Security->startup($this->Controller);
637

    
638
                $key = $this->Controller->request->params['_Token']['key'];
639
                $fields = '5415d31b4483c1e09ddb58d2a91ba9650b12aa83%3A';
640
                $unlocked = '';
641

    
642
                $this->Controller->request->data = array(
643
                        'Model' => array('username' => '', 'password' => ''),
644
                        '_Token' => compact('key', 'fields', 'unlocked')
645
                );
646

    
647
                $result = $this->Controller->Security->validatePost($this->Controller);
648
                $this->assertTrue($result);
649
        }
650

    
651
/**
652
 * Tests hash validation for multiple records, including locked fields
653
 *
654
 * @return void
655
 */
656
        public function testValidatePostComplex() {
657
                $this->Controller->Security->startup($this->Controller);
658

    
659
                $key = $this->Controller->request->params['_Token']['key'];
660
                $fields = 'b72a99e923687687bb5e64025d3cc65e1cecced4%3AAddresses.0.id%7CAddresses.1.id';
661
                $unlocked = '';
662

    
663
                $this->Controller->request->data = array(
664
                        'Addresses' => array(
665
                                '0' => array(
666
                                        'id' => '123456', 'title' => '', 'first_name' => '', 'last_name' => '',
667
                                        'address' => '', 'city' => '', 'phone' => '', 'primary' => ''
668
                                ),
669
                                '1' => array(
670
                                        'id' => '654321', 'title' => '', 'first_name' => '', 'last_name' => '',
671
                                        'address' => '', 'city' => '', 'phone' => '', 'primary' => ''
672
                                )
673
                        ),
674
                        '_Token' => compact('key', 'fields', 'unlocked')
675
                );
676
                $result = $this->Controller->Security->validatePost($this->Controller);
677
                $this->assertTrue($result);
678
        }
679

    
680
/**
681
 * test ValidatePost with multiple select elements.
682
 *
683
 * @return void
684
 */
685
        public function testValidatePostMultipleSelect() {
686
                $this->Controller->Security->startup($this->Controller);
687

    
688
                $key = $this->Controller->request->params['_Token']['key'];
689
                $fields = '8a764bdb989132c1d46f9a45f64ce2da5f9eebb9%3A';
690
                $unlocked = '';
691

    
692
                $this->Controller->request->data = array(
693
                        'Tag' => array('Tag' => array(1, 2)),
694
                        '_Token' => compact('key', 'fields', 'unlocked'),
695
                );
696
                $result = $this->Controller->Security->validatePost($this->Controller);
697
                $this->assertTrue($result);
698

    
699
                $this->Controller->request->data = array(
700
                        'Tag' => array('Tag' => array(1, 2, 3)),
701
                        '_Token' => compact('key', 'fields', 'unlocked'),
702
                );
703
                $result = $this->Controller->Security->validatePost($this->Controller);
704
                $this->assertTrue($result);
705

    
706
                $this->Controller->request->data = array(
707
                        'Tag' => array('Tag' => array(1, 2, 3, 4)),
708
                        '_Token' => compact('key', 'fields', 'unlocked'),
709
                );
710
                $result = $this->Controller->Security->validatePost($this->Controller);
711
                $this->assertTrue($result);
712

    
713
                $fields = '722de3615e63fdff899e86e85e6498b11c50bb66%3A';
714
                $this->Controller->request->data = array(
715
                        'User.password' => 'bar', 'User.name' => 'foo', 'User.is_valid' => '1',
716
                        'Tag' => array('Tag' => array(1)),
717
                        '_Token' => compact('key', 'fields', 'unlocked'),
718
                );
719
                $result = $this->Controller->Security->validatePost($this->Controller);
720
                $this->assertTrue($result);
721
        }
722

    
723
/**
724
 * testValidatePostCheckbox method
725
 *
726
 * First block tests un-checked checkbox
727
 * Second block tests checked checkbox
728
 *
729
 * @return void
730
 */
731
        public function testValidatePostCheckbox() {
732
                $this->Controller->Security->startup($this->Controller);
733
                $key = $this->Controller->request->params['_Token']['key'];
734
                $fields = '01c1f6dbba02ac6f21b229eab1cc666839b14303%3AModel.valid';
735
                $unlocked = '';
736

    
737
                $this->Controller->request->data = array(
738
                        'Model' => array('username' => '', 'password' => '', 'valid' => '0'),
739
                        '_Token' => compact('key', 'fields', 'unlocked')
740
                );
741

    
742
                $result = $this->Controller->Security->validatePost($this->Controller);
743
                $this->assertTrue($result);
744

    
745
                $fields = 'efbcf463a2c31e97c85d95eedc41dff9e9c6a026%3A';
746

    
747
                $this->Controller->request->data = array(
748
                        'Model' => array('username' => '', 'password' => '', 'valid' => '0'),
749
                        '_Token' => compact('key', 'fields', 'unlocked')
750
                );
751

    
752
                $result = $this->Controller->Security->validatePost($this->Controller);
753
                $this->assertTrue($result);
754

    
755
                $this->Controller->request->data = array();
756
                $this->Controller->Security->startup($this->Controller);
757
                $key = $this->Controller->request->params['_Token']['key'];
758

    
759
                $this->Controller->request->data = array(
760
                        'Model' => array('username' => '', 'password' => '', 'valid' => '0'),
761
                        '_Token' => compact('key', 'fields', 'unlocked')
762
                );
763

    
764
                $result = $this->Controller->Security->validatePost($this->Controller);
765
                $this->assertTrue($result);
766
        }
767

    
768
/**
769
 * testValidatePostHidden method
770
 *
771
 * @return void
772
 */
773
        public function testValidatePostHidden() {
774
                $this->Controller->Security->startup($this->Controller);
775
                $key = $this->Controller->request->params['_Token']['key'];
776
                $fields = 'baaf832a714b39a0618238ac89c7065fc8ec853e%3AModel.hidden%7CModel.other_hidden';
777
                $unlocked = '';
778

    
779
                $this->Controller->request->data = array(
780
                        'Model' => array(
781
                                'username' => '', 'password' => '', 'hidden' => '0',
782
                                'other_hidden' => 'some hidden value'
783
                        ),
784
                        '_Token' => compact('key', 'fields', 'unlocked')
785
                );
786
                $result = $this->Controller->Security->validatePost($this->Controller);
787
                $this->assertTrue($result);
788
        }
789

    
790
/**
791
 * testValidatePostWithDisabledFields method
792
 *
793
 * @return void
794
 */
795
        public function testValidatePostWithDisabledFields() {
796
                $this->Controller->Security->disabledFields = array('Model.username', 'Model.password');
797
                $this->Controller->Security->startup($this->Controller);
798
                $key = $this->Controller->request->params['_Token']['key'];
799
                $fields = 'aa7f254ebd8bf2ef118bc5ca1e191d1ae96857f5%3AModel.hidden';
800
                $unlocked = '';
801

    
802
                $this->Controller->request->data = array(
803
                        'Model' => array(
804
                                'username' => '', 'password' => '', 'hidden' => '0'
805
                        ),
806
                        '_Token' => compact('fields', 'key', 'unlocked')
807
                );
808

    
809
                $result = $this->Controller->Security->validatePost($this->Controller);
810
                $this->assertTrue($result);
811
        }
812

    
813
/**
814
 * test validating post data with posted unlocked fields.
815
 *
816
 * @return void
817
 */
818
        public function testValidatePostDisabledFieldsInData() {
819
                $this->Controller->Security->startup($this->Controller);
820
                $key = $this->Controller->request->params['_Token']['key'];
821
                $unlocked = 'Model.username';
822
                $fields = array('Model.hidden', 'Model.password');
823
                $fields = urlencode(Security::hash(
824
                        '/posts/index' .
825
                        serialize($fields) .
826
                        $unlocked .
827
                        Configure::read('Security.salt'))
828
                );
829

    
830
                $this->Controller->request->data = array(
831
                        'Model' => array(
832
                                'username' => 'mark',
833
                                'password' => 'sekret',
834
                                'hidden' => '0'
835
                        ),
836
                        '_Token' => compact('fields', 'key', 'unlocked')
837
                );
838

    
839
                $result = $this->Controller->Security->validatePost($this->Controller);
840
                $this->assertTrue($result);
841
        }
842

    
843
/**
844
 * test that missing 'unlocked' input causes failure
845
 *
846
 * @return void
847
 */
848
        public function testValidatePostFailNoDisabled() {
849
                $this->Controller->Security->startup($this->Controller);
850
                $key = $this->Controller->request->params['_Token']['key'];
851
                $fields = array('Model.hidden', 'Model.password', 'Model.username');
852
                $fields = urlencode(Security::hash(serialize($fields) . Configure::read('Security.salt')));
853

    
854
                $this->Controller->request->data = array(
855
                        'Model' => array(
856
                                'username' => 'mark',
857
                                'password' => 'sekret',
858
                                'hidden' => '0'
859
                        ),
860
                        '_Token' => compact('fields', 'key')
861
                );
862

    
863
                $result = $this->Controller->Security->validatePost($this->Controller);
864
                $this->assertFalse($result);
865
        }
866

    
867
/**
868
 * Test that validatePost fails when unlocked fields are changed.
869
 *
870
 * @return void
871
 */
872
        public function testValidatePostFailDisabledFieldTampering() {
873
                $this->Controller->Security->startup($this->Controller);
874
                $key = $this->Controller->request->params['_Token']['key'];
875
                $unlocked = 'Model.username';
876
                $fields = array('Model.hidden', 'Model.password');
877
                $fields = urlencode(Security::hash(serialize($fields) . $unlocked . Configure::read('Security.salt')));
878

    
879
                // Tamper the values.
880
                $unlocked = 'Model.username|Model.password';
881

    
882
                $this->Controller->request->data = array(
883
                        'Model' => array(
884
                                'username' => 'mark',
885
                                'password' => 'sekret',
886
                                'hidden' => '0'
887
                        ),
888
                        '_Token' => compact('fields', 'key', 'unlocked')
889
                );
890

    
891
                $result = $this->Controller->Security->validatePost($this->Controller);
892
                $this->assertFalse($result);
893
        }
894

    
895
/**
896
 * testValidateHiddenMultipleModel method
897
 *
898
 * @return void
899
 */
900
        public function testValidateHiddenMultipleModel() {
901
                $this->Controller->Security->startup($this->Controller);
902
                $key = $this->Controller->request->params['_Token']['key'];
903
                $fields = '38dd8a37bbb52e67ee4eb812bf1725a6a18b989b%3AModel.valid%7CModel2.valid%7CModel3.valid';
904
                $unlocked = '';
905

    
906
                $this->Controller->request->data = array(
907
                        'Model' => array('username' => '', 'password' => '', 'valid' => '0'),
908
                        'Model2' => array('valid' => '0'),
909
                        'Model3' => array('valid' => '0'),
910
                        '_Token' => compact('key', 'fields', 'unlocked')
911
                );
912
                $result = $this->Controller->Security->validatePost($this->Controller);
913
                $this->assertTrue($result);
914
        }
915

    
916
/**
917
 * testValidateHasManyModel method
918
 *
919
 * @return void
920
 */
921
        public function testValidateHasManyModel() {
922
                $this->Controller->Security->startup($this->Controller);
923
                $key = $this->Controller->request->params['_Token']['key'];
924
                $fields = 'dcef68de6634c60d2e60484ad0e2faec003456e6%3AModel.0.hidden%7CModel.0.valid';
925
                $fields .= '%7CModel.1.hidden%7CModel.1.valid';
926
                $unlocked = '';
927

    
928
                $this->Controller->request->data = array(
929
                        'Model' => array(
930
                                array(
931
                                        'username' => 'username', 'password' => 'password',
932
                                        'hidden' => 'value', 'valid' => '0'
933
                                ),
934
                                array(
935
                                        'username' => 'username', 'password' => 'password',
936
                                        'hidden' => 'value', 'valid' => '0'
937
                                )
938
                        ),
939
                        '_Token' => compact('key', 'fields', 'unlocked')
940
                );
941

    
942
                $result = $this->Controller->Security->validatePost($this->Controller);
943
                $this->assertTrue($result);
944
        }
945

    
946
/**
947
 * testValidateHasManyRecordsPass method
948
 *
949
 * @return void
950
 */
951
        public function testValidateHasManyRecordsPass() {
952
                $this->Controller->Security->startup($this->Controller);
953
                $key = $this->Controller->request->params['_Token']['key'];
954
                $fields = '8b6880fbbd4b69279155f899652ecffdd9b4c5a1%3AAddress.0.id%7CAddress.0.primary%7C';
955
                $fields .= 'Address.1.id%7CAddress.1.primary';
956
                $unlocked = '';
957

    
958
                $this->Controller->request->data = array(
959
                        'Address' => array(
960
                                0 => array(
961
                                        'id' => '123',
962
                                        'title' => 'home',
963
                                        'first_name' => 'Bilbo',
964
                                        'last_name' => 'Baggins',
965
                                        'address' => '23 Bag end way',
966
                                        'city' => 'the shire',
967
                                        'phone' => 'N/A',
968
                                        'primary' => '1',
969
                                ),
970
                                1 => array(
971
                                        'id' => '124',
972
                                        'title' => 'home',
973
                                        'first_name' => 'Frodo',
974
                                        'last_name' => 'Baggins',
975
                                        'address' => '50 Bag end way',
976
                                        'city' => 'the shire',
977
                                        'phone' => 'N/A',
978
                                        'primary' => '1'
979
                                )
980
                        ),
981
                        '_Token' => compact('key', 'fields', 'unlocked')
982
                );
983

    
984
                $result = $this->Controller->Security->validatePost($this->Controller);
985
                $this->assertTrue($result);
986
        }
987

    
988
/**
989
 * Test that values like Foo.0.1
990
 *
991
 * @return void
992
 */
993
        public function testValidateNestedNumericSets() {
994
                $this->Controller->Security->startup($this->Controller);
995
                $key = $this->Controller->request->params['_Token']['key'];
996
                $unlocked = '';
997
                $hashFields = array('TaxonomyData');
998
                $fields = urlencode(
999
                        Security::hash(
1000
                        '/posts/index' .
1001
                        serialize($hashFields) .
1002
                        $unlocked .
1003
                        Configure::read('Security.salt'), 'sha1')
1004
                );
1005

    
1006
                $this->Controller->request->data = array(
1007
                        'TaxonomyData' => array(
1008
                                1 => array(array(2)),
1009
                                2 => array(array(3))
1010
                        ),
1011
                        '_Token' => compact('key', 'fields', 'unlocked')
1012
                );
1013
                $result = $this->Controller->Security->validatePost($this->Controller);
1014
                $this->assertTrue($result);
1015
        }
1016

    
1017
/**
1018
 * testValidateHasManyRecords method
1019
 *
1020
 * validatePost should fail, hidden fields have been changed.
1021
 *
1022
 * @return void
1023
 */
1024
        public function testValidateHasManyRecordsFail() {
1025
                $this->Controller->Security->startup($this->Controller);
1026
                $key = $this->Controller->request->params['_Token']['key'];
1027
                $fields = '7a203edb3d345bbf38fe0dccae960da8842e11d7%3AAddress.0.id%7CAddress.0.primary%7C';
1028
                $fields .= 'Address.1.id%7CAddress.1.primary';
1029
                $unlocked = '';
1030

    
1031
                $this->Controller->request->data = array(
1032
                        'Address' => array(
1033
                                0 => array(
1034
                                        'id' => '123',
1035
                                        'title' => 'home',
1036
                                        'first_name' => 'Bilbo',
1037
                                        'last_name' => 'Baggins',
1038
                                        'address' => '23 Bag end way',
1039
                                        'city' => 'the shire',
1040
                                        'phone' => 'N/A',
1041
                                        'primary' => '5',
1042
                                ),
1043
                                1 => array(
1044
                                        'id' => '124',
1045
                                        'title' => 'home',
1046
                                        'first_name' => 'Frodo',
1047
                                        'last_name' => 'Baggins',
1048
                                        'address' => '50 Bag end way',
1049
                                        'city' => 'the shire',
1050
                                        'phone' => 'N/A',
1051
                                        'primary' => '1'
1052
                                )
1053
                        ),
1054
                        '_Token' => compact('key', 'fields', 'unlocked')
1055
                );
1056

    
1057
                $result = $this->Controller->Security->validatePost($this->Controller);
1058
                $this->assertFalse($result);
1059
        }
1060

    
1061
/**
1062
 * testFormDisabledFields method
1063
 *
1064
 * @return void
1065
 */
1066
        public function testFormDisabledFields() {
1067
                $this->Controller->Security->startup($this->Controller);
1068
                $key = $this->Controller->request->params['_Token']['key'];
1069
                $fields = '216ee717efd1a251a6d6e9efbb96005a9d09f1eb%3An%3A0%3A%7B%7D';
1070
                $unlocked = '';
1071

    
1072
                $this->Controller->request->data = array(
1073
                        'MyModel' => array('name' => 'some data'),
1074
                        '_Token' => compact('key', 'fields', 'unlocked')
1075
                );
1076
                $result = $this->Controller->Security->validatePost($this->Controller);
1077
                $this->assertFalse($result);
1078

    
1079
                $this->Controller->Security->startup($this->Controller);
1080
                $this->Controller->Security->disabledFields = array('MyModel.name');
1081
                $key = $this->Controller->request->params['_Token']['key'];
1082

    
1083
                $this->Controller->request->data = array(
1084
                        'MyModel' => array('name' => 'some data'),
1085
                        '_Token' => compact('key', 'fields', 'unlocked')
1086
                );
1087

    
1088
                $result = $this->Controller->Security->validatePost($this->Controller);
1089
                $this->assertTrue($result);
1090
        }
1091

    
1092
/**
1093
 * testRadio method
1094
 *
1095
 * @return void
1096
 */
1097
        public function testValidatePostRadio() {
1098
                $this->Controller->Security->startup($this->Controller);
1099
                $key = $this->Controller->request->params['_Token']['key'];
1100
                $fields = '3be63770e7953c6d2119f5377a9303372040f66f%3An%3A0%3A%7B%7D';
1101
                $unlocked = '';
1102

    
1103
                $this->Controller->request->data = array(
1104
                        '_Token' => compact('key', 'fields', 'unlocked')
1105
                );
1106
                $result = $this->Controller->Security->validatePost($this->Controller);
1107
                $this->assertFalse($result);
1108

    
1109
                $this->Controller->request->data = array(
1110
                        '_Token' => compact('key', 'fields', 'unlocked'),
1111
                        'Test' => array('test' => '')
1112
                );
1113
                $result = $this->Controller->Security->validatePost($this->Controller);
1114
                $this->assertTrue($result);
1115

    
1116
                $this->Controller->request->data = array(
1117
                        '_Token' => compact('key', 'fields', 'unlocked'),
1118
                        'Test' => array('test' => '1')
1119
                );
1120
                $result = $this->Controller->Security->validatePost($this->Controller);
1121
                $this->assertTrue($result);
1122

    
1123
                $this->Controller->request->data = array(
1124
                        '_Token' => compact('key', 'fields', 'unlocked'),
1125
                        'Test' => array('test' => '2')
1126
                );
1127
                $result = $this->Controller->Security->validatePost($this->Controller);
1128
                $this->assertTrue($result);
1129
        }
1130

    
1131
/**
1132
 * test validatePost uses here() as a hash input.
1133
 *
1134
 * @return void
1135
 */
1136
        public function testValidatePostUrlAsHashInput() {
1137
                $this->Controller->Security->startup($this->Controller);
1138

    
1139
                $key = $this->Controller->request->params['_Token']['key'];
1140
                $fields = '5415d31b4483c1e09ddb58d2a91ba9650b12aa83%3A';
1141
                $unlocked = '';
1142

    
1143
                $this->Controller->request->data = array(
1144
                        'Model' => array('username' => '', 'password' => ''),
1145
                        '_Token' => compact('key', 'fields', 'unlocked')
1146
                );
1147
                $this->assertTrue($this->Controller->Security->validatePost($this->Controller));
1148

    
1149
                $request = $this->getMock('CakeRequest', array('here'), array('articles/edit/1', false));
1150
                $request->expects($this->at(0))
1151
                        ->method('here')
1152
                        ->will($this->returnValue('/posts/index?page=1'));
1153
                $request->expects($this->at(1))
1154
                        ->method('here')
1155
                        ->will($this->returnValue('/posts/edit/1'));
1156

    
1157
                $this->Controller->Security->request = $request;
1158
                $this->assertFalse($this->Controller->Security->validatePost($this->Controller));
1159
                $this->assertFalse($this->Controller->Security->validatePost($this->Controller));
1160
        }
1161

    
1162
/**
1163
 * test that a requestAction's controller will have the _Token appended to
1164
 * the params.
1165
 *
1166
 * @return void
1167
 * @see https://cakephp.lighthouseapp.com/projects/42648/tickets/68
1168
 */
1169
        public function testSettingTokenForRequestAction() {
1170
                $this->Controller->Security->startup($this->Controller);
1171
                $key = $this->Controller->request->params['_Token']['key'];
1172

    
1173
                $this->Controller->params['requested'] = 1;
1174
                unset($this->Controller->request->params['_Token']);
1175

    
1176
                $this->Controller->Security->startup($this->Controller);
1177
                $this->assertEquals($this->Controller->request->params['_Token']['key'], $key);
1178
        }
1179

    
1180
/**
1181
 * test that blackhole doesn't delete the _Token session key so repeat data submissions
1182
 * stay blackholed.
1183
 *
1184
 * @link https://cakephp.lighthouseapp.com/projects/42648/tickets/214
1185
 * @return void
1186
 */
1187
        public function testBlackHoleNotDeletingSessionInformation() {
1188
                $this->Controller->Security->startup($this->Controller);
1189

    
1190
                $this->Controller->Security->blackHole($this->Controller, 'auth');
1191
                $this->assertTrue($this->Controller->Security->Session->check('_Token'), '_Token was deleted by blackHole %s');
1192
        }
1193

    
1194
/**
1195
 * test that csrf checks are skipped for request action.
1196
 *
1197
 * @return void
1198
 */
1199
        public function testCsrfSkipRequestAction() {
1200
                $_SERVER['REQUEST_METHOD'] = 'POST';
1201

    
1202
                $this->Security->validatePost = false;
1203
                $this->Security->csrfCheck = true;
1204
                $this->Security->csrfExpires = '+10 minutes';
1205
                $this->Controller->request->params['requested'] = 1;
1206
                $this->Security->startup($this->Controller);
1207

    
1208
                $this->assertFalse($this->Controller->failed, 'fail() was called.');
1209
        }
1210

    
1211
/**
1212
 * test setting
1213
 *
1214
 * @return void
1215
 */
1216
        public function testCsrfSettings() {
1217
                $this->Security->validatePost = false;
1218
                $this->Security->csrfCheck = true;
1219
                $this->Security->csrfExpires = '+10 minutes';
1220
                $this->Security->startup($this->Controller);
1221

    
1222
                $token = $this->Security->Session->read('_Token');
1223
                $this->assertEquals(1, count($token['csrfTokens']), 'Missing the csrf token.');
1224
                $this->assertEquals(strtotime('+10 minutes'), current($token['csrfTokens']), 'Token expiry does not match');
1225
                $this->assertEquals(array('key', 'unlockedFields'), array_keys($this->Controller->request->params['_Token']), 'Keys don not match');
1226
        }
1227

    
1228
/**
1229
 * Test setting multiple nonces, when startup() is called more than once, (ie more than one request.)
1230
 *
1231
 * @return void
1232
 */
1233
        public function testCsrfSettingMultipleNonces() {
1234
                $this->Security->validatePost = false;
1235
                $this->Security->csrfCheck = true;
1236
                $this->Security->csrfExpires = '+10 minutes';
1237
                $csrfExpires = strtotime('+10 minutes');
1238
                $this->Security->startup($this->Controller);
1239
                $this->Security->startup($this->Controller);
1240

    
1241
                $token = $this->Security->Session->read('_Token');
1242
                $this->assertEquals(2, count($token['csrfTokens']), 'Missing the csrf token.');
1243
                foreach ($token['csrfTokens'] as $expires) {
1244
                        $this->assertWithinMargin($expires, $csrfExpires, 2, 'Token expiry does not match');
1245
                }
1246
        }
1247

    
1248
/**
1249
 * test that nonces are consumed by form submits.
1250
 *
1251
 * @return void
1252
 */
1253
        public function testCsrfNonceConsumption() {
1254
                $this->Security->validatePost = false;
1255
                $this->Security->csrfCheck = true;
1256
                $this->Security->csrfExpires = '+10 minutes';
1257

    
1258
                $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('+10 minutes')));
1259

    
1260
                $this->Controller->request->params['action'] = 'index';
1261
                $this->Controller->request->data = array(
1262
                        '_Token' => array(
1263
                                'key' => 'nonce1'
1264
                        ),
1265
                        'Post' => array(
1266
                                'title' => 'Woot'
1267
                        )
1268
                );
1269
                $this->Security->startup($this->Controller);
1270
                $token = $this->Security->Session->read('_Token');
1271
                $this->assertFalse(isset($token['csrfTokens']['nonce1']), 'Token was not consumed');
1272
        }
1273

    
1274
/**
1275
 * tests that reusable CSRF-token expiry is renewed
1276
 */
1277
        public function testCsrfReusableTokenRenewal() {
1278
                $this->Security->validatePost = false;
1279
                $this->Security->csrfCheck = true;
1280
                $this->Security->csrfUseOnce = false;
1281
                $csrfExpires = '+10 minutes';
1282
                $this->Security->csrfExpires = $csrfExpires;
1283

    
1284
                $this->Security->Session->write('_Token.csrfTokens', array('token' => strtotime('+1 minutes')));
1285

    
1286
                $this->Security->startup($this->Controller);
1287
                $tokens = $this->Security->Session->read('_Token.csrfTokens');
1288
                $this->assertWithinMargin($tokens['token'], strtotime($csrfExpires), 2, 'Token expiry was not renewed');
1289
        }
1290

    
1291
/**
1292
 * test that expired values in the csrfTokens are cleaned up.
1293
 *
1294
 * @return void
1295
 */
1296
        public function testCsrfNonceVacuum() {
1297
                $this->Security->validatePost = false;
1298
                $this->Security->csrfCheck = true;
1299
                $this->Security->csrfExpires = '+10 minutes';
1300

    
1301
                $this->Security->Session->write('_Token.csrfTokens', array(
1302
                        'valid' => strtotime('+30 minutes'),
1303
                        'poof' => strtotime('-11 minutes'),
1304
                        'dust' => strtotime('-20 minutes')
1305
                ));
1306
                $this->Security->startup($this->Controller);
1307
                $tokens = $this->Security->Session->read('_Token.csrfTokens');
1308
                $this->assertEquals(2, count($tokens), 'Too many tokens left behind');
1309
                $this->assertNotEmpty('valid', $tokens, 'Valid token was removed.');
1310
        }
1311

    
1312
/**
1313
 * test that when the key is missing the request is blackHoled
1314
 *
1315
 * @return void
1316
 */
1317
        public function testCsrfBlackHoleOnKeyMismatch() {
1318
                $this->Security->validatePost = false;
1319
                $this->Security->csrfCheck = true;
1320
                $this->Security->csrfExpires = '+10 minutes';
1321

    
1322
                $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('+10 minutes')));
1323

    
1324
                $this->Controller->request->params['action'] = 'index';
1325
                $this->Controller->request->data = array(
1326
                        '_Token' => array(
1327
                                'key' => 'not the right value'
1328
                        ),
1329
                        'Post' => array(
1330
                                'title' => 'Woot'
1331
                        )
1332
                );
1333
                $this->Security->startup($this->Controller);
1334
                $this->assertTrue($this->Controller->failed, 'fail() was not called.');
1335
        }
1336

    
1337
/**
1338
 * test that when the key is missing the request is blackHoled
1339
 *
1340
 * @return void
1341
 */
1342
        public function testCsrfBlackHoleOnExpiredKey() {
1343
                $this->Security->validatePost = false;
1344
                $this->Security->csrfCheck = true;
1345
                $this->Security->csrfExpires = '+10 minutes';
1346

    
1347
                $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('-5 minutes')));
1348

    
1349
                $this->Controller->request->params['action'] = 'index';
1350
                $this->Controller->request->data = array(
1351
                        '_Token' => array(
1352
                                'key' => 'nonce1'
1353
                        ),
1354
                        'Post' => array(
1355
                                'title' => 'Woot'
1356
                        )
1357
                );
1358
                $this->Security->startup($this->Controller);
1359
                $this->assertTrue($this->Controller->failed, 'fail() was not called.');
1360
        }
1361

    
1362
/**
1363
 * test that csrfUseOnce = false works.
1364
 *
1365
 * @return void
1366
 */
1367
        public function testCsrfNotUseOnce() {
1368
                $this->Security->validatePost = false;
1369
                $this->Security->csrfCheck = true;
1370
                $this->Security->csrfUseOnce = false;
1371
                $this->Security->csrfExpires = '+10 minutes';
1372

    
1373
                // Generate one token
1374
                $this->Security->startup($this->Controller);
1375
                $token = $this->Security->Session->read('_Token.csrfTokens');
1376
                $this->assertEquals(1, count($token), 'Should only be one token.');
1377

    
1378
                $this->Security->startup($this->Controller);
1379
                $tokenTwo = $this->Security->Session->read('_Token.csrfTokens');
1380
                $this->assertEquals(1, count($tokenTwo), 'Should only be one token.');
1381
                $this->assertEquals($token, $tokenTwo, 'Tokens should not be different.');
1382

    
1383
                $key = $this->Controller->request->params['_Token']['key'];
1384
                $this->assertEquals(array($key), array_keys($token), '_Token.key and csrfToken do not match request will blackhole.');
1385
        }
1386

    
1387
/**
1388
 * ensure that longer session tokens are not consumed
1389
 *
1390
 * @return void
1391
 */
1392
        public function testCsrfNotUseOnceValidationLeavingToken() {
1393
                $this->Security->validatePost = false;
1394
                $this->Security->csrfCheck = true;
1395
                $this->Security->csrfUseOnce = false;
1396
                $this->Security->csrfExpires = '+10 minutes';
1397

    
1398
                $this->Security->Session->write('_Token.csrfTokens', array('nonce1' => strtotime('+10 minutes')));
1399

    
1400
                $this->Controller->request = $this->getMock('CakeRequest', array('is'));
1401
                $this->Controller->request->params['action'] = 'index';
1402
                $this->Controller->request->data = array(
1403
                        '_Token' => array(
1404
                                'key' => 'nonce1'
1405
                        ),
1406
                        'Post' => array(
1407
                                'title' => 'Woot'
1408
                        )
1409
                );
1410
                $this->Security->startup($this->Controller);
1411
                $token = $this->Security->Session->read('_Token');
1412
                $this->assertTrue(isset($token['csrfTokens']['nonce1']), 'Token was consumed');
1413
        }
1414

    
1415
/**
1416
 * Test generateToken()
1417
 *
1418
 * @return void
1419
 */
1420
        public function testGenerateToken() {
1421
                $request = $this->Controller->request;
1422
                $this->Security->generateToken($request);
1423

    
1424
                $this->assertNotEmpty($request->params['_Token']);
1425
                $this->assertTrue(isset($request->params['_Token']['unlockedFields']));
1426
                $this->assertTrue(isset($request->params['_Token']['key']));
1427
        }
1428

    
1429
/**
1430
 * Test the limiting of CSRF tokens.
1431
 *
1432
 * @return void
1433
 */
1434
        public function testCsrfLimit() {
1435
                $this->Security->csrfLimit = 3;
1436
                $time = strtotime('+10 minutes');
1437
                $tokens = array(
1438
                        '1' => $time,
1439
                        '2' => $time,
1440
                        '3' => $time,
1441
                        '4' => $time,
1442
                        '5' => $time,
1443
                );
1444
                $this->Security->Session->write('_Token', array('csrfTokens' => $tokens));
1445
                $this->Security->generateToken($this->Controller->request);
1446
                $result = $this->Security->Session->read('_Token.csrfTokens');
1447

    
1448
                $this->assertFalse(isset($result['1']));
1449
                $this->assertFalse(isset($result['2']));
1450
                $this->assertFalse(isset($result['3']));
1451
                $this->assertTrue(isset($result['4']));
1452
                $this->assertTrue(isset($result['5']));
1453
        }
1454

    
1455
/**
1456
 * Test unlocked actions
1457
 *
1458
 * @return void
1459
 */
1460
        public function testUnlockedActions() {
1461
                $_SERVER['REQUEST_METHOD'] = 'POST';
1462
                $this->Controller->request->data = array('data');
1463
                $this->Controller->Security->unlockedActions = 'index';
1464
                $this->Controller->Security->blackHoleCallback = null;
1465
                $result = $this->Controller->Security->startup($this->Controller);
1466
                $this->assertNull($result);
1467
        }
1468
}