View source
<?php
namespace Drupal\Tests\Core\Security;
use Drupal\Core\Security\RequestSanitizer;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\Request;
class RequestSanitizerTest extends UnitTestCase {
protected $errors;
protected function setUp() {
parent::setUp();
$this->errors = [];
set_error_handler([
$this,
"errorHandler",
]);
}
public function testRequestSanitization(Request $request, array $expected = [], array $expected_errors = NULL, array $whitelist = []) {
$_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, $whitelist, is_null($expected_errors) ? FALSE : TRUE);
$expected += [
'cookies' => [],
'query' => [],
'request' => [],
];
$expected_query_string = http_build_query($expected['query']);
$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));
$this
->assertEquals(Request::normalizeQueryString($expected_query_string), $request
->getQueryString());
$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']);
if (!empty($expected_errors)) {
foreach ($expected_errors as $expected_error) {
$this
->assertError($expected_error, E_USER_NOTICE);
}
}
else {
$this
->assertEquals([], $this->errors);
}
}
public 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 whitelist'] = [
$request,
[
'query' => [
'q' => 'index.php',
'foo' => [
'#bar' => 'foo',
],
],
],
[],
[
'#bar',
],
];
$request = new Request([], [
'#field' => 'value',
]);
$tests['no sanitization POST whitelist'] = [
$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 whitelist'] = [
$request,
[
'query' => [
'destination' => 'whatever?q[%23test]=value',
],
],
[],
[
'#test',
],
];
$request = new Request([
'destination' => "whatever?\0bar=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;
}
public function testAcceptableDestinationGet($destination) {
$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);
}
public function testSanitizedDestinationGet($destination) {
$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);
}
public function testAcceptableDestinationPost($destination) {
$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);
}
public function testSanitizedDestinationPost($destination) {
$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);
}
protected function createRequestForTesting(array $query = [], array $request = []) {
$request = new Request($query, $request);
$_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;
}
public function providerTestAcceptableDestinations() {
$data = [];
$data[] = [
'node',
];
$data[] = [
'/example.com',
];
$data[] = [
'example:test',
];
$data[] = [
'javascript:alert(0)',
];
return $data;
}
public function providerTestSanitizedDestinations() {
$data = [];
$data[] = [
'//example.com/test',
];
$data[] = [
'http://example.com',
];
return $data;
}
public function errorHandler($errno, $errstr) {
$this->errors[] = compact('errno', 'errstr');
}
protected function assertError($errstr, $errno) {
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));
}
}