Projet

Général

Profil

Paste
Télécharger (21,1 ko) Statistiques
| Branche: | Révision:

root / drupal7 / modules / simpletest / tests / session.test @ 76597ebf

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(), 'drupal_save_session() correctly returns FALSE (inside of testing framework) when initially called with no arguments.', 'Session');
26
    $this->assertFalse(drupal_save_session(FALSE), 'drupal_save_session() correctly returns FALSE when called with FALSE.', 'Session');
27
    $this->assertFalse(drupal_save_session(), 'drupal_save_session() correctly returns FALSE when saving has been disabled.', 'Session');
28
    $this->assertTrue(drupal_save_session(TRUE), 'drupal_save_session() correctly returns TRUE when called with TRUE.', 'Session');
29
    $this->assertTrue(drupal_save_session(), 'drupal_save_session() correctly returns TRUE when saving has been enabled.', '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)), '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]) , '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, format_string('Found name: %name', array('%name' => $user->name)), '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]) , 'Found session ID after logging in.');
67
    $this->assertTrue($matches[1] != $original_session, 'Session ID changed after login.');
68
  }
69

    
70
  /**
71
   * Test data persistence via the session_test module callbacks.
72
   */
73
  function testDataPersistence() {
74
    $user = $this->drupalCreateUser(array('access content'));
75
    // Enable sessions.
76
    $this->sessionReset($user->uid);
77

    
78
    $this->drupalLogin($user);
79

    
80
    $value_1 = $this->randomName();
81
    $this->drupalGet('session-test/set/' . $value_1);
82
    $this->assertText($value_1, 'The session value was stored.', 'Session');
83
    $this->drupalGet('session-test/get');
84
    $this->assertText($value_1, 'Session correctly returned the stored data for an authenticated user.', 'Session');
85

    
86
    // Attempt to write over val_1. If drupal_save_session(FALSE) is working.
87
    // properly, val_1 will still be set.
88
    $value_2 = $this->randomName();
89
    $this->drupalGet('session-test/no-set/' . $value_2);
90
    $this->assertText($value_2, 'The session value was correctly passed to session-test/no-set.', 'Session');
91
    $this->drupalGet('session-test/get');
92
    $this->assertText($value_1, 'Session data is not saved for drupal_save_session(FALSE).', 'Session');
93

    
94
    // Switch browser cookie to anonymous user, then back to user 1.
95
    $this->sessionReset();
96
    $this->sessionReset($user->uid);
97
    $this->assertText($value_1, 'Session data persists through browser close.', 'Session');
98

    
99
    // Logout the user and make sure the stored value no longer persists.
100
    $this->drupalLogout();
101
    $this->sessionReset();
102
    $this->drupalGet('session-test/get');
103
    $this->assertNoText($value_1, "After logout, previous user's session data is not available.", 'Session');
104

    
105
    // Now try to store some data as an anonymous user.
106
    $value_3 = $this->randomName();
107
    $this->drupalGet('session-test/set/' . $value_3);
108
    $this->assertText($value_3, 'Session data stored for anonymous user.', 'Session');
109
    $this->drupalGet('session-test/get');
110
    $this->assertText($value_3, 'Session correctly returned the stored data for an anonymous user.', 'Session');
111

    
112
    // Try to store data when drupal_save_session(FALSE).
113
    $value_4 = $this->randomName();
114
    $this->drupalGet('session-test/no-set/' . $value_4);
115
    $this->assertText($value_4, 'The session value was correctly passed to session-test/no-set.', 'Session');
116
    $this->drupalGet('session-test/get');
117
    $this->assertText($value_3, 'Session data is not saved for drupal_save_session(FALSE).', 'Session');
118

    
119
    // Login, the data should persist.
120
    $this->drupalLogin($user);
121
    $this->sessionReset($user->uid);
122
    $this->drupalGet('session-test/get');
123
    $this->assertNoText($value_1, 'Session has persisted for an authenticated user after logging out and then back in.', 'Session');
124

    
125
    // Change session and create another user.
126
    $user2 = $this->drupalCreateUser(array('access content'));
127
    $this->sessionReset($user2->uid);
128
    $this->drupalLogin($user2);
129
  }
130

    
131
  /**
132
   * Test that empty anonymous sessions are destroyed.
133
   */
