Issue #3011079 by scott_euser, alexpott, Lendude: Convert \Drupal\system\Tests\Session\SessionHttpsTest to PHPUnit

8.7.x
Nathaniel Catchpole 2019-02-14 09:46:07 +00:00
parent 2aa7c3a6fa
commit a51a8f59b1
2 changed files with 310 additions and 250 deletions

View File

@ -1,250 +0,0 @@
<?php
namespace Drupal\system\Tests\Session;
use Drupal\simpletest\WebTestBase;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Session\AccountInterface;
/**
* Ensure that when running under HTTPS two session cookies are generated.
*
* @group Session
*/
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
*/
public static $modules = ['session_test'];
protected function setUp() {
parent::setUp();
$request = Request::createFromGlobals();
if ($request->isSecure()) {
$this->secureSessionName = $this->getSessionName();
$this->insecureSessionName = substr($this->getSessionName(), 1);
}
else {
$this->secureSessionName = 'S' . $this->getSessionName();
$this->insecureSessionName = $this->getSessionName();
}
}
public function testHttpsSession() {
$user = $this->drupalCreateUser(['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->loginHttps($user);
// Test a second concurrent session.
$this->curlClose();
$this->curlCookies = [];
$this->loginHttps($user);
// Check secure cookie on secure page.
$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[$this->insecureSessionName]));
$ssid = $this->cookies[$this->secureSessionName]['value'];
$this->assertSessionIds($ssid, 'Session has a non-empty SID and a correct secure SID.');
// Verify that user is logged in on secure URL.
$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->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();
$this->curlCookies = [$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->curlCookies = [];
$this->loginHttp($user);
$this->drupalGet($this->httpUrl('admin/config'));
$this->assertResponse(200);
$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();
$this->curlCookies = [$this->secureSessionName . '='];
$this->drupalGet($this->httpsUrl('admin/config'));
$this->assertResponse(403);
// Clear browser cookie jar.
$this->cookies = [];
}
/**
* 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 = ['name' => $account->getAccountName(), '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 = ['name' => $account->getAccountName(), '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 = [$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('<front>', [], ['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['REQUEST_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
* The insecure session ID to search for.
* @param $assertion_text
* The text to display when we perform the assertion.
*
* @return
* The result of assertTrue() that there's a session in the system that
* has the given insecure and secure session IDs.
*/
protected function assertSessionIds($sid, $assertion_text) {
$args = [
':sid' => Crypt::hashBase64($sid),
];
return $this->assertTrue(db_query('SELECT timestamp FROM {sessions} WHERE sid = :sid', $args)->fetchField(), $assertion_text);
}
/**
* Builds a URL for submitting a mock HTTPS request to HTTP test environments.
*
* @param $url
* A Drupal path such as 'user/login'.
*
* @return
* URL prepared for the https.php mock front controller.
*/
protected function httpsUrl($url) {
return 'core/modules/system/tests/https.php/' . $url;
}
/**
* Builds a URL for submitting a mock HTTP request to HTTPS test environments.
*
* @param $url
* A Drupal path such as 'user/login'.
*
* @return
* URL prepared for the http.php mock front controller.
*/
protected function httpUrl($url) {
return 'core/modules/system/tests/http.php/' . $url;
}
}

View File

