| Drupal | PHP Cross Reference | Content Management Systems |
1 <?php 2 3 /** 4 * @file 5 * Provides SimpleTests for core session handling functionality. 6 */ 7 8 class SessionTestCase extends DrupalWebTestCase { 9 public static function getInfo() { 10 return array( 11 'name' => 'Session tests', 12 'description' => 'Drupal session handling tests.', 13 'group' => 'Session' 14 ); 15 } 16 17 function setUp() { 18 parent::setUp('session_test'); 19 } 20 21 /** 22 * Tests for drupal_save_session() and drupal_session_regenerate(). 23 */ 24 function testSessionSaveRegenerate() { 25 $this->assertFalse(drupal_save_session(), t('drupal_save_session() correctly returns FALSE (inside of testing framework) when initially called with no arguments.'), t('Session')); 26 $this->assertFalse(drupal_save_session(FALSE), t('drupal_save_session() correctly returns FALSE when called with FALSE.'), t('Session')); 27 $this->assertFalse(drupal_save_session(), t('drupal_save_session() correctly returns FALSE when saving has been disabled.'), t('Session')); 28 $this->assertTrue(drupal_save_session(TRUE), t('drupal_save_session() correctly returns TRUE when called with TRUE.'), t('Session')); 29 $this->assertTrue(drupal_save_session(), t('drupal_save_session() correctly returns TRUE when saving has been enabled.'), t('Session')); 30 31 // Test session hardening code from SA-2008-044. 32 $user = $this->drupalCreateUser(array('access content')); 33 34 // Enable sessions. 35 $this->sessionReset($user->uid); 36 37 // Make sure the session cookie is set as HttpOnly. 38 $this->drupalLogin($user); 39 $this->assertTrue(preg_match('/HttpOnly/i', $this->drupalGetHeader('Set-Cookie', TRUE)), t('Session cookie is set as HttpOnly.')); 40 $this->drupalLogout(); 41 42 // Verify that the session is regenerated if a module calls exit 43 // in hook_user_login(). 44 user_save($user, array('name' => 'session_test_user')); 45 $user->name = 'session_test_user'; 46 $this->drupalGet('session-test/id'); 47 $matches = array(); 48 preg_match('/\s*session_id:(.*)\n/', $this->drupalGetContent(), $matches); 49 $this->assertTrue(!empty($matches[1]) , t('Found session ID before logging in.')); 50 $original_session = $matches[1]; 51 52 // We cannot use $this->drupalLogin($user); because we exit in 53 // session_test_user_login() which breaks a normal assertion. 54 $edit = array( 55 'name' => $user->name, 56 'pass' => $user->pass_raw 57 ); 58 $this->drupalPost('user', $edit, t('Log in')); 59 $this->drupalGet('user'); 60 $pass = $this->assertText($user->name, t('Found name: %name', array('%name' => $user->name)), t('User login')); 61 $this->_logged_in = $pass; 62 63 $this->drupalGet('session-test/id'); 64 $matches = array(); 65 preg_match('/\s*session_id:(.*)\n/', $this->drupalGetContent(), $matches); 66 $this->assertTrue(!empty($matches[1]) , t('Found session ID after logging in.')); 67 $this->assertTrue($matches[1] != $original_session, t('Session ID changed after login.')); 68 } 69 70 /** 71 * Test data persistence via the session_test module callbacks. Also tests 72 * drupal_session_count() since session data is already generated here. 73 */ 74 function testDataPersistence() { 75 $user = $this->drupalCreateUser(array('access content')); 76 // Enable sessions. 77 $this->sessionReset($user->uid); 78 79 $this->drupalLogin($user); 80 81 $value_1 = $this->randomName(); 82 $this->drupalGet('session-test/set/' . $value_1); 83 $this->assertText($value_1, t('The session value was stored.'), t('Session')); 84 $this->drupalGet('session-test/get'); 85 $this->assertText($value_1, t('Session correctly returned the stored data for an authenticated user.'), t('Session')); 86 87 // Attempt to write over val_1. If drupal_save_session(FALSE) is working. 88 // properly, val_1 will still be set. 89 $value_2 = $this->randomName(); 90 $this->drupalGet('session-test/no-set/' . $value_2); 91 $this->assertText($value_2, t('The session value was correctly passed to session-test/no-set.'), t('Session')); 92 $this->drupalGet('session-test/get'); 93 $this->assertText($value_1, t('Session data is not saved for drupal_save_session(FALSE).'), t('Session')); 94 95 // Switch browser cookie to anonymous user, then back to user 1. 96 $this->sessionReset(); 97 $this->sessionReset($user->uid); 98 $this->assertText($value_1, t('Session data persists through browser close.'), t('Session')); 99 100 // Logout the user and make sure the stored value no longer persists. 101 $this->drupalLogout(); 102 $this->sessionReset(); 103 $this->drupalGet('session-test/get'); 104 $this->assertNoText($value_1, t("After logout, previous user's session data is not available."), t('Session')); 105 106 // Now try to store some data as an anonymous user. 107 $value_3 = $this->randomName(); 108 $this->drupalGet('session-test/set/' . $value_3); 109 $this->assertText($value_3, t('Session data stored for anonymous user.'), t('Session')); 110 $this->drupalGet('session-test/get'); 111 $this->assertText($value_3, t('Session correctly returned the stored data for an anonymous user.'), t('Session')); 112 113 // Try to store data when drupal_save_session(FALSE). 114 $value_4 = $this->randomName(); 115 $this->drupalGet('session-test/no-set/' . $value_4); 116 $this->assertText($value_4, t('The session value was correctly passed to session-test/no-set.'), t('Session')); 117 $this->drupalGet('session-test/get'); 118 $this->assertText($value_3, t('Session data is not saved for drupal_save_session(FALSE).'), t('Session')); 119 120 // Login, the data should persist. 121 $this->drupalLogin($user); 122 $this->sessionReset($user->uid); 123 $this->drupalGet('session-test/get'); 124 $this->assertNoText($value_1, t('Session has persisted for an authenticated user after logging out and then back in.'), t('Session')); 125 126 // Change session and create another user. 127 $user2 = $this->drupalCreateUser(array('access content')); 128 $this->sessionReset($user2->uid); 129 $this->drupalLogin($user2); 130 } 131 132 /** 133 * Test that empty anonymous sessions are destroyed. 134 */ 135 function testEmptyAnonymousSession() { 136 // Verify that no session is automatically created for anonymous user. 137 $this->drupalGet(''); 138 $this->assertSessionCookie(FALSE); 139 $this->assertSessionEmpty(TRUE); 140 141 // The same behavior is expected when caching is enabled. 142 variable_set('cache', 1); 143 $this->drupalGet(''); 144 $this->assertSessionCookie(FALSE); 145 $this->assertSessionEmpty(TRUE); 146 $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', t('Page was not cached.')); 147 148 // Start a new session by setting a message. 149 $this->drupalGet('session-test/set-message'); 150 $this->assertSessionCookie(TRUE); 151 $this->assertTrue($this->drupalGetHeader('Set-Cookie'), t('New session was started.')); 152 153 // Display the message, during the same request the session is destroyed 154 // and the session cookie is unset. 155 $this->drupalGet(''); 156 $this->assertSessionCookie(FALSE); 157 $this->assertSessionEmpty(FALSE); 158 $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), t('Caching was bypassed.')); 159 $this->assertText(t('This is a dummy message.'), t('Message was displayed.')); 160 $this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), t('Session cookie was deleted.')); 161 162 // Verify that session was destroyed. 163 $this->drupalGet(''); 164 $this->assertSessionCookie(FALSE); 165 $this->assertSessionEmpty(TRUE); 166 $this->assertNoText(t('This is a dummy message.'), t('Message was not cached.')); 167 $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.')); 168 $this->assertFalse($this->drupalGetHeader('Set-Cookie'), t('New session was not started.')); 169 170 // Verify that no session is created if drupal_save_session(FALSE) is called. 171 $this->drupalGet('session-test/set-message-but-dont-save'); 172 $this->assertSessionCookie(FALSE); 173 $this->assertSessionEmpty(TRUE); 174 175 // Verify that no message is displayed. 176 $this->drupalGet(''); 177 $this->assertSessionCookie(FALSE); 178 $this->assertSessionEmpty(TRUE); 179 $this->assertNoText(t('This is a dummy message.'), t('The message was not saved.')); 180 } 181 182 /** 183 * Test that sessions are only saved when necessary. 184 */ 185 function testSessionWrite() { 186 $user = $this->drupalCreateUser(array('access content')); 187 $this->drupalLogin($user); 188 189 $sql = 'SELECT u.access, s.timestamp FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE u.uid = :uid'; 190 $times1 = db_query($sql, array(':uid' => $user->uid))->fetchObject(); 191 192 // Before every request we sleep one second to make sure that if the session 193 // is saved, its timestamp will change. 194 195 // Modify the session. 196 sleep(1); 197 $this->drupalGet('session-test/set/foo'); 198 $times2 = db_query($sql, array(':uid' => $user->uid))->fetchObject(); 199 $this->assertEqual($times2->access, $times1->access, t('Users table was not updated.')); 200 $this->assertNotEqual($times2->timestamp, $times1->timestamp, t('Sessions table was updated.')); 201 202 // Write the same value again, i.e. do not modify the session. 203 sleep(1); 204 $this->drupalGet('session-test/set/foo'); 205 $times3 = db_query($sql, array(':uid' => $user->uid))->fetchObject(); 206 $this->assertEqual($times3->access, $times1->access, t('Users table was not updated.')); 207 $this->assertEqual($times3->timestamp, $times2->timestamp, t('Sessions table was not updated.')); 208 209 // Do not change the session. 210 sleep(1); 211 $this->drupalGet(''); 212 $times4 = db_query($sql, array(':uid' => $user->uid))->fetchObject(); 213 $this->assertEqual($times4->access, $times3->access, t('Users table was not updated.')); 214 $this->assertEqual($times4->timestamp, $times3->timestamp, t('Sessions table was not updated.')); 215 216 // Force updating of users and sessions table once per second. 217 variable_set('session_write_interval', 0); 218 $this->drupalGet(''); 219 $times5 = db_query($sql, array(':uid' => $user->uid))->fetchObject(); 220 $this->assertNotEqual($times5->access, $times4->access, t('Users table was updated.')); 221 $this->assertNotEqual($times5->timestamp, $times4->timestamp, t('Sessions table was updated.')); 222 } 223 224 /** 225 * Test that empty session IDs are not allowed. 226 */ 227 function testEmptySessionID() { 228 $user = $this->drupalCreateUser(array('access content')); 229 $this->drupalLogin($user); 230 $this->drupalGet('session-test/is-logged-in'); 231 $this->assertResponse(200, t('User is logged in.')); 232 233 // Reset the sid in {sessions} to a blank string. This may exist in the 234 // wild in some cases, although we normally prevent it from happening. 235 db_query("UPDATE {sessions} SET sid = '' WHERE uid = :uid", array(':uid' => $user->uid)); 236 // Send a blank sid in the session cookie, and the session should no longer 237 // be valid. Closing the curl handler will stop the previous session ID 238 // from persisting. 239 $this->curlClose(); 240 $this->additionalCurlOptions[CURLOPT_COOKIE] = rawurlencode($this->session_name) . '=;'; 241 $this->drupalGet('session-test/id-from-cookie'); 242 $this->assertRaw("session_id:\n", t('Session ID is blank as sent from cookie header.')); 243 // Assert that we have an anonymous session now. 244 $this->drupalGet('session-test/is-logged-in'); 245 $this->assertResponse(403, t('An empty session ID is not allowed.')); 246 } 247 248 /** 249 * Reset the cookie file so that it refers to the specified user. 250 * 251 * @param $uid User id to set as the active session. 252 */ 253 function sessionReset($uid = 0) { 254 // Close the internal browser. 255 $this->curlClose(); 256 $this->loggedInUser = FALSE; 257 258 // Change cookie file for user. 259 $this->cookieFile = file_stream_wrapper_get_instance_by_scheme('temporary')->getDirectoryPath() . '/cookie.' . $uid . '.txt'; 260 $this->additionalCurlOptions[CURLOPT_COOKIEFILE] = $this->cookieFile; 261 $this->additionalCurlOptions[CURLOPT_COOKIESESSION] = TRUE; 262 $this->drupalGet('session-test/get'); 263 $this->assertResponse(200, t('Session test module is correctly enabled.'), t('Session')); 264 } 265 266 /** 267 * Assert whether the SimpleTest browser sent a session cookie. 268 */ 269 function assertSessionCookie($sent) { 270 if ($sent) { 271 $this->assertNotNull($this->session_id, t('Session cookie was sent.')); 272 } 273 else { 274 $this->assertNull($this->session_id, t('Session cookie was not sent.')); 275 } 276 } 277 278 /** 279 * Assert whether $_SESSION is empty at the beginning of the request. 280 */ 281 function assertSessionEmpty($empty) { 282 if ($empty) { 283 $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '1', t('Session was empty.')); 284 } 285 else { 286 $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '0', t('Session was not empty.')); 287 } 288 } 289 } 290 291 /** 292 * Ensure that when running under HTTPS two session cookies are generated. 293 */ 294 class SessionHttpsTestCase extends DrupalWebTestCase { 295 296 public static function getInfo() { 297 return array( 298 'name' => 'Session HTTPS handling', 299 'description' => 'Ensure that when running under HTTPS two session cookies are generated.', 300 'group' => 'Session' 301 ); 302 } 303 304 public function setUp() { 305 parent::setUp('session_test'); 306 } 307 308 protected function testHttpsSession() { 309 global $is_https; 310 311 if ($is_https) { 312 $secure_session_name = session_name(); 313 $insecure_session_name = substr(session_name(), 1); 314 } 315 else { 316 $secure_session_name = 'S' . session_name(); 317 $insecure_session_name = session_name(); 318 } 319 320 $user = $this->drupalCreateUser(array('access administration pages')); 321 322 // Test HTTPS session handling by altering the form action to submit the 323 // login form through https.php, which creates a mock HTTPS request. 324 $this->drupalGet('user'); 325 $form = $this->xpath('//form[@id="user-login"]'); 326 $form[0]['action'] = $this->httpsUrl('user'); 327 $edit = array('name' => $user->name, 'pass' => $user->pass_raw); 328 $this->drupalPost(NULL, $edit, t('Log in')); 329 330 // Test a second concurrent session. 331 $this->curlClose(); 332 $this->drupalGet('user'); 333 $form = $this->xpath('//form[@id="user-login"]'); 334 $form[0]['action'] = $this->httpsUrl('user'); 335 $this->drupalPost(NULL, $edit, t('Log in')); 336 337 // Check secure cookie on secure page. 338 $this->assertTrue($this->cookies[$secure_session_name]['secure'], 'The secure cookie has the secure attribute'); 339 // Check insecure cookie is not set. 340 $this->assertFalse(isset($this->cookies[$insecure_session_name])); 341 $ssid = $this->cookies[$secure_session_name]['value']; 342 $this->assertSessionIds($ssid, $ssid, 'Session has a non-empty SID and a correct secure SID.'); 343 $cookie = $secure_session_name . '=' . $ssid; 344 345 // Verify that user is logged in on secure URL. 346 $this->curlClose(); 347 $this->drupalGet($this->httpsUrl('admin/config'), array(), array('Cookie: ' . $cookie)); 348 $this->assertText(t('Configuration')); 349 $this->assertResponse(200); 350 351 // Verify that user is not logged in on non-secure URL. 352 $this->curlClose(); 353 $this->drupalGet($this->httpUrl('admin/config'), array(), array('Cookie: ' . $cookie)); 354 $this->assertNoText(t('Configuration')); 355 $this->assertResponse(403); 356 357 // Verify that empty SID cannot be used on the non-secure site. 358 $this->curlClose(); 359 $cookie = $insecure_session_name . '='; 360 $this->drupalGet($this->httpUrl('admin/config'), array(), array('Cookie: ' . $cookie)); 361 $this->assertResponse(403); 362 363 // Test HTTP session handling by altering the form action to submit the 364 // login form through http.php, which creates a mock HTTP request on HTTPS 365 // test environments. 366 $this->curlClose(); 367 $this->drupalGet('user'); 368 $form = $this->xpath('//form[@id="user-login"]'); 369 $form[0]['action'] = $this->httpUrl('user'); 370 $edit = array('name' => $user->name, 'pass' => $user->pass_raw); 371 $this->drupalPost(NULL, $edit, t('Log in')); 372 $this->drupalGet($this->httpUrl('admin/config')); 373 $this->assertResponse(200); 374 $sid = $this->cookies[$insecure_session_name]['value']; 375 $this->assertSessionIds($sid, '', 'Session has the correct SID and an empty secure SID.'); 376 377 // Verify that empty secure SID cannot be used on the secure site. 378 $this->curlClose(); 379 $cookie = $secure_session_name . '='; 380 $this->drupalGet($this->httpsUrl('admin/config'), array(), array('Cookie: ' . $cookie)); 381 $this->assertResponse(403); 382 383 // Clear browser cookie jar. 384 $this->cookies = array(); 385 386 if ($is_https) { 387 // The functionality does not make sense when running on HTTPS. 388 return; 389 } 390 391 // Enable secure pages. 392 variable_set('https', TRUE); 393 394 $this->curlClose(); 395 // Start an anonymous session on the insecure site. 396 $session_data = $this->randomName(); 397 $this->drupalGet('session-test/set/' . $session_data); 398 // Check secure cookie on insecure page. 399 $this->assertFalse(isset($this->cookies[$secure_session_name]), 'The secure cookie is not sent on insecure pages.'); 400 // Check insecure cookie on insecure page. 401 $this->assertFalse($this->cookies[$insecure_session_name]['secure'], 'The insecure cookie does not have the secure attribute'); 402 403 // Store the anonymous cookie so we can validate that its session is killed 404 // after login. 405 $anonymous_cookie = $insecure_session_name . '=' . $this->cookies[$insecure_session_name]['value']; 406 407 // Check that password request form action is not secure. 408 $this->drupalGet('user/password'); 409 $form = $this->xpath('//form[@id="user-pass"]'); 410 $this->assertNotEqual(substr($form[0]['action'], 0, 6), 'https:', 'Password request form action is not secure'); 411 $form[0]['action'] = $this->httpsUrl('user'); 412 413 // Check that user login form action is secure. 414 $this->drupalGet('user'); 415 $form = $this->xpath('//form[@id="user-login"]'); 416 $this->assertEqual(substr($form[0]['action'], 0, 6), 'https:', 'Login form action is secure'); 417 $form[0]['action'] = $this->httpsUrl('user'); 418 419 $edit = array( 420 'name' => $user->name, 421 'pass' => $user->pass_raw, 422 ); 423 $this->drupalPost(NULL, $edit, t('Log in')); 424 // Check secure cookie on secure page. 425 $this->assertTrue($this->cookies[$secure_session_name]['secure'], 'The secure cookie has the secure attribute'); 426 // Check insecure cookie on secure page. 427 $this->assertFalse($this->cookies[$insecure_session_name]['secure'], 'The insecure cookie does not have the secure attribute'); 428 429 $sid = $this->cookies[$insecure_session_name]['value']; 430 $ssid = $this->cookies[$secure_session_name]['value']; 431 $this->assertSessionIds($sid, $ssid, 'Session has both secure and insecure SIDs'); 432 $cookies = array( 433 $insecure_session_name . '=' . $sid, 434 $secure_session_name . '=' . $ssid, 435 ); 436 437 // Test that session data saved before login is still available on the 438 // authenticated session. 439 $this->drupalGet('session-test/get'); 440 $this->assertText($session_data, 'Session correctly returned the stored data set by the anonymous session.'); 441 442 foreach ($cookies as $cookie_key => $cookie) { 443 foreach (array('admin/config', $this->httpsUrl('admin/config')) as $url_key => $url) { 444 $this->curlClose(); 445 446 $this->drupalGet($url, array(), array('Cookie: ' . $cookie)); 447 if ($cookie_key == $url_key) { 448 $this->assertText(t('Configuration')); 449 $this->assertResponse(200); 450 } 451 else { 452 $this->assertNoText(t('Configuration')); 453 $this->assertResponse(403); 454 } 455 } 456 } 457 458 // Test that session data saved before login is not available using the 459 // pre-login anonymous cookie. 460 $this->cookies = array(); 461 $this->drupalGet('session-test/get', array('Cookie: ' . $anonymous_cookie)); 462 $this->assertNoText($session_data, 'Initial anonymous session is inactive after login.'); 463 464 // Clear browser cookie jar. 465 $this->cookies = array(); 466 467 // Start an anonymous session on the secure site. 468 $this->drupalGet($this->httpsUrl('session-test/set/1')); 469 470 // Mock a login to the secure site using the secure session cookie. 471 $this->drupalGet('user'); 472 $form = $this->xpath('//form[@id="user-login"]'); 473 $form[0]['action'] = $this->httpsUrl('user'); 474 $this->drupalPost(NULL, $edit, t('Log in')); 475 476 // Test that the user is also authenticated on the insecure site. 477 $this->drupalGet("user/{$user->uid}/edit"); 478 $this->assertResponse(200); 479 } 480 481 /** 482 * Test that there exists a session with two specific session IDs. 483 * 484 * @param $sid 485 * The insecure session ID to search for. 486 * @param $ssid 487 * The secure session ID to search for. 488 * @param $assertion_text 489 * The text to display when we perform the assertion. 490 * 491 * @return 492 * The result of assertTrue() that there's a session in the system that 493 * has the given insecure and secure session IDs. 494 */ 495 protected function assertSessionIds($sid, $ssid, $assertion_text) { 496 $args = array( 497 ':sid' => $sid, 498 ':ssid' => $ssid, 499 ); 500 return $this->assertTrue(db_query('SELECT timestamp FROM {sessions} WHERE sid = :sid AND ssid = :ssid', $args)->fetchField(), $assertion_text); 501 } 502 503 /** 504 * Builds a URL for submitting a mock HTTPS request to HTTP test environments. 505 * 506 * @param $url 507 * A Drupal path such as 'user'. 508 * 509 * @return 510 * An absolute URL. 511 */ 512 protected function httpsUrl($url) { 513 global $base_url; 514 return $base_url . '/modules/simpletest/tests/https.php?q=' . $url; 515 } 516 517 /** 518 * Builds a URL for submitting a mock HTTP request to HTTPS test environments. 519 * 520 * @param $url 521 * A Drupal path such as 'user'. 522 * 523 * @return 524 * An absolute URL. 525 */ 526 protected function httpUrl($url) { 527 global $base_url; 528 return $base_url . '/modules/simpletest/tests/http.php?q=' . $url; 529 } 530 } 531
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title