Newer
Older

Dries Buytaert
committed
<?php
// $Id$
/**
* @file
* Provides SimpleTests for core session handling functionality.
*/
class SessionTestCase extends DrupalWebTestCase {

Angie Byron
committed
public static function getInfo() {

Dries Buytaert
committed
return array(
'name' => 'Session tests',
'description' => 'Drupal session handling tests.',
'group' => 'Session'

Dries Buytaert
committed
);
}
function setUp() {
parent::setUp('session_test');
}

Dries Buytaert
committed
/**

Dries Buytaert
committed
* Tests for drupal_save_session() and drupal_session_regenerate().

Dries Buytaert
committed
*/

Dries Buytaert
committed
function testSessionSaveRegenerate() {

Angie Byron
committed
$this->assertFalse(drupal_save_session(), t('drupal_save_session() correctly returns FALSE (inside of testing framework) when initially called with no arguments.'), t('Session'));

Dries Buytaert
committed
$this->assertFalse(drupal_save_session(FALSE), t('drupal_save_session() correctly returns FALSE when called with FALSE.'), t('Session'));
$this->assertFalse(drupal_save_session(), t('drupal_save_session() correctly returns FALSE when saving has been disabled.'), t('Session'));
$this->assertTrue(drupal_save_session(TRUE), t('drupal_save_session() correctly returns TRUE when called with TRUE.'), t('Session'));
$this->assertTrue(drupal_save_session(), t('drupal_save_session() correctly returns TRUE when saving has been enabled.'), t('Session'));

Dries Buytaert
committed
// Test session hardening code from SA-2008-044.
$user = $this->drupalCreateUser(array('access content'));

Dries Buytaert
committed

Dries Buytaert
committed
// Enable sessions.
$this->sessionReset($user->uid);

Dries Buytaert
committed

Dries Buytaert
committed
// Make sure the session cookie is set as HttpOnly.
$this->drupalLogin($user);

Dries Buytaert
committed
$this->assertTrue(preg_match('/HttpOnly/i', $this->drupalGetHeader('Set-Cookie', TRUE)), t('Session cookie is set as HttpOnly.'));

Dries Buytaert
committed
$this->drupalLogout();

Dries Buytaert
committed

Dries Buytaert
committed
// Verify that the session is regenerated if a module calls exit
// in hook_user_login().
user_save($user, array('name' => 'session_test_user'));
$user->name = 'session_test_user';
$this->drupalGet('session-test/id');
$matches = array();
preg_match('/\s*session_id:(.*)\n/', $this->drupalGetContent(), $matches);
$this->assertTrue(!empty($matches[1]) , t('Found session ID before logging in.'));
$original_session = $matches[1];

Dries Buytaert
committed

Dries Buytaert
committed
// We cannot use $this->drupalLogin($user); because we exit in
// session_test_user_login() which breaks a normal assertion.
$edit = array(
'name' => $user->name,
'pass' => $user->pass_raw
);
$this->drupalPost('user', $edit, t('Log in'));

Dries Buytaert
committed
$this->drupalGet('user');

Dries Buytaert
committed
$pass = $this->assertText($user->name, t('Found name: %name', array('%name' => $user->name)), t('User login'));
$this->_logged_in = $pass;
$this->drupalGet('session-test/id');
$matches = array();
preg_match('/\s*session_id:(.*)\n/', $this->drupalGetContent(), $matches);
$this->assertTrue(!empty($matches[1]) , t('Found session ID after logging in.'));
$this->assertTrue($matches[1] != $original_session, t('Session ID changed after login.'));

Dries Buytaert
committed
}
/**
* Test data persistence via the session_test module callbacks. Also tests

Dries Buytaert
committed
* drupal_session_count() since session data is already generated here.

Dries Buytaert
committed
*/
function testDataPersistence() {

Dries Buytaert
committed
// At the very start, we have no session.
$expected_anonymous = 0;
$expected_authenticated = 0;

Dries Buytaert
committed
$user = $this->drupalCreateUser(array('access content'));
// Enable sessions.
$this->sessionReset($user->uid);
$this->drupalLogin($user);

Dries Buytaert
committed
$expected_authenticated++;

Dries Buytaert
committed
$value_1 = $this->randomName();
$this->drupalGet('session-test/set/' . $value_1);
$this->assertText($value_1, t('The session value was stored.'), t('Session'));
$this->drupalGet('session-test/get');
$this->assertText($value_1, t('Session correctly returned the stored data for an authenticated user.'), t('Session'));

Dries Buytaert
committed
// Attempt to write over val_1. If drupal_save_session(FALSE) is working.

Dries Buytaert
committed
// properly, val_1 will still be set.
$value_2 = $this->randomName();
$this->drupalGet('session-test/no-set/' . $value_2);
$this->assertText($value_2, t('The session value was correctly passed to session-test/no-set.'), t('Session'));
$this->drupalGet('session-test/get');

Dries Buytaert
committed
$this->assertText($value_1, t('Session data is not saved for drupal_save_session(FALSE).'), t('Session'));

Dries Buytaert
committed
// Switch browser cookie to anonymous user, then back to user 1.
$this->sessionReset();
$this->sessionReset($user->uid);
$this->assertText($value_1, t('Session data persists through browser close.'), t('Session'));
// Logout the user and make sure the stored value no longer persists.
$this->drupalLogout();

Dries Buytaert
committed
$expected_authenticated--;

Dries Buytaert
committed
$this->sessionReset();
$this->drupalGet('session-test/get');
$this->assertNoText($value_1, t("After logout, previous user's session data is not available."), t('Session'));

Dries Buytaert
committed
// Now try to store some data as an anonymous user.

Dries Buytaert
committed
$value_3 = $this->randomName();
$this->drupalGet('session-test/set/' . $value_3);
$this->assertText($value_3, t('Session data stored for anonymous user.'), t('Session'));
$this->drupalGet('session-test/get');
$this->assertText($value_3, t('Session correctly returned the stored data for an anonymous user.'), t('Session'));

Dries Buytaert
committed
// Session count should go up since we have started an anonymous session now.
$expected_anonymous++;

Dries Buytaert
committed

Dries Buytaert
committed
// Try to store data when drupal_save_session(FALSE).

Dries Buytaert
committed
$value_4 = $this->randomName();
$this->drupalGet('session-test/no-set/' . $value_4);
$this->assertText($value_4, t('The session value was correctly passed to session-test/no-set.'), t('Session'));
$this->drupalGet('session-test/get');

Dries Buytaert
committed
$this->assertText($value_3, t('Session data is not saved for drupal_save_session(FALSE).'), t('Session'));

Dries Buytaert
committed

Dries Buytaert
committed
// Login, the data should persist.

Dries Buytaert
committed
$this->drupalLogin($user);

Dries Buytaert
committed
$expected_anonymous--;
$expected_authenticated++;

Dries Buytaert
committed
$this->sessionReset($user->uid);
$this->drupalGet('session-test/get');
$this->assertNoText($value_1, t('Session has persisted for an authenticated user after logging out and then back in.'), t('Session'));

Dries Buytaert
committed
// Change session and create another user.

Dries Buytaert
committed
$user2 = $this->drupalCreateUser(array('access content'));
$this->sessionReset($user2->uid);
$this->drupalLogin($user2);

Dries Buytaert
committed
$expected_authenticated++;

Dries Buytaert
committed

Dries Buytaert
committed
// Perform drupal_session_count tests here in order to use the session data already generated.

Dries Buytaert
committed
// Test absolute count.

Dries Buytaert
committed
$anonymous = drupal_session_count(0, TRUE);
$authenticated = drupal_session_count(0, FALSE);

Dries Buytaert
committed
$this->assertEqual($anonymous + $authenticated, $expected_anonymous + $expected_authenticated, t('@count total sessions (expected @expected).', array('@count' => $anonymous + $authenticated, '@expected' => $expected_anonymous + $expected_authenticated)), t('Session'));

Dries Buytaert
committed
// Test anonymous count.

Dries Buytaert
committed
$this->assertEqual($anonymous, $expected_anonymous, t('@count anonymous sessions (expected @expected).', array('@count' => $anonymous, '@expected' => $expected_anonymous)), t('Session'));

Dries Buytaert
committed
// Test authenticated count.

Dries Buytaert
committed
$this->assertEqual($authenticated, $expected_authenticated, t('@count authenticated sessions (expected @expected).', array('@count' => $authenticated, '@expected' => $expected_authenticated)), t('Session'));

Dries Buytaert
committed
// Should return 0 sessions from 1 second from now.

Dries Buytaert
committed
$this->assertEqual(drupal_session_count(time() + 1), 0, t('0 sessions newer than the current time.'), t('Session'));

Dries Buytaert
committed
}

Dries Buytaert
committed
/**
* Test that empty anonymous sessions are destroyed.
*/
function testEmptyAnonymousSession() {

Dries Buytaert
committed
// Verify that no session is automatically created for anonymous user.

Dries Buytaert
committed
$this->drupalGet('');
$this->assertSessionCookie(FALSE);
$this->assertSessionEmpty(TRUE);

Dries Buytaert
committed
// The same behavior is expected when caching is enabled.

Dries Buytaert
committed
variable_set('cache', CACHE_NORMAL);
$this->drupalGet('');

Dries Buytaert
committed
$this->assertSessionCookie(FALSE);

Dries Buytaert
committed
$this->assertSessionEmpty(TRUE);

Dries Buytaert
committed
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', t('Page was not cached.'));

Dries Buytaert
committed
// Start a new session by setting a message.
$this->drupalGet('session-test/set-message');

Dries Buytaert
committed
$this->assertSessionCookie(TRUE);

Dries Buytaert
committed
$this->assertTrue($this->drupalGetHeader('Set-Cookie'), t('New session was started.'));

Dries Buytaert
committed
// Display the message, during the same request the session is destroyed
// and the session cookie is unset.

Dries Buytaert
committed
$this->drupalGet('');

Dries Buytaert
committed
$this->assertSessionCookie(FALSE);

Dries Buytaert
committed
$this->assertSessionEmpty(FALSE);

Dries Buytaert
committed
$this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), t('Caching was bypassed.'));

