comparison core/modules/system/src/Tests/Session/SessionTest.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 namespace Drupal\system\Tests\Session;
4
5 use Drupal\simpletest\WebTestBase;
6
7 /**
8 * Drupal session handling tests.
9 *
10 * @group Session
11 */
12 class SessionTest extends WebTestBase {
13
14 /**
15 * Modules to enable.
16 *
17 * @var array
18 */
19 public static $modules = ['session_test'];
20
21 protected $dumpHeaders = TRUE;
22
23 /**
24 * Tests for \Drupal\Core\Session\WriteSafeSessionHandler::setSessionWritable()
25 * ::isSessionWritable and \Drupal\Core\Session\SessionManager::regenerate().
26 */
27 public function testSessionSaveRegenerate() {
28 $session_handler = $this->container->get('session_handler.write_safe');
29 $this->assertTrue($session_handler->isSessionWritable(), 'session_handler->isSessionWritable() initially returns TRUE.');
30 $session_handler->setSessionWritable(FALSE);
31 $this->assertFalse($session_handler->isSessionWritable(), '$session_handler->isSessionWritable() returns FALSE after disabling.');
32 $session_handler->setSessionWritable(TRUE);
33 $this->assertTrue($session_handler->isSessionWritable(), '$session_handler->isSessionWritable() returns TRUE after enabling.');
34
35 // Test session hardening code from SA-2008-044.
36 $user = $this->drupalCreateUser();
37
38 // Enable sessions.
39 $this->sessionReset($user->id());
40
41 // Make sure the session cookie is set as HttpOnly.
42 $this->drupalLogin($user);
43 $this->assertTrue(preg_match('/HttpOnly/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie is set as HttpOnly.');
44 $this->drupalLogout();
45
46 // Verify that the session is regenerated if a module calls exit
47 // in hook_user_login().
48 $user->name = 'session_test_user';
49 $user->save();
50 $this->drupalGet('session-test/id');
51 $matches = [];
52 preg_match('/\s*session_id:(.*)\n/', $this->getRawContent(), $matches);
53 $this->assertTrue(!empty($matches[1]), 'Found session ID before logging in.');
54 $original_session = $matches[1];
55
56 // We cannot use $this->drupalLogin($user); because we exit in
57 // session_test_user_login() which breaks a normal assertion.
58 $edit = [
59 'name' => $user->getUsername(),
60 'pass' => $user->pass_raw
61 ];
62 $this->drupalPostForm('user/login', $edit, t('Log in'));
63 $this->drupalGet('user');
64 $pass = $this->assertText($user->getUsername(), format_string('Found name: %name', ['%name' => $user->getUsername()]), 'User login');
65 $this->_logged_in = $pass;
66
67 $this->drupalGet('session-test/id');
68 $matches = [];
69 preg_match('/\s*session_id:(.*)\n/', $this->getRawContent(), $matches);
70 $this->assertTrue(!empty($matches[1]), 'Found session ID after logging in.');
71 $this->assertTrue($matches[1] != $original_session, 'Session ID changed after login.');
72 }
73
74 /**
75 * Test data persistence via the session_test module callbacks.
76 */
77 public function testDataPersistence() {
78 $user = $this->drupalCreateUser([]);
79 // Enable sessions.
80 $this->sessionReset($user->id());
81
82 $this->drupalLogin($user);
83
84 $value_1 = $this->randomMachineName();
85 $this->drupalGet('session-test/set/' . $value_1);
86 $this->assertText($value_1, 'The session value was stored.', 'Session');
87 $this->drupalGet('session-test/get');
88 $this->assertText($value_1, 'Session correctly returned the stored data for an authenticated user.', 'Session');
89
90 // Attempt to write over val_1. If drupal_save_session(FALSE) is working.
91 // properly, val_1 will still be set.
92 $value_2 = $this->randomMachineName();
93 $this->drupalGet('session-test/no-set/' . $value_2);
94 $this->assertText($value_2, 'The session value was correctly passed to session-test/no-set.', 'Session');
95 $this->drupalGet('session-test/get');
96 $this->assertText($value_1, 'Session data is not saved for drupal_save_session(FALSE).', 'Session');
97
98 // Switch browser cookie to anonymous user, then back to user 1.
99 $this->sessionReset();
100 $this->sessionReset($user->id());
101 $this->assertText($value_1, 'Session data persists through browser close.', 'Session');
102
103 // Logout the user and make sure the stored value no longer persists.
104 $this->drupalLogout();
105 $this->sessionReset();
106 $this->drupalGet('session-test/get');
107 $this->assertNoText($value_1, "After logout, previous user's session data is not available.", 'Session');
108
109 // Now try to store some data as an anonymous user.
110 $value_3 = $this->randomMachineName();
111 $this->drupalGet('session-test/set/' . $value_3);
112 $this->assertText($value_3, 'Session data stored for anonymous user.', 'Session');
113 $this->drupalGet('session-test/get');
114 $this->assertText($value_3, 'Session correctly returned the stored data for an anonymous user.', 'Session');
115
116 // Try to store data when drupal_save_session(FALSE).
117 $value_4 = $this->randomMachineName();
118 $this->drupalGet('session-test/no-set/' . $value_4);
119 $this->assertText($value_4, 'The session value was correctly passed to session-test/no-set.', 'Session');
120 $this->drupalGet('session-test/get');
121 $this->assertText($value_3, 'Session data is not saved for drupal_save_session(FALSE).', 'Session');
122
123 // Login, the data should persist.
124 $this->drupalLogin($user);
125 $this->sessionReset($user->id());
126 $this->drupalGet('session-test/get');
127 $this->assertNoText($value_1, 'Session has persisted for an authenticated user after logging out and then back in.', 'Session');
128
129 // Change session and create another user.
130 $user2 = $this->drupalCreateUser([]);
131 $this->sessionReset($user2->id());
132 $this->drupalLogin($user2);
133 }
134
135 /**
136 * Tests storing data in Session() object.
137 */
138 public function testSessionPersistenceOnLogin() {
139 // Store information via hook_user_login().
140 $user = $this->drupalCreateUser();
141 $this->drupalLogin($user);
142 // Test property added to session object form hook_user_login().
143 $this->drupalGet('session-test/get-from-session-object');
144 $this->assertText('foobar', 'Session data is saved in Session() object.', 'Session');
145 }
146
147 /**
148 * Test that empty anonymous sessions are destroyed.
149 */
150 public function testEmptyAnonymousSession() {
151 // Disable the dynamic_page_cache module; it'd cause session_test's debug
152 // output (that is added in
153 // SessionTestSubscriber::onKernelResponseSessionTest()) to not be added.
154 $this->container->get('module_installer')->uninstall(['dynamic_page_cache']);
155
156 // Verify that no session is automatically created for anonymous user when
157 // page caching is disabled.
158 $this->container->get('module_installer')->uninstall(['page_cache']);
159 $this->drupalGet('');
160 $this->assertSessionCookie(FALSE);
161 $this->assertSessionEmpty(TRUE);
162
163 // The same behavior is expected when caching is enabled.
164 $this->container->get('module_installer')->install(['page_cache']);
165 $config = $this->config('system.performance');
166 $config->set('cache.page.max_age', 300);
167 $config->save();
168 $this->drupalGet('');
169 $this->assertSessionCookie(FALSE);
170 // @todo Reinstate when REQUEST and RESPONSE events fire for cached pages.
171 // $this->assertSessionEmpty(TRUE);
172 $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
173
174 // Start a new session by setting a message.
175 $this->drupalGet('session-test/set-message');
176 $this->assertSessionCookie(TRUE);
177 $this->assertTrue($this->drupalGetHeader('Set-Cookie'), 'New session was started.');
178
179 // Display the message, during the same request the session is destroyed
180 // and the session cookie is unset.
181 $this->drupalGet('');
182 $this->assertSessionCookie(FALSE);
183 $this->assertSessionEmpty(FALSE);
184 $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.');
185 $this->assertText(t('This is a dummy message.'), 'Message was displayed.');
186 $this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), 'Session cookie was deleted.');
187
188 // Verify that session was destroyed.
189 $this->drupalGet('');
190 $this->assertSessionCookie(FALSE);
191 // @todo Reinstate when REQUEST and RESPONSE events fire for cached pages.
192 // $this->assertSessionEmpty(TRUE);
193 $this->assertNoText(t('This is a dummy message.'), 'Message was not cached.');
194 $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
195 $this->assertFalse($this->drupalGetHeader('Set-Cookie'), 'New session was not started.');
196
197 // Verify that no session is created if drupal_save_session(FALSE) is called.
198 $this->drupalGet('session-test/set-message-but-dont-save');
199 $this->assertSessionCookie(FALSE);
200 $this->assertSessionEmpty(TRUE);
201
202 // Verify that no message is displayed.
203 $this->drupalGet('');
204 $this->assertSessionCookie(FALSE);
205 // @todo Reinstate when REQUEST and RESPONSE events fire for cached pages.
206 // $this->assertSessionEmpty(TRUE);
207 $this->assertNoText(t('This is a dummy message.'), 'The message was not saved.');
208 }
209
210 /**
211 * Test that sessions are only saved when necessary.
212 */
213 public function testSessionWrite() {
214 $user = $this->drupalCreateUser([]);
215 $this->drupalLogin($user);
216
217 $sql = 'SELECT u.access, s.timestamp FROM {users_field_data} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE u.uid = :uid';
218 $times1 = db_query($sql, [':uid' => $user->id()])->fetchObject();
219
220 // Before every request we sleep one second to make sure that if the session
221 // is saved, its timestamp will change.
222
223 // Modify the session.
224 sleep(1);
225 $this->drupalGet('session-test/set/foo');
226 $times2 = db_query($sql, [':uid' => $user->id()])->fetchObject();
227 $this->assertEqual($times2->access, $times1->access, 'Users table was not updated.');
228 $this->assertNotEqual($times2->timestamp, $times1->timestamp, 'Sessions table was updated.');
229
230 // Write the same value again, i.e. do not modify the session.
231 sleep(1);
232 $this->drupalGet('session-test/set/foo');
233 $times3 = db_query($sql, [':uid' => $user->id()])->fetchObject();
234 $this->assertEqual($times3->access, $times1->access, 'Users table was not updated.');
235 $this->assertEqual($times3->timestamp, $times2->timestamp, 'Sessions table was not updated.');
236
237 // Do not change the session.
238 sleep(1);
239 $this->drupalGet('');
240 $times4 = db_query($sql, [':uid' => $user->id()])->fetchObject();
241 $this->assertEqual($times4->access, $times3->access, 'Users table was not updated.');
242 $this->assertEqual($times4->timestamp, $times3->timestamp, 'Sessions table was not updated.');
243
244 // Force updating of users and sessions table once per second.
245 $this->settingsSet('session_write_interval', 0);
246 // Write that value also into the test settings.php file.
247 $settings['settings']['session_write_interval'] = (object) [
248 'value' => 0,
249 'required' => TRUE,
250 ];
251 $this->writeSettings($settings);
252 $this->drupalGet('');
253 $times5 = db_query($sql, [':uid' => $user->id()])->fetchObject();
254 $this->assertNotEqual($times5->access, $times4->access, 'Users table was updated.');
255 $this->assertNotEqual($times5->timestamp, $times4->timestamp, 'Sessions table was updated.');
256 }
257
258 /**
259 * Test that empty session IDs are not allowed.
260 */
261 public function testEmptySessionID() {
262 $user = $this->drupalCreateUser([]);
263 $this->drupalLogin($user);
264 $this->drupalGet('session-test/is-logged-in');
265 $this->assertResponse(200, 'User is logged in.');
266
267 // Reset the sid in {sessions} to a blank string. This may exist in the
268 // wild in some cases, although we normally prevent it from happening.
269 db_query("UPDATE {sessions} SET sid = '' WHERE uid = :uid", [':uid' => $user->id()]);
270 // Send a blank sid in the session cookie, and the session should no longer
271 // be valid. Closing the curl handler will stop the previous session ID
272 // from persisting.
273 $this->curlClose();
274 $this->additionalCurlOptions[CURLOPT_COOKIE] = rawurlencode($this->getSessionName()) . '=;';
275 $this->drupalGet('session-test/id-from-cookie');
276 $this->assertRaw("session_id:\n", 'Session ID is blank as sent from cookie header.');
277 // Assert that we have an anonymous session now.
278 $this->drupalGet('session-test/is-logged-in');
279 $this->assertResponse(403, 'An empty session ID is not allowed.');
280 }
281
282 /**
283 * Reset the cookie file so that it refers to the specified user.
284 *
285 * @param $uid
286 * User id to set as the active session.
287 */
288 public function sessionReset($uid = 0) {
289 // Close the internal browser.
290 $this->curlClose();
291 $this->loggedInUser = FALSE;
292
293 // Change cookie file for user.
294 $this->cookieFile = \Drupal::service('stream_wrapper_manager')->getViaScheme('temporary')->getDirectoryPath() . '/cookie.' . $uid . '.txt';
295 $this->additionalCurlOptions[CURLOPT_COOKIEFILE] = $this->cookieFile;
296 $this->additionalCurlOptions[CURLOPT_COOKIESESSION] = TRUE;
297 $this->drupalGet('session-test/get');
298 $this->assertResponse(200, 'Session test module is correctly enabled.', 'Session');
299 }
300
301 /**
302 * Assert whether the SimpleTest browser sent a session cookie.
303 */
304 public function assertSessionCookie($sent) {
305 if ($sent) {
306 $this->assertNotNull($this->sessionId, 'Session cookie was sent.');
307 }
308 else {
309 $this->assertNull($this->sessionId, 'Session cookie was not sent.');
310 }
311 }
312
313 /**
314 * Assert whether $_SESSION is empty at the beginning of the request.
315 */
316 public function assertSessionEmpty($empty) {
317 if ($empty) {
318 $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '1', 'Session was empty.');
319 }
320 else {
321 $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '0', 'Session was not empty.');
322 }
323 }
324
325 }