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() {
$user = $this->drupalCreateUser(array('access content'));
// Enable sessions.
$this->sessionReset($user->uid);
$this->drupalLogin($user);
$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();
$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
// 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);
$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
/**
* 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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
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'));

Angie Byron
committed
// 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'));
$this->assertResponse(403);

Angie Byron
committed
}
// Clear browser cookie jar.
$this->cookies = array();

Dries Buytaert
committed
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"]');

Dries Buytaert
committed
321
322
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
$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'));

Dries Buytaert
committed
}
else {
$this->assertNoText(t('Administer'));
$this->assertResponse(403);