diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 2c58ccc..ad3b7d7 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -810,7 +810,6 @@ protected function initializeRequestGlobals(Request $request) { global $base_url; // Set and derived from $base_url by this function. global $base_path, $base_root, $script_path; - global $base_secure_url, $base_insecure_url; // @todo Refactor with the Symfony Request object. if (isset($base_url)) { @@ -848,8 +847,6 @@ protected function initializeRequestGlobals(Request $request) { $base_path = '/'; } } - $base_secure_url = str_replace('http://', 'https://', $base_url); - $base_insecure_url = str_replace('https://', 'http://', $base_url); // Determine the path of the script relative to the base path, and add a // trailing slash. This is needed for creating URLs to Drupal pages. diff --git a/core/modules/system/src/Tests/Session/SessionHttpsTest.php b/core/modules/system/src/Tests/Session/SessionHttpsTest.php index 9af167b..af6721d 100644 --- a/core/modules/system/src/Tests/Session/SessionHttpsTest.php +++ b/core/modules/system/src/Tests/Session/SessionHttpsTest.php @@ -11,6 +11,7 @@ use Symfony\Component\HttpFoundation\Request; use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\String; +use Drupal\Core\Session\AccountInterface; /** * Ensure that when running under HTTPS two session cookies are generated. @@ -20,6 +21,20 @@ class SessionHttpsTest extends WebTestBase { /** + * The name of the session cookie when using HTTP. + * + * @var string + */ + protected $insecureSessionName; + + /** + * The name of the session cookie when using HTTPS. + * + * @var string + */ + protected $secureSessionName; + + /** * Modules to enable. * * @var array @@ -28,81 +43,68 @@ class SessionHttpsTest extends WebTestBase { protected function setUp() { parent::setUp(); - $this->request = Request::createFromGlobals(); - $this->container->get('request_stack')->push($this->request); - } - protected function testHttpsSession() { - if ($this->request->isSecure()) { - $secure_session_name = $this->getSessionName(); - $insecure_session_name = substr($this->getSessionName(), 1); + $request = Request::createFromGlobals(); + if ($request->isSecure()) { + $this->secureSessionName = $this->getSessionName(); + $this->insecureSessionName = substr($this->getSessionName(), 1); } else { - $secure_session_name = 'S' . $this->getSessionName(); - $insecure_session_name = $this->getSessionName(); + $this->secureSessionName = 'S' . $this->getSessionName(); + $this->insecureSessionName = $this->getSessionName(); } + } + protected function testHttpsSession() { $user = $this->drupalCreateUser(array('access administration pages')); // Test HTTPS session handling by altering the form action to submit the // login form through https.php, which creates a mock HTTPS request. - $this->drupalGet('user/login'); - $form = $this->xpath('//form[@id="user-login-form"]'); - $form[0]['action'] = $this->httpsUrl('user/login'); - $edit = array('name' => $user->getUsername(), 'pass' => $user->pass_raw); - $this->drupalPostForm(NULL, $edit, t('Log in')); + $this->loginHttps($user); // Test a second concurrent session. $this->curlClose(); - $this->drupalGet('user/login'); - $form = $this->xpath('//form[@id="user-login-form"]'); - $form[0]['action'] = $this->httpsUrl('user/login'); - $this->drupalPostForm(NULL, $edit, t('Log in')); + $this->curlCookies = array(); + $this->loginHttps($user); // Check secure cookie on secure page. - $this->assertTrue($this->cookies[$secure_session_name]['secure'], 'The secure cookie has the secure attribute'); + $this->assertTrue($this->cookies[$this->secureSessionName]['secure'], 'The secure cookie has the secure attribute'); // Check insecure cookie is not set. - $this->assertFalse(isset($this->cookies[$insecure_session_name])); - $ssid = $this->cookies[$secure_session_name]['value']; + $this->assertFalse(isset($this->cookies[$this->insecureSessionName])); + $ssid = $this->cookies[$this->secureSessionName]['value']; $this->assertSessionIds($ssid, 'Session has a non-empty SID and a correct secure SID.'); - $cookie = $secure_session_name . '=' . $ssid; // Verify that user is logged in on secure URL. - $this->curlClose(); - $this->drupalGet($this->httpsUrl('admin/config'), array(), array('Cookie: ' . $cookie)); + $this->drupalGet($this->httpsUrl('admin/config')); $this->assertText(t('Configuration')); $this->assertResponse(200); // Verify that user is not logged in on non-secure URL. - $this->curlClose(); - $this->drupalGet($this->httpUrl('admin/config'), array(), array('Cookie: ' . $cookie)); + $this->drupalGet($this->httpUrl('admin/config')); $this->assertNoText(t('Configuration')); $this->assertResponse(403); // Verify that empty SID cannot be used on the non-secure site. $this->curlClose(); - $cookie = $insecure_session_name . '='; - $this->drupalGet($this->httpUrl('admin/config'), array(), array('Cookie: ' . $cookie)); + $this->curlCookies = array($this->insecureSessionName . '='); + $this->drupalGet($this->httpUrl('admin/config')); $this->assertResponse(403); // Test HTTP session handling by altering the form action to submit the // login form through http.php, which creates a mock HTTP request on HTTPS // test environments. $this->curlClose(); - $this->drupalGet('user/login'); - $form = $this->xpath('//form[@id="user-login-form"]'); - $form[0]['action'] = $this->httpUrl('user/login'); - $edit = array('name' => $user->getUsername(), 'pass' => $user->pass_raw); - $this->drupalPostForm(NULL, $edit, t('Log in')); + $this->curlCookies = array(); + $this->loginHttp($user); $this->drupalGet($this->httpUrl('admin/config')); $this->assertResponse(200); - $sid = $this->cookies[$insecure_session_name]['value']; + $sid = $this->cookies[$this->insecureSessionName]['value']; $this->assertSessionIds($sid, '', 'Session has the correct SID and an empty secure SID.'); // Verify that empty secure SID cannot be used on the secure site. $this->curlClose(); - $cookie = $secure_session_name . '='; - $this->drupalGet($this->httpsUrl('admin/config'), array(), array('Cookie: ' . $cookie)); + $this->curlCookies = array($this->secureSessionName . '='); + $this->drupalGet($this->httpsUrl('admin/config')); $this->assertResponse(403); // Clear browser cookie jar. @@ -110,6 +112,103 @@ protected function testHttpsSession() { } /** + * Log in a user via HTTP. + * + * Note that the parents $session_id and $loggedInUser is not updated. + */ + protected function loginHttp(AccountInterface $account) { + $this->drupalGet('user/login'); + + // Alter the form action to submit the login form through http.php, which + // creates a mock HTTP request on HTTPS test environments. + $form = $this->xpath('//form[@id="user-login-form"]'); + $form[0]['action'] = $this->httpUrl('user/login'); + $edit = array('name' => $account->getUsername(), 'pass' => $account->pass_raw); + + // When posting directly to the HTTP or HTTPS mock front controller, the + // location header on the returned response is an absolute URL. That URL + // needs to be converted into a request to the respective mock front + // controller in order to retrieve the target page. Because the URL in the + // location header needs to be modified, it is necessary to disable the + // automatic redirects normally performed by parent::curlExec(). + $maximum_redirects = $this->maximumRedirects; + $this->maximumRedirects = 0; + $this->drupalPostForm(NULL, $edit, t('Log in')); + $this->maximumRedirects = $maximum_redirects; + + // Follow the location header. + $path = $this->getPathFromLocationHeader(FALSE); + $this->drupalGet($this->httpUrl($path)); + $this->assertResponse(200); + } + + /** + * Log in a user via HTTPS. + * + * Note that the parents $session_id and $loggedInUser is not updated. + */ + protected function loginHttps(AccountInterface $account) { + $this->drupalGet('user/login'); + + // Alter the form action to submit the login form through https.php, which + // creates a mock HTTPS request on HTTP test environments. + $form = $this->xpath('//form[@id="user-login-form"]'); + $form[0]['action'] = $this->httpsUrl('user/login'); + $edit = array('name' => $account->getUsername(), 'pass' => $account->pass_raw); + + // When posting directly to the HTTP or HTTPS mock front controller, the + // location header on the returned response is an absolute URL. That URL + // needs to be converted into a request to the respective mock front + // controller in order to retrieve the target page. Because the URL in the + // location header needs to be modified, it is necessary to disable the + // automatic redirects normally performed by parent::curlExec(). + $maximum_redirects = $this->maximumRedirects; + $this->maximumRedirects = 0; + $this->drupalPostForm(NULL, $edit, t('Log in')); + $this->maximumRedirects = $maximum_redirects; + + // When logging in via the HTTPS mock, the child site will issue a session + // cookie with the secure attribute set. While this cookie will be stored in + // the curl handle, it will not be used on subsequent requests via the HTTPS + // mock, unless when operating in a true HTTPS environment. Therefore it is + // necessary to manually collect the session cookie and add it to the + // curlCookies property such that it will be used on subsequent requests via + // the HTTPS mock. + $this->curlCookies = array($this->secureSessionName . '=' . $this->cookies[$this->secureSessionName]['value']); + + // Follow the location header. + $path = $this->getPathFromLocationHeader(TRUE); + $this->drupalGet($this->httpsUrl($path)); + $this->assertResponse(200); + } + + /** + * Extract internal path from the location header on the response. + */ + protected function getPathFromLocationHeader($https = FALSE, $response_code = 303) { + // Generate the base_url. + $base_url = $this->container->get('url_generator')->generateFromRoute('', [], ['absolute' => TRUE]); + if ($https) { + $base_url = str_replace('http://', 'https://', $base_url); + } + else { + $base_url = str_replace('https://', 'http://', $base_url); + } + + // The mock front controllers (http.php and https.php) add the script name + // to $_SERVER['REQEUST_URI'] and friends. Therefore it is necessary to + // strip that also. + $base_url .= 'index.php/'; + + // Extract relative path from location header. + $this->assertResponse($response_code); + $location = $this->drupalGetHeader('location'); + + $this->assertIdentical(strpos($location, $base_url), 0, 'Location header contains expected base URL'); + return substr($location, strlen($base_url)); + } + + /** * Test that there exists a session with two specific session IDs. * * @param $sid @@ -135,12 +234,10 @@ protected function assertSessionIds($sid, $assertion_text) { * A Drupal path such as 'user/login'. * * @return - * An absolute URL. + * URL prepared for the https.php mock front controller. */ protected function httpsUrl($url) { - global $base_url; - $this->request->server->set('HTTPS', 'on'); - return $base_url . '/core/modules/system/tests/https.php/' . $url; + return 'core/modules/system/tests/https.php/' . $url; } /** @@ -150,10 +247,10 @@ protected function httpsUrl($url) { * A Drupal path such as 'user/login'. * * @return - * An absolute URL. + * URL prepared for the http.php mock front controller. */ protected function httpUrl($url) { - global $base_url; - return $base_url . '/core/modules/system/tests/http.php/' . $url; + return 'core/modules/system/tests/http.php/' . $url; } + } diff --git a/core/modules/system/tests/http.php b/core/modules/system/tests/http.php index 3386979..4eac640 100644 --- a/core/modules/system/tests/http.php +++ b/core/modules/system/tests/http.php @@ -12,9 +12,6 @@ $autoloader = require_once './core/vendor/autoload.php'; -// Set a global variable to indicate a mock HTTP request. -$is_http_mock = !empty($_SERVER['HTTPS']); - // Change to HTTP. $_SERVER['HTTPS'] = NULL; ini_set('session.cookie_secure', FALSE); diff --git a/core/modules/system/tests/https.php b/core/modules/system/tests/https.php index 00a6b13..acb65ac 100644 --- a/core/modules/system/tests/https.php +++ b/core/modules/system/tests/https.php @@ -15,9 +15,6 @@ $autoloader = require_once './core/vendor/autoload.php'; -// Set a global variable to indicate a mock HTTPS request. -$is_https_mock = empty($_SERVER['HTTPS']); - // Change to HTTPS. $_SERVER['HTTPS'] = 'on'; foreach ($_SERVER as &$value) { diff --git a/core/modules/system/tests/modules/session_test/src/EventSubscriber/SessionTestSubscriber.php b/core/modules/system/tests/modules/session_test/src/EventSubscriber/SessionTestSubscriber.php index 9029093..ffd27a5 100644 --- a/core/modules/system/tests/modules/session_test/src/EventSubscriber/SessionTestSubscriber.php +++ b/core/modules/system/tests/modules/session_test/src/EventSubscriber/SessionTestSubscriber.php @@ -9,7 +9,6 @@ use Drupal\Core\Session\SessionManagerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -57,20 +56,8 @@ public function onKernelRequestSessionTest(GetResponseEvent $event) { * The Event to process. */ public function onKernelResponseSessionTest(FilterResponseEvent $event) { - $response = $event->getResponse(); - if ($response instanceOf RedirectResponse) { - // Force the redirection to go to a non-secure page after being on a - // secure page through https.php. - global $base_insecure_url, $is_https_mock; - // Alter the redirect to use HTTP when using a mock HTTPS request through - // https.php because form submissions would otherwise redirect to a - // non-existent HTTPS site. - if (!empty($is_https_mock)) { - $path = $base_insecure_url . '/' . $response->getTargetUrl(); - $response->setTargetUrl($path); - } - } // Set header for session testing. + $response = $event->getResponse(); $response->headers->set('X-Session-Empty', $this->emptySession); } @@ -80,9 +67,9 @@ public function onKernelResponseSessionTest(FilterResponseEvent $event) { * @return array * An array of event listener definitions. */ - static function getSubscribedEvents() { - $events[KernelEvents::RESPONSE][] = array('onKernelResponseSessionTest', 300); - $events[KernelEvents::REQUEST][] = array('onKernelRequestSessionTest', 100); + public static function getSubscribedEvents() { + $events[KernelEvents::RESPONSE][] = array('onKernelResponseSessionTest'); + $events[KernelEvents::REQUEST][] = array('onKernelRequestSessionTest'); return $events; }