FIX Don't redirect admin URLs regardless of trailing slash (#10781)

This commit is contained in:
Guy Sartorelli 2023-05-17 10:49:34 +12:00 committed by GitHub
parent 4a2dc2dd97
commit 2afb01463b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 104 additions and 64 deletions

View File

@ -412,7 +412,10 @@ class CanonicalURLMiddleware implements HTTPMiddleware
$paths = (array) $this->getEnforceTrailingSlashConfigIgnorePaths(); $paths = (array) $this->getEnforceTrailingSlashConfigIgnorePaths();
if (!empty($paths)) { if (!empty($paths)) {
foreach ($paths as $path) { foreach ($paths as $path) {
if (str_starts_with(trim($path, '/'), trim($requestPath, '/'))) { if (str_starts_with(
$this->trailingSlashForComparison($requestPath),
$this->trailingSlashForComparison($path)
)) {
return false; return false;
} }
} }
@ -439,6 +442,15 @@ class CanonicalURLMiddleware implements HTTPMiddleware
return true; return true;
} }
/**
* Ensure a string has a trailing slash to that we can use str_starts_with and compare
* paths like admin/ with administration/ and get a correct result.
*/
private function trailingSlashForComparison(string $path): string
{
return trim($path, '/') . '/';
}
/** /**
* @return int * @return int
*/ */

View File

@ -85,63 +85,42 @@ class CanonicalURLMiddlewareTest extends SapphireTest
$this->assertFalse($middleware->getForceBasicAuthToSSL(), 'Explicitly set is returned'); $this->assertFalse($middleware->getForceBasicAuthToSSL(), 'Explicitly set is returned');
} }
public function testRedirectTrailingSlash() public function provideRedirectTrailingSlash()
{ {
$testScenarios = [ $testScenarios = [];
[ foreach ([true, false] as $forceRedirect) {
'forceRedirect' => true, foreach ([true, false] as $addTrailingSlash) {
'addTrailingSlash' => true, foreach ([true, false] as $requestHasSlash) {
'requestHasSlash' => true, $testScenarios[] = [
], $forceRedirect,
[ $addTrailingSlash,
'forceRedirect' => true, $requestHasSlash,
'addTrailingSlash' => true,
'requestHasSlash' => false,
],
[
'forceRedirect' => true,
'addTrailingSlash' => false,
'requestHasSlash' => true,
],
[
'forceRedirect' => true,
'addTrailingSlash' => false,
'requestHasSlash' => false,
],
[
'forceRedirect' => false,
'addTrailingSlash' => true,
'requestHasSlash' => true,
],
[
'forceRedirect' => false,
'addTrailingSlash' => true,
'requestHasSlash' => false,
],
[
'forceRedirect' => false,
'addTrailingSlash' => false,
'requestHasSlash' => true,
],
[
'forceRedirect' => false,
'addTrailingSlash' => false,
'requestHasSlash' => false,
],
]; ];
foreach ($testScenarios as $scenario) { }
$forceRedirect = $scenario['forceRedirect']; }
$addTrailingSlash = $scenario['addTrailingSlash']; }
$requestHasSlash = $scenario['requestHasSlash']; return $testScenarios;
}
$middleware = $this->getMockedMiddleware(false); /**
* @dataProvider provideRedirectTrailingSlash
$middleware->setEnforceTrailingSlashConfig($forceRedirect); */
public function testRedirectTrailingSlash(bool $forceRedirect, bool $addTrailingSlash, bool $requestHasSlash)
{
Controller::config()->set('add_trailing_slash', $addTrailingSlash); Controller::config()->set('add_trailing_slash', $addTrailingSlash);
$noRedirect = !$forceRedirect || ($addTrailingSlash && $requestHasSlash) || (!$addTrailingSlash && !$requestHasSlash);
$middleware = $this->getMockedMiddleware(false);
$middleware->setEnforceTrailingSlashConfig($forceRedirect);
$requestSlash = $requestHasSlash ? '/' : ''; $requestSlash = $requestHasSlash ? '/' : '';
$requestURL = "/about-us{$requestSlash}"; $requestURL = "/about-us{$requestSlash}";
$this->performRedirectTest($requestURL, $middleware, !$noRedirect, $addTrailingSlash);
}
private function performRedirectTest(string $requestURL, CanonicalURLMiddleware $middleware, bool $shouldRedirect, bool $addTrailingSlash)
{
Environment::setEnv('REQUEST_URI', $requestURL); Environment::setEnv('REQUEST_URI', $requestURL);
$request = new HTTPRequest('GET', $requestURL); $request = new HTTPRequest('GET', $requestURL);
$request->setScheme('https'); $request->setScheme('https');
@ -153,8 +132,7 @@ class CanonicalURLMiddlewareTest extends SapphireTest
return $mockResponse; return $mockResponse;
}); });
$noRedirect = !$forceRedirect || ($addTrailingSlash && $requestHasSlash) || (!$addTrailingSlash && !$requestHasSlash); if (!$shouldRedirect) {
if ($noRedirect) {
$this->assertNull($result->getHeader('Location'), 'No location header should be added'); $this->assertNull($result->getHeader('Location'), 'No location header should be added');
$this->assertEquals(200, $result->getStatusCode(), 'No redirection should be made'); $this->assertEquals(200, $result->getStatusCode(), 'No redirection should be made');
} else { } else {
@ -166,6 +144,56 @@ class CanonicalURLMiddlewareTest extends SapphireTest
} }
} }
} }
public function provideRedirectTrailingSlashIgnorePaths()
{
return [
[
'addTrailingSlash' => false,
'requestHasSlash' => false,
],
[
'addTrailingSlash' => false,
'requestHasSlash' => true,
],
[
'addTrailingSlash' => true,
'requestHasSlash' => true,
],
[
'addTrailingSlash' => true,
'requestHasSlash' => false,
],
];
}
/**
* @dataProvider provideRedirectTrailingSlashIgnorePaths
*/
public function testRedirectTrailingSlashIgnorePaths(bool $addTrailingSlash, bool $requestHasSlash)
{
Controller::config()->set('add_trailing_slash', $addTrailingSlash);
$middleware = $this->getMockedMiddleware(false);
$middleware->setEnforceTrailingSlashConfig(true);
$requestSlash = $requestHasSlash ? '/' : '';
$noRedirectPaths = [
"/admin{$requestSlash}",
"/admin/graphql{$requestSlash}",
"/dev/tasks/my-task{$requestSlash}",
];
$allowRedirectPaths = [
"/administration{$requestSlash}",
"/administration/more-path{$requestSlash}",
];
foreach ($noRedirectPaths as $path) {
$this->performRedirectTest($path, $middleware, false, $addTrailingSlash);
}
foreach ($allowRedirectPaths as $path) {
$this->performRedirectTest($path, $middleware, $addTrailingSlash !== $requestHasSlash, $addTrailingSlash);
}
} }
private function getMockedMiddleware($mockGetRedirect = true): CanonicalURLMiddleware private function getMockedMiddleware($mockGetRedirect = true): CanonicalURLMiddleware