Dries Buytaert
committed
$this->assertText(t('This is a dummy message.'), t('Message was displayed.'));
$this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), t('Session cookie was deleted.'));
// Verify that session was destroyed.
$this->drupalGet('');
$this->assertSessionCookie(FALSE);

Dries Buytaert
committed
$this->assertSessionEmpty(TRUE);
$this->assertNoText(t('This is a dummy message.'), t('Message was not cached.'));

Dries Buytaert
committed
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.'));

Dries Buytaert
committed
$this->assertFalse($this->drupalGetHeader('Set-Cookie'), t('New session was not started.'));

Dries Buytaert
committed
// Verify that no session is created if drupal_save_session(FALSE) is called.
$this->drupalGet('session-test/set-message-but-dont-save');
$this->assertSessionCookie(FALSE);
$this->assertSessionEmpty(TRUE);
// Verify that no message is displayed.
$this->drupalGet('');
$this->assertSessionCookie(FALSE);
$this->assertSessionEmpty(TRUE);
$this->assertNoText(t('This is a dummy message.'), t('The message was not saved.'));

Dries Buytaert
committed
}

Dries Buytaert
committed
/**
* Reset the cookie file so that it refers to the specified user.
*
* @param $uid User id to set as the active session.
*/
function sessionReset($uid = 0) {
// Close the internal browser.
$this->curlClose();

Dries Buytaert
committed
$this->loggedInUser = FALSE;

Dries Buytaert
committed
// Change cookie file for user.
$this->cookieFile = file_directory_path('temporary') . '/cookie.' . $uid . '.txt';
$this->additionalCurlOptions[CURLOPT_COOKIEFILE] = $this->cookieFile;
$this->additionalCurlOptions[CURLOPT_COOKIESESSION] = TRUE;

Dries Buytaert
committed
$this->drupalGet('session-test/get');
$this->assertResponse(200, t('Session test module is correctly enabled.'), t('Session'));
}