134
  function testEmptyAnonymousSession() {
135
    // Verify that no session is automatically created for anonymous user.
136
    $this->drupalGet('');
137
    $this->assertSessionCookie(FALSE);
138
    $this->assertSessionEmpty(TRUE);
139

    
140
    // The same behavior is expected when caching is enabled.
141
    variable_set('cache', 1);
142
    $this->drupalGet('');
143
    $this->assertSessionCookie(FALSE);
144
    $this->assertSessionEmpty(TRUE);
145
    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
146

    
147
    // Start a new session by setting a message.
148
    $this->drupalGet('session-test/set-message');
149
    $this->assertSessionCookie(TRUE);
150
    $this->assertTrue($this->drupalGetHeader('Set-Cookie'), 'New session was started.');
151

    
152
    // Display the message, during the same request the session is destroyed
153
    // and the session cookie is unset.
154
    $this->drupalGet('');
155
    $this->assertSessionCookie(FALSE);
156
    $this->assertSessionEmpty(FALSE);
157
    $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.');
158
    $this->assertText(t('This is a dummy message.'), 'Message was displayed.');
159
    $this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), 'Session cookie was deleted.');
160

    
161
    // Verify that session was destroyed.
162
    $this->drupalGet('');
163
    $this->assertSessionCookie(FALSE);
164
    $this->assertSessionEmpty(TRUE);
165
    $this->assertNoText(t('This is a dummy message.'), 'Message was not cached.');
166
    $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
167
    $this->assertFalse($this->drupalGetHeader('Set-Cookie'), 'New session was not started.');
168

    
169
    // Verify that no session is created if drupal_save_session(FALSE) is called.
170
    $this->drupalGet('session-test/set-message-but-dont-save');
171
    $this->assertSessionCookie(FALSE);
172
    $this->assertSessionEmpty(TRUE);
173

    
174
    // Verify that no message is displayed.
175
    $this->drupalGet('');
176
    $this->assertSessionCookie(FALSE);
177
    $this->assertSessionEmpty(TRUE);
178
    $this->assertNoText(t('This is a dummy message.'), 'The message was not saved.');
179
  }
180

    
181
  /**
182
   * Test that sessions are only saved when necessary.
183
   */
184
  function testSessionWrite() {
185
    $user = $this->drupalCreateUser(array('access content'));
186
    $this->drupalLogin($user);
187

    
188
    $sql = 'SELECT u.access, s.timestamp FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE u.uid = :uid';
189
    $times1 = db_query($sql, array(':uid' => $user->uid))->fetchObject();
190

    
191
    // Before every request we sleep one second to make sure that if the session
192
    // is saved, its timestamp will change.
193

    
194
    // Modify the session.
195
    sleep(1);
196
    $this->drupalGet('session-test/set/foo');
197
    $times2 = db_query($sql, array(':uid' => $user->uid))->fetchObject();
198
    $this->assertEqual($times2->access, $times1->access, 'Users table was not updated.');
199
    $this->assertNotEqual($times2->timestamp, $times1->timestamp, 'Sessions table was updated.');
200

    
201
    // Write the same value again, i.e. do not modify the session.
202
    sleep(1);
203
    $this->drupalGet('session-test/set/foo');
204
    $times3 = db_query($sql, array(':uid' => $user->uid))->fetchObject();
205
    $this->assertEqual($times3->access, $times1->access, 'Users table was not updated.');
206
    $this->assertEqual($times3->timestamp, $times2->timestamp, 'Sessions table was not updated.');
207

    
208
    // Do not change the session.
209
    sleep(1);
210
    $this->drupalGet('');
211
    $times4 = db_query($sql, array(':uid' => $user->uid))->fetchObject();
212
    $this->assertEqual($times4->access, $times3->access, 'Users table was not updated.');
213
    $this->assertEqual($times4->timestamp, $times3->timestamp, 'Sessions table was not updated.');
214

    
215
    // Force updating of users and sessions table once per second.
216
    variable_set('session_write_interval', 0);
217
    $this->drupalGet('');
218
    $times5 = db_query($sql, array(':uid' => $user->uid))->fetchObject();
219
    $this->assertNotEqual($times5->access, $times4->access, 'Users table was updated.');
220
    $this->assertNotEqual($times5->timestamp, $times4->timestamp, 'Sessions table was updated.');
221
  }
222

    
223
  /**
224
   * Test that empty session IDs are not allowed.
225
   */
