Updated to Drupal 8.6.4, which is PHP 7.3 friendly. Also updated HTMLaw library....
[yaffs-website] / web / core / modules / system / src / Tests / Session / SessionTest.php
1 <?php
2
3 namespace Drupal\system\Tests\Session;
4
5 use Drupal\simpletest\WebTestBase;
6
7 /**
8  * Drupal session handling tests.
9  *
10  * @group Session
11  */
12 class SessionTest extends WebTestBase {
13
14   /**
15    * Modules to enable.
16    *
17    * @var array
18    */
19   public static $modules = ['session_test'];
20
21   protected $dumpHeaders = TRUE;
22
23   /**
24    * Tests for \Drupal\Core\Session\WriteSafeSessionHandler::setSessionWritable()
25    * ::isSessionWritable and \Drupal\Core\Session\SessionManager::regenerate().
26    */
27   public function testSessionSaveRegenerate() {
28     $session_handler = $this->container->get('session_handler.write_safe');
29     $this->assertTrue($session_handler->isSessionWritable(), 'session_handler->isSessionWritable() initially returns TRUE.');
30     $session_handler->setSessionWritable(FALSE);
31     $this->assertFalse($session_handler->isSessionWritable(), '$session_handler->isSessionWritable() returns FALSE after disabling.');
32     $session_handler->setSessionWritable(TRUE);
33     $this->assertTrue($session_handler->isSessionWritable(), '$session_handler->isSessionWritable() returns TRUE after enabling.');
34
35     // Test session hardening code from SA-2008-044.
36     $user = $this->drupalCreateUser();
37
38     // Enable sessions.
39     $this->sessionReset($user->id());
40
41     // Make sure the session cookie is set as HttpOnly.
42     $this->drupalLogin($user);
43     $this->assertTrue(preg_match('/HttpOnly/i', $this->drupalGetHeader('Set-Cookie', TRUE)), 'Session cookie is set as HttpOnly.');
44     $this->drupalLogout();
45
46     // Verify that the session is regenerated if a module calls exit
47     // in hook_user_login().
48     $user->name = 'session_test_user';
49     $user->save();
50     $this->drupalGet('session-test/id');
51     $matches = [];
52     preg_match('/\s*session_id:(.*)\n/', $this->getRawContent(), $matches);
53     $this->assertTrue(!empty($matches[1]), 'Found session ID before logging in.');
54     $original_session = $matches[1];
55
56     // We cannot use $this->drupalLogin($user); because we exit in
57     // session_test_user_login() which breaks a normal assertion.
58     $edit = [
59       'name' => $user->getUsername(),
60       'pass' => $user->pass_raw,
61     ];
62     $this->drupalPostForm('user/login', $edit, t('Log in'));
63     $this->drupalGet('user');
64     $pass = $this->assertText($user->getUsername(), format_string('Found name: %name', ['%name' => $user->getUsername()]), 'User login');
65     $this->_logged_in = $pass;
66
67     $this->drupalGet('session-test/id');
68     $matches = [];
69     preg_match('/\s*session_id:(.*)\n/', $this->getRawContent(), $matches);
70     $this->assertTrue(!empty($matches[1]), 'Found session ID after logging in.');
71     $this->assertTrue($matches[1] != $original_session, 'Session ID changed after login.');
72   }
73
74   /**
75    * Test data persistence via the session_test module callbacks.
76    */
77   public function testDataPersistence() {
78     $user = $this->drupalCreateUser([]);
79     // Enable sessions.
80     $this->sessionReset($user->id());
81
82     $this->drupalLogin($user);
83
84     $value_1 = $this->randomMachineName();
85     $this->drupalGet('session-test/set/' . $value_1);
86     $this->assertText($value_1, 'The session value was stored.', 'Session');
87     $this->drupalGet('session-test/get');
88     $this->assertText($value_1, 'Session correctly returned the stored data for an authenticated user.', 'Session');
89
90     // Attempt to write over val_1. If drupal_save_session(FALSE) is working.
91     // properly, val_1 will still be set.
92     $value_2 = $this->randomMachineName();
93     $this->drupalGet('session-test/no-set/' . $value_2);
94     $this->assertText($value_2, 'The session value was correctly passed to session-test/no-set.', 'Session');
95     $this->drupalGet('session-test/get');
96     $this->assertText($value_1, 'Session data is not saved for drupal_save_session(FALSE).', 'Session');
97
98     // Switch browser cookie to anonymous user, then back to user 1.
99     $this->sessionReset();
100     $this->sessionReset($user->id());
101     $this->assertText($value_1, 'Session data persists through browser close.', 'Session');
102
103     // Logout the user and make sure the stored value no longer persists.
104     $this->drupalLogout();
105     $this->sessionReset();
106     $this->drupalGet('session-test/get');
107     $this->assertNoText($value_1, "After logout, previous user's session data is not available.", 'Session');
108
109     // Now try to store some data as an anonymous user.
110     $value_3 = $this->randomMachineName();
111     $this->drupalGet('session-test/set/' . $value_3);
112     $this->assertText($value_3, 'Session data stored for anonymous user.', 'Session');
113     $this->drupalGet('session-test/get');
114     $this->assertText($value_3, 'Session correctly returned the stored data for an anonymous user.', 'Session');
115
116     // Try to store data when drupal_save_session(FALSE).
117     $value_4 = $this->randomMachineName();
118     $this->drupalGet('session-test/no-set/' . $value_4);
119     $this->assertText($value_4, 'The session value was correctly passed to session-test/no-set.', 'Session');
120     $this->drupalGet('session-test/get');
121     $this->assertText($value_3, 'Session data is not saved for drupal_save_session(FALSE).', 'Session');
122
123     // Login, the data should persist.
124     $this->drupalLogin($user);
125     $this->sessionReset($user->id());
126     $this->drupalGet('session-test/get');
127     $this->assertNoText($value_1, 'Session has persisted for an authenticated user after logging out and then back in.', 'Session');
128
129     // Change session and create another user.
130     $user2 = $this->drupalCreateUser([]);
131     $this->sessionReset($user2->id());
132     $this->drupalLogin($user2);
133   }
134
135   /**
136    * Tests storing data in Session() object.
137    */
138   public function testSessionPersistenceOnLogin() {
139     // Store information via hook_user_login().
140     $user = $this->drupalCreateUser();
141     $this->drupalLogin($user);
142     // Test property added to session object form hook_user_login().
143     $this->drupalGet('session-test/get-from-session-object');
144     $this->assertText('foobar', 'Session data is saved in Session() object.', 'Session');
145   }
146
147   /**
148    * Test that empty anonymous sessions are destroyed.
149    */
150   public function testEmptyAnonymousSession() {
151     // Disable the dynamic_page_cache module; it'd cause session_test's debug
152     // output (that is added in
153     // SessionTestSubscriber::onKernelResponseSessionTest()) to not be added.
154     $this->container->get('module_installer')->uninstall(['dynamic_page_cache']);
155
156     // Verify that no session is automatically created for anonymous user when
157     // page caching is disabled.
158     $this->container->get('module_installer')->uninstall(['page_cache']);
159     $this->drupalGet('');
160     $this->assertSessionCookie(FALSE);
161     $this->assertSessionEmpty(TRUE);
162
163     // The same behavior is expected when caching is enabled.
164     $this->container->get('module_installer')->install(['page_cache']);
165     $config = $this->config('system.performance');
166     $config->set('cache.page.max_age', 300);
167     $config->save();
168     $this->drupalGet('');
169     $this->assertSessionCookie(FALSE);
170     // @todo Reinstate when REQUEST and RESPONSE events fire for cached pages.
171     // $this->assertSessionEmpty(TRUE);
172     $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.');
173
174     // Start a new session by setting a message.
175     $this->drupalGet('session-test/set-message');
176     $this->assertSessionCookie(TRUE);
177     $this->assertTrue($this->drupalGetHeader('Set-Cookie'), 'New session was started.');
178
179     // Display the message, during the same request the session is destroyed
180     // and the session cookie is unset.
181     $this->drupalGet('');
182     $this->assertSessionCookie(FALSE);
183     $this->assertSessionEmpty(FALSE);
184     $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.');
185     $this->assertText(t('This is a dummy message.'), 'Message was displayed.');
186     $this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), 'Session cookie was deleted.');
187
188     // Verify that session was destroyed.
189     $this->drupalGet('');
190     $this->assertSessionCookie(FALSE);
191     // @todo Reinstate when REQUEST and RESPONSE events fire for cached pages.
192     // $this->assertSessionEmpty(TRUE);
193     $this->assertNoText(t('This is a dummy message.'), 'Message was not cached.');
194     $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.');
195     $this->assertFalse($this->drupalGetHeader('Set-Cookie'), 'New session was not started.');
196
197     // Verify that no session is created if drupal_save_session(FALSE) is called.
198     $this->drupalGet('session-test/set-message-but-dont-save');
199     $this->assertSessionCookie(FALSE);
200     $this->assertSessionEmpty(TRUE);
201
202     // Verify that no message is displayed.
203     $this->drupalGet('');
204     $this->assertSessionCookie(FALSE);
205     // @todo Reinstate when REQUEST and RESPONSE events fire for cached pages.
206     // $this->assertSessionEmpty(TRUE);
207     $this->assertNoText(t('This is a dummy message.'), 'The message was not saved.');
208   }
209
210   /**
211    * Test that sessions are only saved when necessary.
212    */
213   public function testSessionWrite() {
214     $user = $this->drupalCreateUser([]);
215     $this->drupalLogin($user);
216
217     $sql = 'SELECT u.access, s.timestamp FROM {users_field_data} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE u.uid = :uid';
218     $times1 = db_query($sql, [':uid' => $user->id()])->fetchObject();
219
220     // Before every request we sleep one second to make sure that if the session
221     // is saved, its timestamp will change.
222
223     // Modify the session.
224     sleep(1);
225     $this->drupalGet('session-test/set/foo');
226     $times2 = db_query($sql, [':uid' => $user->id()])->fetchObject();
227     $this->assertEqual($times2->access, $times1->access, 'Users table was not updated.');
228     $this->assertNotEqual($times2->timestamp, $times1->timestamp, 'Sessions table was updated.');
229
230     // Write the same value again, i.e. do not modify the session.
231     sleep(1);
232     $this->drupalGet('session-test/set/foo');
233     $times3 = db_query($sql, [':uid' => $user->id()])->fetchObject();
234     $this->assertEqual($times3->access, $times1->access, 'Users table was not updated.');
235     $this->assertEqual($times3->timestamp, $times2->timestamp, 'Sessions table was not updated.');
236
237     // Do not change the session.
238     sleep(1);
239     $this->drupalGet('');
240     $times4 = db_query($sql, [':uid' => $user->id()])->fetchObject();
241     $this->assertEqual($times4->access, $times3->access, 'Users table was not updated.');
242     $this->assertEqual($times4->timestamp, $times3->timestamp, 'Sessions table was not updated.');
243
244     // Force updating of users and sessions table once per second.
245     $this->settingsSet('session_write_interval', 0);
246     // Write that value also into the test settings.php file.
247     $settings['settings']['session_write_interval'] = (object) [
248       'value' => 0,
249       'required' => TRUE,
250     ];
251     $this->writeSettings($settings);
252     $this->drupalGet('');
253     $times5 = db_query($sql, [':uid' => $user->id()])->fetchObject();
254     $this->assertNotEqual($times5->access, $times4->access, 'Users table was updated.');
255     $this->assertNotEqual($times5->timestamp, $times4->timestamp, 'Sessions table was updated.');
256   }
257
258   /**
259    * Test that empty session IDs are not allowed.
260    */
261   public function testEmptySessionID() {
262     $user = $this->drupalCreateUser([]);
263     $this->drupalLogin($user);
264     $this->drupalGet('session-test/is-logged-in');
265     $this->assertResponse(200, 'User is logged in.');
266
267     // Reset the sid in {sessions} to a blank string. This may exist in the
268     // wild in some cases, although we normally prevent it from happening.
269     db_query("UPDATE {sessions} SET sid = '' WHERE uid = :uid", [':uid' => $user->id()]);
270     // Send a blank sid in the session cookie, and the session should no longer
271     // be valid. Closing the curl handler will stop the previous session ID
272     // from persisting.
273     $this->curlClose();
274     $this->additionalCurlOptions[CURLOPT_COOKIE] = rawurlencode($this->getSessionName()) . '=;';
275     $this->drupalGet('session-test/id-from-cookie');
276     $this->assertRaw("session_id:\n", 'Session ID is blank as sent from cookie header.');
277     // Assert that we have an anonymous session now.
278     $this->drupalGet('session-test/is-logged-in');
279     $this->assertResponse(403, 'An empty session ID is not allowed.');
280   }
281
282   /**
283    * Reset the cookie file so that it refers to the specified user.
284    *
285    * @param $uid
286    *   User id to set as the active session.
287    */
288   public function sessionReset($uid = 0) {
289     // Close the internal browser.
290     $this->curlClose();
291     $this->loggedInUser = FALSE;
292
293     // Change cookie file for user.
294     $this->cookieFile = \Drupal::service('stream_wrapper_manager')->getViaScheme('temporary')->getDirectoryPath() . '/cookie.' . $uid . '.txt';
295     $this->additionalCurlOptions[CURLOPT_COOKIEFILE] = $this->cookieFile;
296     $this->additionalCurlOptions[CURLOPT_COOKIESESSION] = TRUE;
297     $this->drupalGet('session-test/get');
298     $this->assertResponse(200, 'Session test module is correctly enabled.', 'Session');
299   }
300
301   /**
302    * Assert whether the SimpleTest browser sent a session cookie.
303    */
304   public function assertSessionCookie($sent) {
305     if ($sent) {
306       $this->assertNotNull($this->sessionId, 'Session cookie was sent.');
307     }
308     else {
309       $this->assertNull($this->sessionId, 'Session cookie was not sent.');
310     }
311   }
312
313   /**
314    * Assert whether $_SESSION is empty at the beginning of the request.
315    */
316   public function assertSessionEmpty($empty) {
317     if ($empty) {
318       $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '1', 'Session was empty.');
319     }
320     else {
321       $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '0', 'Session was not empty.');
322     }
323   }
324
325 }