Chris@0: isSecure()) { Chris@0: $this->secureSessionName = $this->getSessionName(); Chris@0: $this->insecureSessionName = substr($this->getSessionName(), 1); Chris@0: } Chris@0: else { Chris@0: $this->secureSessionName = 'S' . $this->getSessionName(); Chris@0: $this->insecureSessionName = $this->getSessionName(); Chris@0: } Chris@0: } Chris@0: Chris@0: public function testHttpsSession() { Chris@0: $user = $this->drupalCreateUser(['access administration pages']); Chris@0: Chris@0: // Test HTTPS session handling by altering the form action to submit the Chris@0: // login form through https.php, which creates a mock HTTPS request. Chris@0: $this->loginHttps($user); Chris@0: Chris@0: // Test a second concurrent session. Chris@0: $this->curlClose(); Chris@0: $this->curlCookies = []; Chris@0: $this->loginHttps($user); Chris@0: Chris@0: // Check secure cookie on secure page. Chris@0: $this->assertTrue($this->cookies[$this->secureSessionName]['secure'], 'The secure cookie has the secure attribute'); Chris@0: // Check insecure cookie is not set. Chris@0: $this->assertFalse(isset($this->cookies[$this->insecureSessionName])); Chris@0: $ssid = $this->cookies[$this->secureSessionName]['value']; Chris@0: $this->assertSessionIds($ssid, 'Session has a non-empty SID and a correct secure SID.'); Chris@0: Chris@0: // Verify that user is logged in on secure URL. Chris@0: $this->drupalGet($this->httpsUrl('admin/config')); Chris@0: $this->assertText(t('Configuration')); Chris@0: $this->assertResponse(200); Chris@0: Chris@0: // Verify that user is not logged in on non-secure URL. Chris@0: $this->drupalGet($this->httpUrl('admin/config')); Chris@0: $this->assertNoText(t('Configuration')); Chris@0: $this->assertResponse(403); Chris@0: Chris@0: // Verify that empty SID cannot be used on the non-secure site. Chris@0: $this->curlClose(); Chris@0: $this->curlCookies = [$this->insecureSessionName . '=']; Chris@0: $this->drupalGet($this->httpUrl('admin/config')); Chris@0: $this->assertResponse(403); Chris@0: Chris@0: // Test HTTP session handling by altering the form action to submit the Chris@0: // login form through http.php, which creates a mock HTTP request on HTTPS Chris@0: // test environments. Chris@0: $this->curlClose(); Chris@0: $this->curlCookies = []; Chris@0: $this->loginHttp($user); Chris@0: $this->drupalGet($this->httpUrl('admin/config')); Chris@0: $this->assertResponse(200); Chris@0: $sid = $this->cookies[$this->insecureSessionName]['value']; Chris@0: $this->assertSessionIds($sid, '', 'Session has the correct SID and an empty secure SID.'); Chris@0: Chris@0: // Verify that empty secure SID cannot be used on the secure site. Chris@0: $this->curlClose(); Chris@0: $this->curlCookies = [$this->secureSessionName . '=']; Chris@0: $this->drupalGet($this->httpsUrl('admin/config')); Chris@0: $this->assertResponse(403); Chris@0: Chris@0: // Clear browser cookie jar. Chris@0: $this->cookies = []; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Log in a user via HTTP. Chris@0: * Chris@0: * Note that the parents $session_id and $loggedInUser is not updated. Chris@0: */ Chris@0: protected function loginHttp(AccountInterface $account) { Chris@0: $this->drupalGet('user/login'); Chris@0: Chris@0: // Alter the form action to submit the login form through http.php, which Chris@0: // creates a mock HTTP request on HTTPS test environments. Chris@0: $form = $this->xpath('//form[@id="user-login-form"]'); Chris@0: $form[0]['action'] = $this->httpUrl('user/login'); Chris@0: $edit = ['name' => $account->getUsername(), 'pass' => $account->pass_raw]; Chris@0: Chris@0: // When posting directly to the HTTP or HTTPS mock front controller, the Chris@0: // location header on the returned response is an absolute URL. That URL Chris@0: // needs to be converted into a request to the respective mock front Chris@0: // controller in order to retrieve the target page. Because the URL in the Chris@0: // location header needs to be modified, it is necessary to disable the Chris@0: // automatic redirects normally performed by parent::curlExec(). Chris@0: $maximum_redirects = $this->maximumRedirects; Chris@0: $this->maximumRedirects = 0; Chris@0: $this->drupalPostForm(NULL, $edit, t('Log in')); Chris@0: $this->maximumRedirects = $maximum_redirects; Chris@0: Chris@0: // Follow the location header. Chris@0: $path = $this->getPathFromLocationHeader(FALSE); Chris@0: $this->drupalGet($this->httpUrl($path)); Chris@0: $this->assertResponse(200); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Log in a user via HTTPS. Chris@0: * Chris@0: * Note that the parents $session_id and $loggedInUser is not updated. Chris@0: */ Chris@0: protected function loginHttps(AccountInterface $account) { Chris@0: $this->drupalGet('user/login'); Chris@0: Chris@0: // Alter the form action to submit the login form through https.php, which Chris@0: // creates a mock HTTPS request on HTTP test environments. Chris@0: $form = $this->xpath('//form[@id="user-login-form"]'); Chris@0: $form[0]['action'] = $this->httpsUrl('user/login'); Chris@0: $edit = ['name' => $account->getUsername(), 'pass' => $account->pass_raw]; Chris@0: Chris@0: // When posting directly to the HTTP or HTTPS mock front controller, the Chris@0: // location header on the returned response is an absolute URL. That URL Chris@0: // needs to be converted into a request to the respective mock front Chris@0: // controller in order to retrieve the target page. Because the URL in the Chris@0: // location header needs to be modified, it is necessary to disable the Chris@0: // automatic redirects normally performed by parent::curlExec(). Chris@0: $maximum_redirects = $this->maximumRedirects; Chris@0: $this->maximumRedirects = 0; Chris@0: $this->drupalPostForm(NULL, $edit, t('Log in')); Chris@0: $this->maximumRedirects = $maximum_redirects; Chris@0: Chris@0: // When logging in via the HTTPS mock, the child site will issue a session Chris@0: // cookie with the secure attribute set. While this cookie will be stored in Chris@0: // the curl handle, it will not be used on subsequent requests via the HTTPS Chris@0: // mock, unless when operating in a true HTTPS environment. Therefore it is Chris@0: // necessary to manually collect the session cookie and add it to the Chris@0: // curlCookies property such that it will be used on subsequent requests via Chris@0: // the HTTPS mock. Chris@0: $this->curlCookies = [$this->secureSessionName . '=' . $this->cookies[$this->secureSessionName]['value']]; Chris@0: Chris@0: // Follow the location header. Chris@0: $path = $this->getPathFromLocationHeader(TRUE); Chris@0: $this->drupalGet($this->httpsUrl($path)); Chris@0: $this->assertResponse(200); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Extract internal path from the location header on the response. Chris@0: */ Chris@0: protected function getPathFromLocationHeader($https = FALSE, $response_code = 303) { Chris@0: // Generate the base_url. Chris@0: $base_url = $this->container->get('url_generator')->generateFromRoute('', [], ['absolute' => TRUE]); Chris@0: if ($https) { Chris@0: $base_url = str_replace('http://', 'https://', $base_url); Chris@0: } Chris@0: else { Chris@0: $base_url = str_replace('https://', 'http://', $base_url); Chris@0: } Chris@0: Chris@0: // The mock front controllers (http.php and https.php) add the script name Chris@0: // to $_SERVER['REQEUST_URI'] and friends. Therefore it is necessary to Chris@0: // strip that also. Chris@0: $base_url .= 'index.php/'; Chris@0: Chris@0: // Extract relative path from location header. Chris@0: $this->assertResponse($response_code); Chris@0: $location = $this->drupalGetHeader('location'); Chris@0: Chris@0: $this->assertIdentical(strpos($location, $base_url), 0, 'Location header contains expected base URL'); Chris@0: return substr($location, strlen($base_url)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Test that there exists a session with two specific session IDs. Chris@0: * Chris@0: * @param $sid Chris@0: * The insecure session ID to search for. Chris@0: * @param $assertion_text Chris@0: * The text to display when we perform the assertion. Chris@0: * Chris@0: * @return Chris@0: * The result of assertTrue() that there's a session in the system that Chris@0: * has the given insecure and secure session IDs. Chris@0: */ Chris@0: protected function assertSessionIds($sid, $assertion_text) { Chris@0: $args = [ Chris@0: ':sid' => Crypt::hashBase64($sid), Chris@0: ]; Chris@0: return $this->assertTrue(db_query('SELECT timestamp FROM {sessions} WHERE sid = :sid', $args)->fetchField(), $assertion_text); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Builds a URL for submitting a mock HTTPS request to HTTP test environments. Chris@0: * Chris@0: * @param $url Chris@0: * A Drupal path such as 'user/login'. Chris@0: * Chris@0: * @return Chris@0: * URL prepared for the https.php mock front controller. Chris@0: */ Chris@0: protected function httpsUrl($url) { Chris@0: return 'core/modules/system/tests/https.php/' . $url; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Builds a URL for submitting a mock HTTP request to HTTPS test environments. Chris@0: * Chris@0: * @param $url Chris@0: * A Drupal path such as 'user/login'. Chris@0: * Chris@0: * @return Chris@0: * URL prepared for the http.php mock front controller. Chris@0: */ Chris@0: protected function httpUrl($url) { Chris@0: return 'core/modules/system/tests/http.php/' . $url; Chris@0: } Chris@0: Chris@0: }