annotate core/modules/system/src/Tests/Session/SessionTest.php @ 12:7a779792577d

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