226
  function testEmptySessionID() {
227
    $user = $this->drupalCreateUser(array('access content'));
228
    $this->drupalLogin($user);
229
    $this->drupalGet('session-test/is-logged-in');
230
    $this->assertResponse(200, 'User is logged in.');
231

    
232
    // Reset the sid in {sessions} to a blank string. This may exist in the
233
    // wild in some cases, although we normally prevent it from happening.
234
    db_query("UPDATE {sessions} SET sid = '' WHERE uid = :uid", array(':uid' => $user->uid));
235
    // Send a blank sid in the session cookie, and the session should no longer
236
    // be valid. Closing the curl handler will stop the previous session ID
237
    // from persisting.
238
    $this->curlClose();
239
    $this->additionalCurlOptions[CURLOPT_COOKIE] = rawurlencode($this->session_name) . '=;';
240
    $this->drupalGet('session-test/id-from-cookie');
241
    $this->assertRaw("session_id:\n", 'Session ID is blank as sent from cookie header.');
242
    // Assert that we have an anonymous session now.
243
    $this->drupalGet('session-test/is-logged-in');
244
    $this->assertResponse(403, 'An empty session ID is not allowed.');
245
  }
246

    
247
  /**
248
   * Reset the cookie file so that it refers to the specified user.
249
   *
250
   * @param $uid User id to set as the active session.
251
   */
252
  function sessionReset($uid = 0) {
253
    // Close the internal browser.
254
    $this->curlClose();
255
    $this->loggedInUser = FALSE;
256

    
257
    // Change cookie file for user.
258
    $this->cookieFile = file_stream_wrapper_get_instance_by_scheme('temporary')->getDirectoryPath() . '/cookie.' . $uid . '.txt';
259
    $this->additionalCurlOptions[CURLOPT_COOKIEFILE] = $this->cookieFile;
260
    $this->additionalCurlOptions[CURLOPT_COOKIESESSION] = TRUE;
261
    $this->drupalGet('session-test/get');
262
    $this->assertResponse(200, 'Session test module is correctly enabled.', 'Session');
263
  }
264

    
265
  /**
266
   * Assert whether the SimpleTest browser sent a session cookie.
267
   */
268
  function assertSessionCookie($sent) {
269
    if ($sent) {
270
      $this->assertNotNull($this->session_id, 'Session cookie was sent.');
271
    }
272
    else {
273
      $this->assertNull($this->session_id, 'Session cookie was not sent.');
274
    }
275
  }
276

    
277
  /**
278
   * Assert whether $_SESSION is empty at the beginning of the request.
279
   */
280
  function assertSessionEmpty($empty) {
281
    if ($empty) {
282
      $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '1', 'Session was empty.');
283
    }
284
    else {
285
      $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '0', 'Session was not empty.');
286
    }
287
  }
288
}
289

    
290
/**
291
 * Ensure that when running under HTTPS two session cookies are generated.
292
 */
