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