annotate core/modules/system/src/Tests/Session/SessionHttpsTest.php @ 12:7a779792577d

Update Drupal core to v8.4.5 (via Composer)
author Chris Cannam
date Fri, 23 Feb 2018 15:52:07 +0000
parents 4c8ae668cc8c
children 129ea1e6d783
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\system\Tests\Session;
Chris@0 4
Chris@0 5 use Drupal\simpletest\WebTestBase;
Chris@0 6 use Symfony\Component\HttpFoundation\Request;
Chris@0 7 use Drupal\Component\Utility\Crypt;
Chris@0 8 use Drupal\Core\Session\AccountInterface;
Chris@0 9
Chris@0 10 /**
Chris@0 11 * Ensure that when running under HTTPS two session cookies are generated.
Chris@0 12 *
Chris@0 13 * @group Session
Chris@0 14 */
Chris@0 15 class SessionHttpsTest extends WebTestBase {
Chris@0 16
Chris@0 17 /**
Chris@0 18 * The name of the session cookie when using HTTP.
Chris@0 19 *
Chris@0 20 * @var string
Chris@0 21 */
Chris@0 22 protected $insecureSessionName;
Chris@0 23
Chris@0 24 /**
Chris@0 25 * The name of the session cookie when using HTTPS.
Chris@0 26 *
Chris@0 27 * @var string
Chris@0 28 */
Chris@0 29 protected $secureSessionName;
Chris@0 30
Chris@0 31 /**
Chris@0 32 * Modules to enable.
Chris@0 33 *
Chris@0 34 * @var array
Chris@0 35 */
Chris@0 36 public static $modules = ['session_test'];
Chris@0 37
Chris@0 38 protected function setUp() {
Chris@0 39 parent::setUp();
Chris@0 40
Chris@0 41 $request = Request::createFromGlobals();
Chris@0 42 if ($request->isSecure()) {
Chris@0 43 $this->secureSessionName = $this->getSessionName();
Chris@0 44 $this->insecureSessionName = substr($this->getSessionName(), 1);
Chris@0 45 }
Chris@0 46 else {
Chris@0 47 $this->secureSessionName = 'S' . $this->getSessionName();
Chris@0 48 $this->insecureSessionName = $this->getSessionName();
Chris@0 49 }
Chris@0 50 }
Chris@0 51
Chris@0 52 public function testHttpsSession() {
Chris@0 53 $user = $this->drupalCreateUser(['access administration pages']);
Chris@0 54
Chris@0 55 // Test HTTPS session handling by altering the form action to submit the
Chris@0 56 // login form through https.php, which creates a mock HTTPS request.
Chris@0 57 $this->loginHttps($user);
Chris@0 58
Chris@0 59 // Test a second concurrent session.
Chris@0 60 $this->curlClose();
Chris@0 61 $this->curlCookies = [];
Chris@0 62 $this->loginHttps($user);
Chris@0 63
Chris@0 64 // Check secure cookie on secure page.
Chris@0 65 $this->assertTrue($this->cookies[$this->secureSessionName]['secure'], 'The secure cookie has the secure attribute');
Chris@0 66 // Check insecure cookie is not set.
Chris@0 67 $this->assertFalse(isset($this->cookies[$this->insecureSessionName]));
Chris@0 68 $ssid = $this->cookies[$this->secureSessionName]['value'];
Chris@0 69 $this->assertSessionIds($ssid, 'Session has a non-empty SID and a correct secure SID.');
Chris@0 70
Chris@0 71 // Verify that user is logged in on secure URL.
Chris@0 72 $this->drupalGet($this->httpsUrl('admin/config'));
Chris@0 73 $this->assertText(t('Configuration'));
Chris@0 74 $this->assertResponse(200);
Chris@0 75
Chris@0 76 // Verify that user is not logged in on non-secure URL.
Chris@0 77 $this->drupalGet($this->httpUrl('admin/config'));
Chris@0 78 $this->assertNoText(t('Configuration'));
Chris@0 79 $this->assertResponse(403);
Chris@0 80
Chris@0 81 // Verify that empty SID cannot be used on the non-secure site.
Chris@0 82 $this->curlClose();
Chris@0 83 $this->curlCookies = [$this->insecureSessionName . '='];
Chris@0 84 $this->drupalGet($this->httpUrl('admin/config'));
Chris@0 85 $this->assertResponse(403);
Chris@0 86
Chris@0 87 // Test HTTP session handling by altering the form action to submit the
Chris@0 88 // login form through http.php, which creates a mock HTTP request on HTTPS
Chris@0 89 // test environments.
Chris@0 90 $this->curlClose();
Chris@0 91 $this->curlCookies = [];
Chris@0 92 $this->loginHttp($user);
Chris@0 93 $this->drupalGet($this->httpUrl('admin/config'));
Chris@0 94 $this->assertResponse(200);
Chris@0 95 $sid = $this->cookies[$this->insecureSessionName]['value'];
Chris@0 96 $this->assertSessionIds($sid, '', 'Session has the correct SID and an empty secure SID.');
Chris@0 97
Chris@0 98 // Verify that empty secure SID cannot be used on the secure site.
Chris@0 99 $this->curlClose();
Chris@0 100 $this->curlCookies = [$this->secureSessionName . '='];
Chris@0 101 $this->drupalGet($this->httpsUrl('admin/config'));
Chris@0 102 $this->assertResponse(403);
Chris@0 103
Chris@0 104 // Clear browser cookie jar.
Chris@0 105 $this->cookies = [];
Chris@0 106 }
Chris@0 107
Chris@0 108 /**
Chris@0 109 * Log in a user via HTTP.
Chris@0 110 *
Chris@0 111 * Note that the parents $session_id and $loggedInUser is not updated.
Chris@0 112 */
Chris@0 113 protected function loginHttp(AccountInterface $account) {
Chris@0 114 $this->drupalGet('user/login');
Chris@0 115
Chris@0 116 // Alter the form action to submit the login form through http.php, which
Chris@0 117 // creates a mock HTTP request on HTTPS test environments.
Chris@0 118 $form = $this->xpath('//form[@id="user-login-form"]');
Chris@0 119 $form[0]['action'] = $this->httpUrl('user/login');
Chris@0 120 $edit = ['name' => $account->getUsername(), 'pass' => $account->pass_raw];
Chris@0 121
Chris@0 122 // When posting directly to the HTTP or HTTPS mock front controller, the
Chris@0 123 // location header on the returned response is an absolute URL. That URL
Chris@0 124 // needs to be converted into a request to the respective mock front
Chris@0 125 // controller in order to retrieve the target page. Because the URL in the
Chris@0 126 // location header needs to be modified, it is necessary to disable the
Chris@0 127 // automatic redirects normally performed by parent::curlExec().
Chris@0 128 $maximum_redirects = $this->maximumRedirects;
Chris@0 129 $this->maximumRedirects = 0;
Chris@0 130 $this->drupalPostForm(NULL, $edit, t('Log in'));
Chris@0 131 $this->maximumRedirects = $maximum_redirects;
Chris@0 132
Chris@0 133 // Follow the location header.
Chris@0 134 $path = $this->getPathFromLocationHeader(FALSE);
Chris@0 135 $this->drupalGet($this->httpUrl($path));
Chris@0 136 $this->assertResponse(200);
Chris@0 137 }
Chris@0 138
Chris@0 139 /**
Chris@0 140 * Log in a user via HTTPS.
Chris@0 141 *
Chris@0 142 * Note that the parents $session_id and $loggedInUser is not updated.
Chris@0 143 */
Chris@0 144 protected function loginHttps(AccountInterface $account) {
Chris@0 145 $this->drupalGet('user/login');
Chris@0 146
Chris@0 147 // Alter the form action to submit the login form through https.php, which
Chris@0 148 // creates a mock HTTPS request on HTTP test environments.
Chris@0 149 $form = $this->xpath('//form[@id="user-login-form"]');
Chris@0 150 $form[0]['action'] = $this->httpsUrl('user/login');
Chris@0 151 $edit = ['name' => $account->getUsername(), 'pass' => $account->pass_raw];
Chris@0 152
Chris@0 153 // When posting directly to the HTTP or HTTPS mock front controller, the
Chris@0 154 // location header on the returned response is an absolute URL. That URL
Chris@0 155 // needs to be converted into a request to the respective mock front
Chris@0 156 // controller in order to retrieve the target page. Because the URL in the
Chris@0 157 // location header needs to be modified, it is necessary to disable the
Chris@0 158 // automatic redirects normally performed by parent::curlExec().
Chris@0 159 $maximum_redirects = $this->maximumRedirects;
Chris@0 160 $this->maximumRedirects = 0;
Chris@0 161 $this->drupalPostForm(NULL, $edit, t('Log in'));
Chris@0 162 $this->maximumRedirects = $maximum_redirects;
Chris@0 163
Chris@0 164 // When logging in via the HTTPS mock, the child site will issue a session
Chris@0 165 // cookie with the secure attribute set. While this cookie will be stored in
Chris@0 166 // the curl handle, it will not be used on subsequent requests via the HTTPS
Chris@0 167 // mock, unless when operating in a true HTTPS environment. Therefore it is
Chris@0 168 // necessary to manually collect the session cookie and add it to the
Chris@0 169 // curlCookies property such that it will be used on subsequent requests via
Chris@0 170 // the HTTPS mock.
Chris@0 171 $this->curlCookies = [$this->secureSessionName . '=' . $this->cookies[$this->secureSessionName]['value']];
Chris@0 172
Chris@0 173 // Follow the location header.
Chris@0 174 $path = $this->getPathFromLocationHeader(TRUE);
Chris@0 175 $this->drupalGet($this->httpsUrl($path));
Chris@0 176 $this->assertResponse(200);
Chris@0 177 }
Chris@0 178
Chris@0 179 /**
Chris@0 180 * Extract internal path from the location header on the response.
Chris@0 181 */
Chris@0 182 protected function getPathFromLocationHeader($https = FALSE, $response_code = 303) {
Chris@0 183 // Generate the base_url.
Chris@0 184 $base_url = $this->container->get('url_generator')->generateFromRoute('<front>', [], ['absolute' => TRUE]);
Chris@0 185 if ($https) {
Chris@0 186 $base_url = str_replace('http://', 'https://', $base_url);
Chris@0 187 }
Chris@0 188 else {
Chris@0 189 $base_url = str_replace('https://', 'http://', $base_url);
Chris@0 190 }
Chris@0 191
Chris@0 192 // The mock front controllers (http.php and https.php) add the script name
Chris@0 193 // to $_SERVER['REQEUST_URI'] and friends. Therefore it is necessary to
Chris@0 194 // strip that also.
Chris@0 195 $base_url .= 'index.php/';
Chris@0 196
Chris@0 197 // Extract relative path from location header.
Chris@0 198 $this->assertResponse($response_code);
Chris@0 199 $location = $this->drupalGetHeader('location');
Chris@0 200
Chris@0 201 $this->assertIdentical(strpos($location, $base_url), 0, 'Location header contains expected base URL');
Chris@0 202 return substr($location, strlen($base_url));
Chris@0 203 }
Chris@0 204
Chris@0 205 /**
Chris@0 206 * Test that there exists a session with two specific session IDs.
Chris@0 207 *
Chris@0 208 * @param $sid
Chris@0 209 * The insecure session ID to search for.
Chris@0 210 * @param $assertion_text
Chris@0 211 * The text to display when we perform the assertion.
Chris@0 212 *
Chris@0 213 * @return
Chris@0 214 * The result of assertTrue() that there's a session in the system that
Chris@0 215 * has the given insecure and secure session IDs.
Chris@0 216 */
Chris@0 217 protected function assertSessionIds($sid, $assertion_text) {
Chris@0 218 $args = [
Chris@0 219 ':sid' => Crypt::hashBase64($sid),
Chris@0 220 ];
Chris@0 221 return $this->assertTrue(db_query('SELECT timestamp FROM {sessions} WHERE sid = :sid', $args)->fetchField(), $assertion_text);
Chris@0 222 }
Chris@0 223
Chris@0 224 /**
Chris@0 225 * Builds a URL for submitting a mock HTTPS request to HTTP test environments.
Chris@0 226 *
Chris@0 227 * @param $url
Chris@0 228 * A Drupal path such as 'user/login'.
Chris@0 229 *
Chris@0 230 * @return
Chris@0 231 * URL prepared for the https.php mock front controller.
Chris@0 232 */
Chris@0 233 protected function httpsUrl($url) {
Chris@0 234 return 'core/modules/system/tests/https.php/' . $url;
Chris@0 235 }
Chris@0 236
Chris@0 237 /**
Chris@0 238 * Builds a URL for submitting a mock HTTP request to HTTPS test environments.
Chris@0 239 *
Chris@0 240 * @param $url
Chris@0 241 * A Drupal path such as 'user/login'.
Chris@0 242 *
Chris@0 243 * @return
Chris@0 244 * URL prepared for the http.php mock front controller.
Chris@0 245 */
Chris@0 246 protected function httpUrl($url) {
Chris@0 247 return 'core/modules/system/tests/http.php/' . $url;
Chris@0 248 }
Chris@0 249
Chris@0 250 }