Dries Buytaert
committed
/**
* Assert whether the SimpleTest browser sent a session cookie.
*/
function assertSessionCookie($sent) {
if ($sent) {

Dries Buytaert
committed
$this->assertNotNull($this->session_id, t('Session cookie was sent.'));

Dries Buytaert
committed
}
else {

Dries Buytaert
committed
$this->assertNull($this->session_id, t('Session cookie was not sent.'));

Dries Buytaert
committed
}
}
/**
* Assert whether $_SESSION is empty at the beginning of the request.
*/
function assertSessionEmpty($empty) {
if ($empty) {
$this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '1', t('Session was empty.'));
}
else {
$this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '0', t('Session was not empty.'));
}
}

Dries Buytaert
committed
}

Dries Buytaert
committed
/**
* Ensure that when running under https two session cookies are generated.
*/
class SessionHttpsTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Session https handling',
'description' => 'Ensure that when running under https two session cookies are generated.',
'group' => 'Session'
);
}
public function setUp() {
parent::setUp('session_test');
}
protected function testHttpsSession() {
global $is_https;

Angie Byron
committed
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
if ($is_https) {
$secure_session_name = session_name();
$insecure_session_name = substr(session_name(), 1);
}
else {
$secure_session_name = 'S' . session_name();
$insecure_session_name = session_name();
}
$user = $this->drupalCreateUser(array('access administration pages'));
// Test HTTPS session handling by altering the form action to submit the
// login form through https.php, which creates a mock HTTPS request.
$this->drupalGet('user');
$form = $this->xpath('//form[@id="user-login"]');
$form[0]['action'] = $this->httpsUrl('user');
$edit = array('name' => $user->name, 'pass' => $user->pass_raw);
$this->drupalPost(NULL, $edit, t('Log in'));
// Test a second concurrent session.
$this->curlClose();
$this->drupalGet('user');
$form = $this->xpath('//form[@id="user-login"]');
$form[0]['action'] = $this->httpsUrl('user');
$this->drupalPost(NULL, $edit, t('Log in'));
// Check secure cookie on secure page.
$this->assertTrue($this->cookies[$secure_session_name]['secure'], 'The secure cookie has the secure attribute');
// Check insecure cookie is not set.
$this->assertFalse(isset($this->cookies[$insecure_session_name]));
$args = array_fill_keys(array(':sid', ':ssid'), $this->cookies[$secure_session_name]['value']);
$this->assertTrue(db_query('SELECT sid FROM {sessions} WHERE sid = :sid AND ssid = :ssid', $args)->fetchField(), 'Session has both SIDs');
$cookie = $secure_session_name . '=' . $args[':ssid'];
// Verify that user is logged in on secure URL.
$this->curlClose();
$this->drupalGet($this->httpsUrl('admin'), array(), array('Cookie: ' . $cookie));
$this->assertText(t('Administer'));
// Verify that user is not logged in on non-secure URL.
if (!$is_https) {
$this->curlClose();
$this->drupalGet('admin', array(), array('Cookie: ' . $cookie));
$this->assertNoText(t('Administer'));
}
// Clear browser cookie jar.
$this->cookies = array();

Dries Buytaert
committed
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
if ($is_https) {
// The functionality does not make sense when running on https.
return;
}
// Enable secure pages.
variable_set('https', TRUE);
$this->curlClose();
$this->drupalGet('session-test/set/1');
// Check secure cookie on insecure page.
$this->assertFalse(isset($this->cookies[$secure_session_name]), 'The secure cookie is not sent on insecure pages.');
// Check insecure cookie on insecure page.
$this->assertFalse($this->cookies[$insecure_session_name]['secure'], 'The insecure cookie does not have the secure attribute');
// Check that password request form action is not secure.
$this->drupalGet('user/password');
$form = $this->xpath('//form[@id="user-pass"]');
$this->assertNotEqual(substr($form[0]['action'], 0, 6), 'https:', 'Password request form action is not secure');
$form[0]['action'] = $this->httpsUrl('user');
// Check that user login form action is secure.
$this->drupalGet('user');
$form = &$this->xpath('//form[@id="user-login"]');
$this->assertEqual(substr($form[0]['action'], 0, 6), 'https:', 'Login form action is secure');
$form[0]['action'] = $this->httpsUrl('user');
$edit = array(
'name' => $user->name,
'pass' => $user->pass_raw,
);
$this->drupalPost(NULL, $edit, t('Log in'));
// Check secure cookie on secure page.
$this->assertTrue($this->cookies[$secure_session_name]['secure'], 'The secure cookie has the secure attribute');
// Check insecure cookie on secure page.
$this->assertFalse($this->cookies[$insecure_session_name]['secure'], 'The insecure cookie does not have the secure attribute');
$args = array(
':sid' => $this->cookies[$insecure_session_name]['value'],
':ssid' => $this->cookies[$secure_session_name]['value'],
);
$this->assertTrue(db_query('SELECT sid FROM {sessions} WHERE sid = :sid AND ssid = :ssid', $args)->fetchField(), 'Session has both SIDs');
$cookies = array(
$insecure_session_name . '=' . $args[':sid'],
$secure_session_name . '=' . $args[':ssid'],
);
foreach ($cookies as $cookie_key => $cookie) {
foreach (array('admin', $this->httpsUrl('admin')) as $url_key => $url) {
$this->curlClose();
$this->drupalGet($url, array(), array('Cookie: ' . $cookie));
if ($cookie_key == $url_key) {
$this->assertText(t('Administer'));
}
else {
$this->assertNoText(t('Administer'));
}
}
}
}
protected function httpsUrl($url) {
global $base_url;
return $base_url . '/modules/simpletest/tests/https.php?q=' . $url;
}
}