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, ['Test' => 'Test', 'Test-2' => 'Test-2']); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
public function testSettingExistingDoesntClear() |
190
|
|
|
{ |
191
|
|
|
$s = new Session(['something' => ['does' => 'exist']]); |
192
|
|
|
|
193
|
|
|
$s->set('something.does', 'exist'); |
194
|
|
|
$result = $s->changedData(); |
195
|
|
|
unset($result['HTTP_USER_AGENT']); |
196
|
|
|
$this->assertEmpty($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->assertEmpty($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([]); |
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->assertEmpty($s2->get('val')); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
public function testDisabledUserAgentLockout() |
292
|
|
|
{ |
293
|
|
|
Session::config()->set('strict_user_agent_check', false); |
294
|
|
|
|
295
|
|
|
// Set a user agent |
296
|
|
|
$req1 = new HTTPRequest('GET', '/'); |
297
|
|
|
$req1->addHeader('User-Agent', 'Test Agent'); |
298
|
|
|
|
299
|
|
|
// Generate our session |
300
|
|
|
$s = new Session([]); |
301
|
|
|
$s->init($req1); |
302
|
|
|
$s->set('val', 123); |
303
|
|
|
$s->finalize($req1); |
304
|
|
|
|
305
|
|
|
// Change our UA |
306
|
|
|
$req2 = new HTTPRequest('GET', '/'); |
307
|
|
|
$req2->addHeader('User-Agent', 'Fake Agent'); |
308
|
|
|
|
309
|
|
|
// Verify the new session reset our values |
310
|
|
|
$s2 = new Session($s); |
311
|
|
|
$s2->init($req2); |
312
|
|
|
$this->assertEquals($s2->get('val'), 123); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
public function testSave() |
316
|
|
|
{ |
317
|
|
|
$request = new HTTPRequest('GET', '/'); |
318
|
|
|
|
319
|
|
|
// Test change of nested array type |
320
|
|
|
$s = new Session($_SESSION = ['something' => ['some' => 'value', 'another' => 'item']]); |
321
|
|
|
$s->set('something', 'string'); |
322
|
|
|
$s->save($request); |
323
|
|
|
$this->assertEquals( |
324
|
|
|
['something' => 'string'], |
325
|
|
|
$_SESSION |
326
|
|
|
); |
327
|
|
|
|
328
|
|
|
// Test multiple changes combine safely |
329
|
|
|
$s = new Session($_SESSION = ['something' => ['some' => 'value', 'another' => 'item']]); |
330
|
|
|
$s->set('something.another', 'newanother'); |
331
|
|
|
$s->clear('something.some'); |
332
|
|
|
$s->set('something.newkey', 'new value'); |
333
|
|
|
$s->save($request); |
334
|
|
|
$this->assertEquals( |
335
|
|
|
[ |
336
|
|
|
'something' => [ |
337
|
|
|
'another' => 'newanother', |
338
|
|
|
'newkey' => 'new value', |
339
|
|
|
], |
340
|
|
|
], |
341
|
|
|
$_SESSION |
342
|
|
|
); |
343
|
|
|
|
344
|
|
|
// Test cleared keys are restorable |
345
|
|
|
$s = new Session($_SESSION = ['bookmarks' => [1 => 1, 2 => 2]]); |
346
|
|
|
$s->clear('bookmarks'); |
347
|
|
|
$s->set('bookmarks', [ |
348
|
|
|
1 => 1, |
349
|
|
|
3 => 3, |
350
|
|
|
]); |
351
|
|
|
$s->save($request); |
352
|
|
|
$this->assertEquals( |
353
|
|
|
[ |
354
|
|
|
'bookmarks' => [ |
355
|
|
|
1 => 1, |
356
|
|
|
3 => 3, |
357
|
|
|
], |
358
|
|
|
], |
359
|
|
|
$_SESSION |
360
|
|
|
); |
361
|
|
|
} |
362
|
|
|
} |
363
|
|
|
|