@ -0,0 +1,310 @@
<?php
namespace Drupal\Tests\system\Functional\Session;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Session\AccountInterface;
use Drupal\Tests\BrowserTestBase;
use GuzzleHttp\Cookie\CookieJar;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\HttpFoundation\Request;
/**
* Ensure that when running under HTTPS two session cookies are generated.
*
* @group Session
*/
class SessionHttpsTest extends BrowserTestBase {
/**
* 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
*/
public static $modules = ['session_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$request = Request::createFromGlobals();
if ($request->isSecure()) {
$this->secureSessionName = $this->getSessionName();
$this->insecureSessionName = substr($this->getSessionName(), 1);
}
else {
$this->secureSessionName = 'S' . $this->getSessionName();
$this->insecureSessionName = $this->getSessionName();
}
}
/**
* Tests HTTPS sessions.
*/
public function testHttpsSession() {
$user = $this->drupalCreateUser(['access administration pages']);
/** @var \Symfony\Component\BrowserKit\CookieJar $browser_kit_cookie_jar */
$browser_kit_cookie_jar = $this->getSession()->getDriver()->getClient()->getCookieJar();
// Test HTTPS session handling by submitting the login form through
// https.php, which creates a mock HTTPS request.
$this->loginHttps($user);
$first_secure_session = $this->getSession()->getCookie($this->secureSessionName);
// Test a second concurrent session.
$this->loginHttps($user);
$this->assertNotSame($first_secure_session, $this->getSession()->getCookie($this->secureSessionName));
// Check secure cookie is set.
$this->assertTrue((bool) $this->getSession()->getCookie($this->secureSessionName));
// Check insecure cookie is not set.
$this->assertFalse((bool) $this->getSession()->getCookie($this->insecureSessionName));
$this->assertSessionIds($this->getSession()->getCookie($this->secureSessionName), 'Session has a non-empty SID and a correct secure SID.');
$this->assertSessionIds($first_secure_session, 'The first secure session still exists.');
// Verify that user is logged in on secure URL.
$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->drupalGet($this->httpUrl('admin/config'));
$this->assertNoText(t('Configuration'));
$this->assertResponse(403);
// Verify that empty SID cannot be used on the non-secure site.
$browser_kit_cookie_jar->set(Cookie::fromString($this->insecureSessionName . '=', $this->baseUrl));
$this->drupalGet($this->httpUrl('admin/config'));
$this->assertResponse(403);
// Remove the secure session name from the cookie jar before logging in via
// HTTP on HTTPS environments.
$browser_kit_cookie_jar->expire($this->secureSessionName);
// Test HTTP session handling by submitting the login form through http.php,
// which creates a mock HTTP request on HTTPS test environments.
$this->loginHttp($user);
$this->drupalGet($this->httpUrl('admin/config'));
$this->assertResponse(200);
$this->assertSessionIds($this->getSession()->getCookie($this->insecureSessionName), 'Session has the correct SID and an empty secure SID.');
// Verify that empty secure SID cannot be used on the secure site.
$browser_kit_cookie_jar->set(Cookie::fromString($this->secureSessionName . '=', $this->baseUrl));
$this->drupalGet($this->httpsUrl('admin/config'));
$this->assertResponse(403);
}
/**
* Log in a user via HTTP.
*
* Note that the parents $session_id and $loggedInUser is not updated.
*/
protected function loginHttp(AccountInterface $account) {
$guzzle_cookie_jar = $this->getGuzzleCookieJar();
$post = [
'form_id' => 'user_login_form',
'form_build_id' => $this->getUserLoginFormBuildId(),
'name' => $account->getAccountName(),
'pass' => $account->passRaw,
'op' => 'Log in',
];
$url = $this->buildUrl($this->httpUrl('user/login'));
// 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 the Guzzle CurlHandler.
/** @var \Psr\Http\Message\ResponseInterface $response */
$response = $this->getHttpClient()->post($url, [
'form_params' => $post,
'http_errors' => FALSE,
'cookies' => $guzzle_cookie_jar,
'allow_redirects' => FALSE,
]);
// When logging in via the HTTP mock, the child site will issue a session
// cookie without the secure attribute set. While this cookie will be stored
// in the Guzzle CookieJar, it will not be used on subsequent requests.
// Update the BrowserKit CookieJar so that subsequent requests have the
// correct cookie.
$cookie = $guzzle_cookie_jar->getCookieByName($this->insecureSessionName);
$this->assertFalse($cookie->getSecure(), 'The insecure cookie does not have the secure attribute');
/** @var \Symfony\Component\BrowserKit\CookieJar $browser_kit_cookie_jar */
$browser_kit_cookie_jar = $this->getSession()->getDriver()->getClient()->getCookieJar();
$browser_kit_cookie_jar->updateFromSetCookie($response->getHeader('Set-Cookie'), $this->baseUrl);
// Follow the location header.
$path = $this->getPathFromLocationHeader($response, 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) {
$guzzle_cookie_jar = $this->getGuzzleCookieJar();
$post = [
'form_id' => 'user_login_form',
'form_build_id' => $this->getUserLoginFormBuildId(),
'name' => $account->getAccountName(),
'pass' => $account->passRaw,
'op' => 'Log in',
];
$url = $this->buildUrl($this->httpsUrl('user/login'));
// 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 the Guzzle CurlHandler.
/** @var \Psr\Http\Message\ResponseInterface $response */
$response = $this->getHttpClient()->post($url, [
'form_params' => $post,
'http_errors' => FALSE,
'cookies' => $guzzle_cookie_jar,
'allow_redirects' => FALSE,
]);
// 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 Guzzle CookieJar, it will not be used on subsequent requests.
// Update the BrowserKit CookieJar so that subsequent requests have the
// correct cookie.
$cookie = $guzzle_cookie_jar->getCookieByName($this->secureSessionName);
$this->assertTrue($cookie->getSecure(), 'The secure cookie has the secure attribute');
/** @var \Symfony\Component\BrowserKit\CookieJar $browser_kit_cookie_jar */
$browser_kit_cookie_jar = $this->getSession()->getDriver()->getClient()->getCookieJar();
$browser_kit_cookie_jar->updateFromSetCookie($response->getHeader('Set-Cookie'), $this->baseUrl);
// Follow the location header.
$path = $this->getPathFromLocationHeader($response, TRUE);
$this->drupalGet($this->httpsUrl($path));
$this->assertResponse(200);
}
/**
* Extracts internal path from the location header on the response.
*
* @param \Psr\Http\Message\ResponseInterface $response
* The response from logging in.
* @param bool $https
* Whether the log in was via HTTPS. Defaults to FALSE.
*
* @return string
* The internal path from the location header on the response.
*/
protected function getPathFromLocationHeader(ResponseInterface $response, $https = FALSE) {
if ($https) {
$base_url = str_replace('http://', 'https://', $this->baseUrl);
}
else {
$base_url = str_replace('https://', 'http://', $this->baseUrl);
}
// The mock front controllers (http.php and https.php) add the script name
// to $_SERVER['REQUEST_URI'] and friends. Therefore it is necessary to
// strip that also.
$base_url .= '/index.php/';
// Extract relative path from location header.
$this->assertSame(303, $response->getStatusCode());
$location = $response->getHeader('location')[0];
$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
* The insecure session ID to search for.
* @param $assertion_text
* The text to display when we perform the assertion.
*
* @return
* The result of assertTrue() that there's a session in the system that
* has the given insecure and secure session IDs.
*/
protected function assertSessionIds($sid, $assertion_text) {
$args = [
':sid' => Crypt::hashBase64($sid),
];
return $this->assertTrue(db_query('SELECT timestamp FROM {sessions} WHERE sid = :sid', $args)->fetchField(), $assertion_text);
}
/**
* Builds a URL for submitting a mock HTTPS request to HTTP test environments.
*
* @param $url
* A Drupal path such as 'user/login'.
*
* @return
* URL prepared for the https.php mock front controller.
*/
protected function httpsUrl($url) {
return 'core/modules/system/tests/https.php/' . $url;
}
/**
* Builds a URL for submitting a mock HTTP request to HTTPS test environments.
*
* @param $url
* A Drupal path such as 'user/login'.
*
* @return
* URL prepared for the http.php mock front controller.
*/
protected function httpUrl($url) {
return 'core/modules/system/tests/http.php/' . $url;
}
/**
* Creates a new Guzzle CookieJar with a Xdebug cookie if necessary.
*
* @return \GuzzleHttp\Cookie\CookieJar
* The Guzzle CookieJar.
*/
protected function getGuzzleCookieJar() {
// @todo Add xdebug cookie.
$cookies = $this->extractCookiesFromRequest(\Drupal::request());
foreach ($cookies as $cookie_name => $values) {
$cookies[$cookie_name] = $values[0];
}
return CookieJar::fromArray($cookies, $this->baseUrl);
}
/**
* Gets the form build ID for the user login form.
*
* @return string
* The form build ID for the user login form.
*/
protected function getUserLoginFormBuildId() {
$this->drupalGet('user/login');
return (string) $this->getSession()->getPage()->findField('form_build_id');
}
}