Drupal PHP Cross Reference Content Management Systems

Source: /modules/simpletest/tests/session.test - 531 lines - 21889 bytes - Summary - Text - Print

   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

title

Description

title

Description

title

title

Body