class RequestSanitizerTest
Same name in other branches
- 7.x modules/simpletest/tests/request_sanitizer.test \RequestSanitizerTest
- 9 core/tests/Drupal/Tests/Core/Security/RequestSanitizerTest.php \Drupal\Tests\Core\Security\RequestSanitizerTest
- 8.9.x core/tests/Drupal/Tests/Core/Security/RequestSanitizerTest.php \Drupal\Tests\Core\Security\RequestSanitizerTest
- 10 core/tests/Drupal/Tests/Core/Security/RequestSanitizerTest.php \Drupal\Tests\Core\Security\RequestSanitizerTest
Tests RequestSanitizer class.
@coversDefaultClass \Drupal\Core\Security\RequestSanitizer @runTestsInSeparateProcesses @preserveGlobalState disabled @group Security
Hierarchy
- class \Drupal\Tests\Core\Security\RequestSanitizerTest extends \Drupal\Tests\UnitTestCase
Expanded class hierarchy of RequestSanitizerTest
File
-
core/
tests/ Drupal/ Tests/ Core/ Security/ RequestSanitizerTest.php, line 19
Namespace
Drupal\Tests\Core\SecurityView source
class RequestSanitizerTest extends UnitTestCase {
/**
* Log of errors triggered during sanitization.
*
* @var array
*/
protected $errors;
/**
* {@inheritdoc}
*/
protected function setUp() : void {
parent::setUp();
$this->errors = [];
set_error_handler([
$this,
"errorHandler",
]);
}
/**
* {@inheritdoc}
*/
protected function tearDown() : void {
restore_error_handler();
parent::tearDown();
}
/**
* Tests RequestSanitizer class.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request to sanitize.
* @param array $expected
* An array of expected request parameters after sanitization. The possible
* keys are 'cookies', 'query', 'request' which correspond to the parameter
* bags names on the request object. These values are also used to test the
* PHP globals post sanitization.
* @param array|null $expected_errors
* An array of expected errors. If set to NULL then error logging is
* disabled.
* @param array $allow_list
* An array of keys to allow and not sanitize.
*
* @dataProvider providerTestRequestSanitization
*/
public function testRequestSanitization(Request $request, array $expected = [], ?array $expected_errors = NULL, array $allow_list = []) : void {
// Set up globals.
$_GET = $request->query
->all();
$_POST = $request->request
->all();
$_COOKIE = $request->cookies
->all();
$_REQUEST = array_merge($request->query
->all(), $request->request
->all());
$request->server
->set('QUERY_STRING', http_build_query($request->query
->all()));
$_SERVER['QUERY_STRING'] = $request->server
->get('QUERY_STRING');
$request = RequestSanitizer::sanitize($request, $allow_list, is_null($expected_errors) ? FALSE : TRUE);
// Normalize the expected data.
$expected += [
'cookies' => [],
'query' => [],
'request' => [],
];
$expected_query_string = http_build_query($expected['query']);
// Test the request.
$this->assertEquals($expected['cookies'], $request->cookies
->all());
$this->assertEquals($expected['query'], $request->query
->all());
$this->assertEquals($expected['request'], $request->request
->all());
$this->assertTrue($request->attributes
->get(RequestSanitizer::SANITIZED));
// The request object normalizes the request query string.
$this->assertEquals(Request::normalizeQueryString($expected_query_string), $request->getQueryString());
// Test PHP globals.
$this->assertEquals($expected['cookies'], $_COOKIE);
$this->assertEquals($expected['query'], $_GET);
$this->assertEquals($expected['request'], $_POST);
$expected_request = array_merge($expected['query'], $expected['request']);
$this->assertEquals($expected_request, $_REQUEST);
$this->assertEquals($expected_query_string, $_SERVER['QUERY_STRING']);
// Ensure any expected errors have been triggered.
if (!empty($expected_errors)) {
foreach ($expected_errors as $expected_error) {
$this->assertError($expected_error, E_USER_NOTICE);
}
}
else {
$this->assertEquals([], $this->errors);
}
}
/**
* Data provider for testRequestSanitization.
*
* @return array
*/
public static function providerTestRequestSanitization() {
$tests = [];
$request = new Request([
'q' => 'index.php',
]);
$tests['no sanitization GET'] = [
$request,
[
'query' => [
'q' => 'index.php',
],
],
];
$request = new Request([], [
'field' => 'value',
]);
$tests['no sanitization POST'] = [
$request,
[
'request' => [
'field' => 'value',
],
],
];
$request = new Request([], [], [], [
'key' => 'value',
]);
$tests['no sanitization COOKIE'] = [
$request,
[
'cookies' => [
'key' => 'value',
],
],
];
$request = new Request([
'q' => 'index.php',
], [
'field' => 'value',
], [], [
'key' => 'value',
]);
$tests['no sanitization GET, POST, COOKIE'] = [
$request,
[
'query' => [
'q' => 'index.php',
],
'request' => [
'field' => 'value',
],
'cookies' => [
'key' => 'value',
],
],
];
$request = new Request([
'q' => 'index.php',
]);
$tests['no sanitization GET log'] = [
$request,
[
'query' => [
'q' => 'index.php',
],
],
[],
];
$request = new Request([], [
'field' => 'value',
]);
$tests['no sanitization POST log'] = [
$request,
[
'request' => [
'field' => 'value',
],
],
[],
];
$request = new Request([], [], [], [
'key' => 'value',
]);
$tests['no sanitization COOKIE log'] = [
$request,
[
'cookies' => [
'key' => 'value',
],
],
[],
];
$request = new Request([
'#q' => 'index.php',
]);
$tests['sanitization GET'] = [
$request,
];
$request = new Request([], [
'#field' => 'value',
]);
$tests['sanitization POST'] = [
$request,
];
$request = new Request([], [], [], [
'#key' => 'value',
]);
$tests['sanitization COOKIE'] = [
$request,
];
$request = new Request([
'#q' => 'index.php',
], [
'#field' => 'value',
], [], [
'#key' => 'value',
]);
$tests['sanitization GET, POST, COOKIE'] = [
$request,
];
$request = new Request([
'#q' => 'index.php',
]);
$tests['sanitization GET log'] = [
$request,
[],
[
'Potentially unsafe keys removed from query string parameters (GET): #q',
],
];
$request = new Request([], [
'#field' => 'value',
]);
$tests['sanitization POST log'] = [
$request,
[],
[
'Potentially unsafe keys removed from request body parameters (POST): #field',
],
];
$request = new Request([], [], [], [
'#key' => 'value',
]);
$tests['sanitization COOKIE log'] = [
$request,
[],
[
'Potentially unsafe keys removed from cookie parameters: #key',
],
];
$request = new Request([
'#q' => 'index.php',
], [
'#field' => 'value',
], [], [
'#key' => 'value',
]);
$tests['sanitization GET, POST, COOKIE log'] = [
$request,
[],
[
'Potentially unsafe keys removed from query string parameters (GET): #q',
'Potentially unsafe keys removed from request body parameters (POST): #field',
'Potentially unsafe keys removed from cookie parameters: #key',
],
];
$request = new Request([
'q' => 'index.php',
'foo' => [
'#bar' => 'foo',
],
]);
$tests['recursive sanitization log'] = [
$request,
[
'query' => [
'q' => 'index.php',
'foo' => [],
],
],
[
'Potentially unsafe keys removed from query string parameters (GET): #bar',
],
];
$request = new Request([
'q' => 'index.php',
'foo' => [
'#bar' => 'foo',
],
]);
$tests['recursive no sanitization allowed list'] = [
$request,
[
'query' => [
'q' => 'index.php',
'foo' => [
'#bar' => 'foo',
],
],
],
[],
[
'#bar',
],
];
$request = new Request([], [
'#field' => 'value',
]);
$tests['no sanitization POST allowed list'] = [
$request,
[
'request' => [
'#field' => 'value',
],
],
[],
[
'#field',
],
];
$request = new Request([
'q' => 'index.php',
'foo' => [
'#bar' => 'foo',
'#foo' => 'bar',
],
]);
$tests['recursive multiple sanitization log'] = [
$request,
[
'query' => [
'q' => 'index.php',
'foo' => [],
],
],
[
'Potentially unsafe keys removed from query string parameters (GET): #bar, #foo',
],
];
$request = new Request([
'#q' => 'index.php',
]);
$request->attributes
->set(RequestSanitizer::SANITIZED, TRUE);
$tests['already sanitized request'] = [
$request,
[
'query' => [
'#q' => 'index.php',
],
],
];
$request = new Request([
'destination' => 'whatever?%23test=value',
]);
$tests['destination removal GET'] = [
$request,
];
$request = new Request([], [
'destination' => 'whatever?%23test=value',
]);
$tests['destination removal POST'] = [
$request,
];
$request = new Request([], [], [], [
'destination' => 'whatever?%23test=value',
]);
$tests['destination removal COOKIE'] = [
$request,
];
$request = new Request([
'destination' => 'whatever?%23test=value',
]);
$tests['destination removal GET log'] = [
$request,
[],
[
'Potentially unsafe destination removed from query parameter bag because it contained the following keys: #test',
],
];
$request = new Request([], [
'destination' => 'whatever?%23test=value',
]);
$tests['destination removal POST log'] = [
$request,
[],
[
'Potentially unsafe destination removed from request parameter bag because it contained the following keys: #test',
],
];
$request = new Request([], [], [], [
'destination' => 'whatever?%23test=value',
]);
$tests['destination removal COOKIE log'] = [
$request,
[],
[
'Potentially unsafe destination removed from cookies parameter bag because it contained the following keys: #test',
],
];
$request = new Request([
'destination' => 'whatever?q[%23test]=value',
]);
$tests['destination removal subkey'] = [
$request,
];
$request = new Request([
'destination' => 'whatever?q[%23test]=value',
]);
$tests['destination allowed list'] = [
$request,
[
'query' => [
'destination' => 'whatever?q[%23test]=value',
],
],
[],
[
'#test',
],
];
$request = new Request([
'destination' => "whatever?\x00bar=base&%23test=value",
]);
$tests['destination removal zero byte'] = [
$request,
];
$request = new Request([
'destination' => 'whatever?q=value',
]);
$tests['destination kept'] = [
$request,
[
'query' => [
'destination' => 'whatever?q=value',
],
],
];
$request = new Request([
'destination' => 'whatever',
]);
$tests['destination no query'] = [
$request,
[
'query' => [
'destination' => 'whatever',
],
],
];
return $tests;
}
/**
* Tests acceptable destinations are not removed from GET requests.
*
* @param string $destination
* The destination string to test.
*
* @dataProvider providerTestAcceptableDestinations
*/
public function testAcceptableDestinationGet($destination) : void {
// Set up a GET request.
$request = $this->createRequestForTesting([
'destination' => $destination,
]);
$request = RequestSanitizer::sanitize($request, [], TRUE);
$this->assertSame($destination, $request->query
->get('destination', NULL));
$this->assertNull($request->request
->get('destination', NULL));
$this->assertSame($destination, $_GET['destination']);
$this->assertSame($destination, $_REQUEST['destination']);
$this->assertArrayNotHasKey('destination', $_POST);
$this->assertEquals([], $this->errors);
}
/**
* Tests unacceptable destinations are removed from GET requests.
*
* @param string $destination
* The destination string to test.
*
* @dataProvider providerTestSanitizedDestinations
*/
public function testSanitizedDestinationGet($destination) : void {
// Set up a GET request.
$request = $this->createRequestForTesting([
'destination' => $destination,
]);
$request = RequestSanitizer::sanitize($request, [], TRUE);
$this->assertNull($request->request
->get('destination', NULL));
$this->assertNull($request->query
->get('destination', NULL));
$this->assertArrayNotHasKey('destination', $_POST);
$this->assertArrayNotHasKey('destination', $_REQUEST);
$this->assertArrayNotHasKey('destination', $_GET);
$this->assertError('Potentially unsafe destination removed from query parameter bag because it points to an external URL.', E_USER_NOTICE);
}
/**
* Tests acceptable destinations are not removed from POST requests.
*
* @param string $destination
* The destination string to test.
*
* @dataProvider providerTestAcceptableDestinations
*/
public function testAcceptableDestinationPost($destination) : void {
// Set up a POST request.
$request = $this->createRequestForTesting([], [
'destination' => $destination,
]);
$request = RequestSanitizer::sanitize($request, [], TRUE);
$this->assertSame($destination, $request->request
->get('destination', NULL));
$this->assertNull($request->query
->get('destination', NULL));
$this->assertSame($destination, $_POST['destination']);
$this->assertSame($destination, $_REQUEST['destination']);
$this->assertArrayNotHasKey('destination', $_GET);
$this->assertEquals([], $this->errors);
}
/**
* Tests unacceptable destinations are removed from GET requests.
*
* @param string $destination
* The destination string to test.
*
* @dataProvider providerTestSanitizedDestinations
*/
public function testSanitizedDestinationPost($destination) : void {
// Set up a POST request.
$request = $this->createRequestForTesting([], [
'destination' => $destination,
]);
$request = RequestSanitizer::sanitize($request, [], TRUE);
$this->assertNull($request->request
->get('destination', NULL));
$this->assertNull($request->query
->get('destination', NULL));
$this->assertArrayNotHasKey('destination', $_POST);
$this->assertArrayNotHasKey('destination', $_REQUEST);
$this->assertArrayNotHasKey('destination', $_GET);
$this->assertError('Potentially unsafe destination removed from request parameter bag because it points to an external URL.', E_USER_NOTICE);
}
/**
* Creates a request and sets PHP globals for testing.
*
* @param array $query
* (optional) The GET parameters.
* @param array $request
* (optional) The POST parameters.
*
* @return \Symfony\Component\HttpFoundation\Request
* The request object.
*/
protected function createRequestForTesting(array $query = [], array $request = []) {
$request = new Request($query, $request);
// Set up globals.
$_GET = $request->query
->all();
$_POST = $request->request
->all();
$_COOKIE = $request->cookies
->all();
$_REQUEST = array_merge($request->query
->all(), $request->request
->all());
$request->server
->set('QUERY_STRING', http_build_query($request->query
->all()));
$_SERVER['QUERY_STRING'] = $request->server
->get('QUERY_STRING');
return $request;
}
/**
* Data provider for testing acceptable destinations.
*/
public static function providerTestAcceptableDestinations() {
$data = [];
// Standard internal example node path is present in the 'destination'
// parameter.
$data[] = [
'node',
];
// Internal path with one leading slash is allowed.
$data[] = [
'/example.com',
];
// Internal URL using a colon is allowed.
$data[] = [
'example:test',
];
// JavaScript URL is allowed because it is treated as an internal URL.
$data[] = [
'javascript:alert(0)',
];
return $data;
}
/**
* Data provider for testing sanitized destinations.
*/
public static function providerTestSanitizedDestinations() {
$data = [];
// External URL without scheme is not allowed.
$data[] = [
'//example.com/test',
];
// External URL is not allowed.
$data[] = [
'http://example.com',
];
return $data;
}
/**
* Catches and logs errors to $this->errors.
*
* @param int $errno
* The severity level of the error.
* @param string $errstr
* The error message.
*/
public function errorHandler($errno, $errstr) : void {
$this->errors[] = compact('errno', 'errstr');
}
/**
* Asserts that the expected error has been logged.
*
* @param string $errstr
* The error message.
* @param int $errno
* The severity level of the error.
*
* @internal
*/
protected function assertError(string $errstr, int $errno) : void {
foreach ($this->errors as $error) {
if ($error['errstr'] === $errstr && $error['errno'] === $errno) {
return;
}
}
$this->fail("Error with level {$errno} and message '{$errstr}' not found in " . var_export($this->errors, TRUE));
}
}
Members
Title Sort descending | Modifiers | Object type | Summary | Overriden Title |
---|---|---|---|---|
ExpectDeprecationTrait::expectDeprecation | public | function | Adds an expected deprecation. | |
ExpectDeprecationTrait::getCallableName | private static | function | Returns a callable as a string suitable for inclusion in a message. | |
ExpectDeprecationTrait::setUpErrorHandler | public | function | Sets up the test error handler. | |
ExpectDeprecationTrait::tearDownErrorHandler | public | function | Tears down the test error handler. | |
RandomGeneratorTrait::getRandomGenerator | protected | function | Gets the random generator for the utility methods. | |
RandomGeneratorTrait::randomMachineName | protected | function | Generates a unique random string containing letters and numbers. | |
RandomGeneratorTrait::randomObject | public | function | Generates a random PHP object. | |
RandomGeneratorTrait::randomString | public | function | Generates a pseudo-random string of ASCII characters of codes 32 to 126. | |
RequestSanitizerTest::$errors | protected | property | Log of errors triggered during sanitization. | |
RequestSanitizerTest::assertError | protected | function | Asserts that the expected error has been logged. | |
RequestSanitizerTest::createRequestForTesting | protected | function | Creates a request and sets PHP globals for testing. | |
RequestSanitizerTest::errorHandler | public | function | Catches and logs errors to $this->errors. | |
RequestSanitizerTest::providerTestAcceptableDestinations | public static | function | Data provider for testing acceptable destinations. | |
RequestSanitizerTest::providerTestRequestSanitization | public static | function | Data provider for testRequestSanitization. | |
RequestSanitizerTest::providerTestSanitizedDestinations | public static | function | Data provider for testing sanitized destinations. | |
RequestSanitizerTest::setUp | protected | function | Overrides UnitTestCase::setUp | |
RequestSanitizerTest::tearDown | protected | function | ||
RequestSanitizerTest::testAcceptableDestinationGet | public | function | Tests acceptable destinations are not removed from GET requests. | |
RequestSanitizerTest::testAcceptableDestinationPost | public | function | Tests acceptable destinations are not removed from POST requests. | |
RequestSanitizerTest::testRequestSanitization | public | function | Tests RequestSanitizer class. | |
RequestSanitizerTest::testSanitizedDestinationGet | public | function | Tests unacceptable destinations are removed from GET requests. | |
RequestSanitizerTest::testSanitizedDestinationPost | public | function | Tests unacceptable destinations are removed from GET requests. | |
UnitTestCase::$root | protected | property | The app root. | |
UnitTestCase::getClassResolverStub | protected | function | Returns a stub class resolver. | |
UnitTestCase::getConfigFactoryStub | public | function | Returns a stub config factory that behaves according to the passed array. | |
UnitTestCase::getConfigStorageStub | public | function | Returns a stub config storage that returns the supplied configuration. | |
UnitTestCase::getContainerWithCacheTagsInvalidator | protected | function | Sets up a container with a cache tags invalidator. | |
UnitTestCase::getStringTranslationStub | public | function | Returns a stub translation manager that just returns the passed string. | |
UnitTestCase::setUpBeforeClass | public static | function |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.