1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SilverStripe\Control\Tests; |
4
|
|
|
|
5
|
|
|
use http\Exception\BadMessageException; |
6
|
|
|
use SilverStripe\Control\Cookie; |
7
|
|
|
use SilverStripe\Control\Session; |
8
|
|
|
use SilverStripe\Dev\SapphireTest; |
9
|
|
|
use SilverStripe\Control\HTTPRequest; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Tests to cover the {@link Session} class |
13
|
|
|
*/ |
14
|
|
|
class SessionTest extends SapphireTest |
15
|
|
|
{ |
16
|
|
|
/** |
17
|
|
|
* @var Session |
18
|
|
|
*/ |
19
|
|
|
protected $session = null; |
20
|
|
|
|
21
|
|
|
protected function setUp() |
22
|
|
|
{ |
23
|
|
|
$this->session = new Session([]); |
24
|
|
|
return parent::setUp(); |
25
|
|
|
} |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @runInSeparateProcess |
29
|
|
|
* @preserveGlobalState disabled |
30
|
|
|
*/ |
31
|
|
|
public function testInitDoesNotStartSessionWithoutIdentifier() |
32
|
|
|
{ |
33
|
|
|
$req = new HTTPRequest('GET', '/'); |
34
|
|
|
$session = new Session(null); // unstarted session |
35
|
|
|
$session->init($req); |
36
|
|
|
$this->assertFalse($session->isStarted()); |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @runInSeparateProcess |
41
|
|
|
* @preserveGlobalState disabled |
42
|
|
|
*/ |
43
|
|
|
public function testInitStartsSessionWithIdentifier() |
44
|
|
|
{ |
45
|
|
|
$req = new HTTPRequest('GET', '/'); |
46
|
|
|
Cookie::set(session_name(), '1234'); |
47
|
|
|
$session = new Session(null); // unstarted session |
48
|
|
|
$session->init($req); |
49
|
|
|
$this->assertTrue($session->isStarted()); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @runInSeparateProcess |
54
|
|
|
* @preserveGlobalState disabled |
55
|
|
|
*/ |
56
|
|
|
public function testInitStartsSessionWithData() |
57
|
|
|
{ |
58
|
|
|
$req = new HTTPRequest('GET', '/'); |
59
|
|
|
$session = new Session([]); |
60
|
|
|
$session->init($req); |
61
|
|
|
$this->assertTrue($session->isStarted()); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @runInSeparateProcess |
66
|
|
|
* @preserveGlobalState disabled |
67
|
|
|
*/ |
68
|
|
|
public function testStartUsesDefaultCookieNameWithHttp() |
69
|
|
|
{ |
70
|
|
|
$req = (new HTTPRequest('GET', '/')) |
71
|
|
|
->setScheme('http'); |
72
|
|
|
Cookie::set(session_name(), '1234'); |
73
|
|
|
$session = new Session(null); // unstarted session |
74
|
|
|
$session->start($req); |
75
|
|
|
$this->assertNotEquals(session_name(), $session->config()->get('cookie_name_secure')); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* @runInSeparateProcess |
80
|
|
|
* @preserveGlobalState disabled |
81
|
|
|
*/ |
82
|
|
|
public function testStartUsesDefaultCookieNameWithHttpsAndCookieSecureOff() |
83
|
|
|
{ |
84
|
|
|
$req = (new HTTPRequest('GET', '/')) |
85
|
|
|
->setScheme('https'); |
86
|
|
|
Cookie::set(session_name(), '1234'); |
87
|
|
|
$session = new Session(null); // unstarted session |
88
|
|
|
$session->start($req); |
89
|
|
|
$this->assertNotEquals(session_name(), $session->config()->get('cookie_name_secure')); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* @runInSeparateProcess |
94
|
|
|
* @preserveGlobalState disabled |
95
|
|
|
*/ |
96
|
|
|
public function testStartUsesSecureCookieNameWithHttpsAndCookieSecureOn() |
97
|
|
|
{ |
98
|
|
|
$req = (new HTTPRequest('GET', '/')) |
99
|
|
|
->setScheme('https'); |
100
|
|
|
Cookie::set(session_name(), '1234'); |
101
|
|
|
$session = new Session(null); // unstarted session |
102
|
|
|
$session->config()->update('cookie_secure', true); |
103
|
|
|
$session->start($req); |
104
|
|
|
$this->assertEquals(session_name(), $session->config()->get('cookie_name_secure')); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* @runInSeparateProcess |
109
|
|
|
* @preserveGlobalState disabled |
110
|
|
|
* @expectedException BadMethodCallException |
111
|
|
|
* @expectedExceptionMessage Session has already started |
112
|
|
|
*/ |
113
|
|
|
public function testStartErrorsWhenStartingTwice() |
114
|
|
|
{ |
115
|
|
|
$req = new HTTPRequest('GET', '/'); |
116
|
|
|
$session = new Session(null); // unstarted session |
117
|
|
|
$session->start($req); |
118
|
|
|
$session->start($req); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* @runInSeparateProcess |
123
|
|
|
* @preserveGlobalState disabled |
124
|
|
|
*/ |
125
|
|
|
public function testStartRetainsInMemoryData() |
126
|
|
|
{ |
127
|
|
|
$this->markTestIncomplete('Test'); |
128
|
|
|
// TODO Figure out how to simulate session vars without a session_start() resetting them |
129
|
|
|
// $_SESSION['existing'] = true; |
|
|
|
|
130
|
|
|
// $_SESSION['merge'] = 1; |
|
|
|
|
131
|
|
|
$req = new HTTPRequest('GET', '/'); |
132
|
|
|
$session = new Session(null); // unstarted session |
133
|
|
|
$session->set('new', true); |
134
|
|
|
$session->set('merge', 2); |
135
|
|
|
$session->start($req); // simulate lazy start |
136
|
|
|
$this->assertEquals( |
137
|
|
|
[ |
138
|
|
|
// 'existing' => true, |
|
|
|
|
139
|
|
|
'new' => true, |
140
|
|
|
'merge' => 2 |
141
|
|
|
], |
142
|
|
|
$session->getAll() |
143
|
|
|
); |
144
|
|
|
|
145
|
|
|
unset($_SESSION); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
public function testGetSetBasics() |
149
|
|
|
{ |
150
|
|
|
$this->session->set('Test', 'Test'); |
151
|
|
|
|
152
|
|
|
$this->assertEquals($this->session->get('Test'), 'Test'); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
public function testClearElement() |
156
|
|
|
{ |
157
|
|
|
$this->session->set('Test', 'Test'); |
158
|
|
|
$this->session->clear('Test'); |
159
|
|
|
|
160
|
|
|
$this->assertEquals($this->session->get('Test'), ''); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
public function testClearAllElements() |
164
|
|
|
{ |
165
|
|
|
$this->session->set('Test', 'Test'); |
166
|
|
|
$this->session->set('Test-1', 'Test-1'); |
167
|
|
|
|
168
|
|
|
$this->session->clearAll(); |
169
|
|
|
|
170
|
|
|
// should session get return null? The array key should probably be |
171
|
|
|
// unset from the data array |
172
|
|
|
$this->assertEquals($this->session->get('Test'), ''); |
173
|
|
|
$this->assertEquals($this->session->get('Test-1'), ''); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
public function testGetAllElements() |
177
|
|
|
{ |
178
|
|
|
$this->session->clearAll(); // Remove all session that might've been set by the test harness |
179
|
|
|
|
180
|
|
|
$this->session->set('Test', 'Test'); |
181
|
|
|
$this->session->set('Test-2', 'Test-2'); |
182
|
|
|
|
183
|
|
|
$session = $this->session->getAll(); |
184
|
|
|
unset($session['HTTP_USER_AGENT']); |
185
|
|
|
|
186
|
|
|
$this->assertEquals($session, array('Test' => 'Test', 'Test-2' => 'Test-2')); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
public function testSettingExistingDoesntClear() |
190
|
|
|
{ |
191
|
|
|
$s = new Session(array('something' => array('does' => 'exist'))); |
192
|
|
|
|
193
|
|
|
$s->set('something.does', 'exist'); |
194
|
|
|
$result = $s->changedData(); |
195
|
|
|
unset($result['HTTP_USER_AGENT']); |
196
|
|
|
$this->assertEquals(array(), $result); |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Check that changedData isn't populated with junk when clearing non-existent entries. |
201
|
|
|
*/ |
202
|
|
|
public function testClearElementThatDoesntExist() |
203
|
|
|
{ |
204
|
|
|
$s = new Session(['something' => ['does' => 'exist']]); |
205
|
|
|
$s->clear('something.doesnt.exist'); |
206
|
|
|
|
207
|
|
|
// Clear without existing data |
208
|
|
|
$data = $s->get('something.doesnt.exist'); |
209
|
|
|
$this->assertEquals(array(), $s->changedData()); |
210
|
|
|
$this->assertNull($data); |
211
|
|
|
|
212
|
|
|
// Clear with existing change |
213
|
|
|
$s->set('something-else', 'val'); |
214
|
|
|
$s->clear('something-new'); |
215
|
|
|
$data = $s->get('something-else'); |
216
|
|
|
$this->assertEquals(['something-else' => true], $s->changedData()); |
217
|
|
|
$this->assertEquals('val', $data); |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Check that changedData is populated with clearing data. |
222
|
|
|
*/ |
223
|
|
|
public function testClearElementThatDoesExist() |
224
|
|
|
{ |
225
|
|
|
$s = new Session(['something' => ['does' => 'exist']]); |
226
|
|
|
|
227
|
|
|
// Ensure keys are properly removed and not simply nullified |
228
|
|
|
$s->clear('something.does'); |
229
|
|
|
$this->assertEquals( |
230
|
|
|
['something' => ['does' => true]], |
231
|
|
|
$s->changedData() |
232
|
|
|
); |
233
|
|
|
$this->assertEquals( |
234
|
|
|
[], // 'does' removed |
235
|
|
|
$s->get('something') |
236
|
|
|
); |
237
|
|
|
|
238
|
|
|
// Clear at more specific level should also clear other changes |
239
|
|
|
$s->clear('something'); |
240
|
|
|
$this->assertEquals( |
241
|
|
|
['something' => true], |
242
|
|
|
$s->changedData() |
243
|
|
|
); |
244
|
|
|
$this->assertEquals( |
245
|
|
|
null, // Should be removed not just empty array |
246
|
|
|
$s->get('something') |
247
|
|
|
); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
public function testRequestContainsSessionId() |
251
|
|
|
{ |
252
|
|
|
$req = new HTTPRequest('GET', '/'); |
253
|
|
|
$session = new Session(null); // unstarted session |
254
|
|
|
$this->assertFalse($session->requestContainsSessionId($req)); |
255
|
|
|
Cookie::set(session_name(), '1234'); |
256
|
|
|
$this->assertTrue($session->requestContainsSessionId($req)); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
public function testRequestContainsSessionIdRespectsCookieNameSecure() |
260
|
|
|
{ |
261
|
|
|
$req = (new HTTPRequest('GET', '/')) |
262
|
|
|
->setScheme('https'); |
263
|
|
|
$session = new Session(null); // unstarted session |
264
|
|
|
Cookie::set($session->config()->get('cookie_name_secure'), '1234'); |
265
|
|
|
$session->config()->update('cookie_secure', true); |
266
|
|
|
$this->assertTrue($session->requestContainsSessionId($req)); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
public function testUserAgentLockout() |
270
|
|
|
{ |
271
|
|
|
// Set a user agent |
272
|
|
|
$req1 = new HTTPRequest('GET', '/'); |
273
|
|
|
$req1->addHeader('User-Agent', 'Test Agent'); |
274
|
|
|
|
275
|
|
|
// Generate our session |
276
|
|
|
$s = new Session(array()); |
277
|
|
|
$s->init($req1); |
278
|
|
|
$s->set('val', 123); |
279
|
|
|
$s->finalize($req1); |
280
|
|
|
|
281
|
|
|
// Change our UA |
282
|
|
|
$req2 = new HTTPRequest('GET', '/'); |
283
|
|
|
$req2->addHeader('User-Agent', 'Fake Agent'); |
284
|
|
|
|
285
|
|
|
// Verify the new session reset our values |
286
|
|
|
$s2 = new Session($s); |
287
|
|
|
$s2->init($req2); |
288
|
|
|
$this->assertNotEquals($s2->get('val'), 123); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
public function testSave() |
292
|
|
|
{ |
293
|
|
|
$request = new HTTPRequest('GET', '/'); |
294
|
|
|
|
295
|
|
|
// Test change of nested array type |
296
|
|
|
$s = new Session($_SESSION = ['something' => ['some' => 'value', 'another' => 'item']]); |
297
|
|
|
$s->set('something', 'string'); |
298
|
|
|
$s->save($request); |
299
|
|
|
$this->assertEquals( |
300
|
|
|
['something' => 'string'], |
301
|
|
|
$_SESSION |
302
|
|
|
); |
303
|
|
|
|
304
|
|
|
// Test multiple changes combine safely |
305
|
|
|
$s = new Session($_SESSION = ['something' => ['some' => 'value', 'another' => 'item']]); |
306
|
|
|
$s->set('something.another', 'newanother'); |
307
|
|
|
$s->clear('something.some'); |
308
|
|
|
$s->set('something.newkey', 'new value'); |
309
|
|
|
$s->save($request); |
310
|
|
|
$this->assertEquals( |
311
|
|
|
[ |
312
|
|
|
'something' => [ |
313
|
|
|
'another' => 'newanother', |
314
|
|
|
'newkey' => 'new value', |
315
|
|
|
] |
316
|
|
|
], |
317
|
|
|
$_SESSION |
318
|
|
|
); |
319
|
|
|
|
320
|
|
|
// Test cleared keys are restorable |
321
|
|
|
$s = new Session($_SESSION = ['bookmarks' => [ 1 => 1, 2 => 2]]); |
322
|
|
|
$s->clear('bookmarks'); |
323
|
|
|
$s->set('bookmarks', [ |
324
|
|
|
1 => 1, |
325
|
|
|
3 => 3, |
326
|
|
|
]); |
327
|
|
|
$s->save($request); |
328
|
|
|
$this->assertEquals( |
329
|
|
|
[ |
330
|
|
|
'bookmarks' => [ |
331
|
|
|
1 => 1, |
332
|
|
|
3 => 3, |
333
|
|
|
] |
334
|
|
|
], |
335
|
|
|
$_SESSION |
336
|
|
|
); |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.