293
class SessionHttpsTestCase extends DrupalWebTestCase {
294

    
295
  public static function getInfo() {
296
    return array(
297
      'name' => 'Session HTTPS handling',
298
      'description' => 'Ensure that when running under HTTPS two session cookies are generated.',
299
      'group' => 'Session'
300
    );
301
  }
302

    
303
  public function setUp() {
304
    parent::setUp('session_test');
305
  }
306

    
307
  protected function testHttpsSession() {
308
    global $is_https;
309

    
310
    if ($is_https) {
311
      $secure_session_name = session_name();
312
      $insecure_session_name = substr(session_name(), 1);
313
    }
314
    else {
315
      $secure_session_name = 'S' . session_name();
316
      $insecure_session_name = session_name();
317
    }
318

    
319
    $user = $this->drupalCreateUser(array('access administration pages'));
320

    
321
    // Test HTTPS session handling by altering the form action to submit the
322
    // login form through https.php, which creates a mock HTTPS request.
323
    $this->drupalGet('user');
324
    $form = $this->xpath('//form[@id="user-login"]');
325
    $form[0]['action'] = $this->httpsUrl('user');
326
    $edit = array('name' => $user->name, 'pass' => $user->pass_raw);
327
    $this->drupalPost(NULL, $edit, t('Log in'));
328

    
329
    // Test a second concurrent session.
330
    $this->curlClose();
331
    $this->drupalGet('user');
332
    $form = $this->xpath('//form[@id="user-login"]');
333
    $form[0]['action'] = $this->httpsUrl('user');
334
    $this->drupalPost(NULL, $edit, t('Log in'));
335

    
336
    // Check secure cookie on secure page.
337
    $this->assertTrue($this->cookies[$secure_session_name]['secure'], 'The secure cookie has the secure attribute');
338
    // Check insecure cookie is not set.
339
    $this->assertFalse(isset($this->cookies[$insecure_session_name]));
340
    $ssid = $this->cookies[$secure_session_name]['value'];
341
    $this->assertSessionIds($ssid, $ssid, 'Session has a non-empty SID and a correct secure SID.');
342
    $cookie = $secure_session_name . '=' . $ssid;
343

    
344
    // Verify that user is logged in on secure URL.
345
    $this->curlClose();
346
    $this->drupalGet($this->httpsUrl('admin/config'), array(), array('Cookie: ' . $cookie));
347
    $this->assertText(t('Configuration'));
348
    $this->assertResponse(200);
349

    
350
    // Verify that user is not logged in on non-secure URL.
351
    $this->curlClose();
352
    $this->drupalGet($this->httpUrl('admin/config'), array(), array('Cookie: ' . $cookie));
353
    $this->assertNoText(t('Configuration'));
354
    $this->assertResponse(403);
355

    
356
    // Verify that empty SID cannot be used on the non-secure site.
357
    $this->curlClose();
358
    $cookie = $insecure_session_name . '=';
359
    $this->drupalGet($this->httpUrl('admin/config'), array(), array('Cookie: ' . $cookie));
360
    $this->assertResponse(403);
361

    
362
    // Test HTTP session handling by altering the form action to submit the
363
    // login form through http.php, which creates a mock HTTP request on HTTPS
364
    // test environments.
365
    $this->curlClose();
366
    $this->drupalGet('user');
367
    $form = $this->xpath('//form[@id="user-login"]');
368
    $form[0]['action'] = $this->httpUrl('user');
369
    $edit = array('name' => $user->name, 'pass' => $user->pass_raw);
370
    $this->drupalPost(NULL, $edit, t('Log in'));
371
    $this->drupalGet($this->httpUrl('admin/config'));
372
    $this->assertResponse(200);
373
    $sid = $this->cookies[$insecure_session_name]['value'];
374
    $this->assertSessionIds($sid, '', 'Session has the correct SID and an empty secure SID.');
375

    
376
    // Verify that empty secure SID cannot be used on the secure site.
377
    $this->curlClose();
378
    $cookie = $secure_session_name . '=';
379
    $this->drupalGet($this->httpsUrl('admin/config'), array(), array('Cookie: ' . $cookie));
380
    $this->assertResponse(403);
381

    
382
    // Clear browser cookie jar.
383
    $this->cookies = array();
384

    
385
    if ($is_https) {
386
      // The functionality does not make sense when running on HTTPS.
387
      return;
388
    }
389

    
390
    // Enable secure pages.
391
    variable_set('https', TRUE);
392

    
393
    $this->curlClose();
394
    // Start an anonymous session on the insecure site.
395
    $session_data = $this->randomName();
396
    $this->drupalGet('session-test/set/' . $session_data);
397
    // Check secure cookie on insecure page.
398
    $this->assertFalse(isset($this->cookies[$secure_session_name]), 'The secure cookie is not sent on insecure pages.');
399
    // Check insecure cookie on insecure page.
400
    $this->assertFalse($this->cookies[$insecure_session_name]['secure'], 'The insecure cookie does not have the secure attribute');
401

    
402
    // Store the anonymous cookie so we can validate that its session is killed
403
    // after login.
404
    $anonymous_cookie = $insecure_session_name . '=' . $this->cookies[$insecure_session_name]['value'];
405

    
406
    // Check that password request form action is not secure.
407
    $this->drupalGet('user/password');
408
    $form = $this->xpath('//form[@id="user-pass"]');
409
    $this->assertNotEqual(substr($form[0]['action'], 0, 6), 'https:', 'Password request form action is not secure');
410
    $form[0]['action'] = $this->httpsUrl('user');
411

    
412
    // Check that user login form action is secure.
413
    $this->drupalGet('user');
414
    $form = $this->xpath('//form[@id="user-login"]');
415
    $this->assertEqual(substr($form[0]['action'], 0, 6), 'https:', 'Login form action is secure');
416
    $form[0]['action'] = $this->httpsUrl('user');
417

    
418
    $edit = array(
419
      'name' => $user->name,
420
      'pass' => $user->pass_raw,
421
    );
422
    $this->drupalPost(NULL, $edit, t('Log in'));
423
    // Check secure cookie on secure page.
424
    $this->assertTrue($this->cookies[$secure_session_name]['secure'], 'The secure cookie has the secure attribute');
425
    // Check insecure cookie on secure page.
426
    $this->assertFalse($this->cookies[$insecure_session_name]['secure'], 'The insecure cookie does not have the secure attribute');
427

    
428
    $sid = $this->cookies[$insecure_session_name]['value'];
429
    $ssid = $this->cookies[$secure_session_name]['value'];
430
    $this->assertSessionIds($sid, $ssid, 'Session has both secure and insecure SIDs');
431
    $cookies = array(
432
      $insecure_session_name . '=' . $sid,
433
      $secure_session_name . '=' . $ssid,
434
    );
435

    
436
    // Test that session data saved before login is still available on the
437
    // authenticated session.
438
    $this->drupalGet('session-test/get');
439
    $this->assertText($session_data, 'Session correctly returned the stored data set by the anonymous session.');
440

    
441
    foreach ($cookies as $cookie_key => $cookie) {
442
      foreach (array('admin/config', $this->httpsUrl('admin/config')) as $url_key => $url) {
443
        $this->curlClose();
444

    
445
        $this->drupalGet($url, array(), array('Cookie: ' . $cookie));
446
        if ($cookie_key == $url_key) {
447
          $this->assertText(t('Configuration'));
448
          $this->assertResponse(200);
449
        }
450
        else {
451
          $this->assertNoText(t('Configuration'));
452
          $this->assertResponse(403);
453
        }
454
      }
455
    }
456

    
457
    // Test that session data saved before login is not available using the
458
    // pre-login anonymous cookie.
459
    $this->cookies = array();
460
    $this->drupalGet('session-test/get', array('Cookie: ' . $anonymous_cookie));
461
    $this->assertNoText($session_data, 'Initial anonymous session is inactive after login.');
462

    
463
    // Clear browser cookie jar.
464
    $this->cookies = array();
465

    
466
    // Start an anonymous session on the secure site.
467
    $this->drupalGet($this->httpsUrl('session-test/set/1'));
468

    
469
    // Mock a login to the secure site using the secure session cookie.
470
    $this->drupalGet('user');
471
    $form = $this->xpath('//form[@id="user-login"]');
472
    $form[0]['action'] = $this->httpsUrl('user');
473
    $this->drupalPost(NULL, $edit, t('Log in'));
474

    
475
    // Test that the user is also authenticated on the insecure site.
476
    $this->drupalGet("user/{$user->uid}/edit");
477
    $this->assertResponse(200);
478
  }
479

    
480
  /**
481
   * Test that there exists a session with two specific session IDs.
482
   *
483
   * @param $sid
484
   *   The insecure session ID to search for.
485
   * @param $ssid
486
   *   The secure session ID to search for.
487
   * @param $assertion_text
488
   *   The text to display when we perform the assertion.
489
   *
490
   * @return
491
   *   The result of assertTrue() that there's a session in the system that
492
   *   has the given insecure and secure session IDs.
493
   */
494
  protected function assertSessionIds($sid, $ssid, $assertion_text) {
495
    $args = array(
496
      ':sid' => $sid,
497
      ':ssid' => $ssid,
498
    );
499
    return $this->assertTrue(db_query('SELECT timestamp FROM {sessions} WHERE sid = :sid AND ssid = :ssid', $args)->fetchField(), $assertion_text);
500
  }
501

    
502
  /**
503
   * Builds a URL for submitting a mock HTTPS request to HTTP test environments.
504
   *
505
   * @param $url
506
   *   A Drupal path such as 'user'.
507
   *
508
   * @return
509
   *   An absolute URL.
510
   */
511
  protected function httpsUrl($url) {
512
    global $base_url;
513
    return $base_url . '/modules/simpletest/tests/https.php?q=' . $url;
514
  }
515

    
516
  /**
517
   * Builds a URL for submitting a mock HTTP request to HTTPS test environments.
518
   *
519
   * @param $url
520
   *   A Drupal path such as 'user'.
521
   *
522
   * @return
523
   *   An absolute URL.
524
   */
525
  protected function httpUrl($url) {
526
    global $base_url;
527
    return $base_url . '/modules/simpletest/tests/http.php?q=' . $url;
528
  }
529
}
530