diff --git a/src/Core/Startup/ConfirmationToken.php b/src/Core/Startup/AbstractConfirmationToken.php
similarity index 95%
rename from src/Core/Startup/ConfirmationToken.php
rename to src/Core/Startup/AbstractConfirmationToken.php
index d23563f46..11f78b48a 100644
--- a/src/Core/Startup/ConfirmationToken.php
+++ b/src/Core/Startup/AbstractConfirmationToken.php
@@ -15,7 +15,7 @@ use SilverStripe\Security\RandomGenerator;
*
* @internal This class is designed specifically for use pre-startup and may change without warning
*/
-abstract class ConfirmationToken
+abstract class AbstractConfirmationToken
{
/**
* @var HTTPRequest
@@ -173,6 +173,16 @@ HTML;
*/
abstract public function params($includeToken = true);
+ /**
+ * @return string
+ */
+ abstract public function getRedirectUrlBase();
+
+ /**
+ * @return array
+ */
+ abstract public function getRedirectUrlParams();
+
/**
* Get redirection URL
*
diff --git a/src/Core/Startup/ConfirmationTokenChain.php b/src/Core/Startup/ConfirmationTokenChain.php
new file mode 100644
index 000000000..a47f2c4c0
--- /dev/null
+++ b/src/Core/Startup/ConfirmationTokenChain.php
@@ -0,0 +1,178 @@
+tokens[] = $token;
+ }
+
+ /**
+ * Collect all tokens that require a redirect
+ *
+ * @return \Generator
+ */
+ protected function filteredTokens()
+ {
+ foreach ($this->tokens as $token) {
+ if ($token->reloadRequired() || $token->reloadRequiredIfError()) {
+ yield $token;
+ }
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public function suppressionRequired()
+ {
+ foreach ($this->tokens as $token) {
+ if ($token->reloadRequired()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Suppress URLs & GET vars from tokens that require a redirect
+ */
+ public function suppressTokens()
+ {
+ foreach ($this->filteredTokens() as $token) {
+ $token->suppress();
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public function reloadRequired()
+ {
+ foreach ($this->tokens as $token) {
+ if ($token->reloadRequired()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @return bool
+ */
+ public function reloadRequiredIfError()
+ {
+ foreach ($this->tokens as $token) {
+ if ($token->reloadRequiredIfError()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param bool $includeToken
+ * @return array
+ */
+ public function params($includeToken = true)
+ {
+ $params = [];
+ foreach ($this->tokens as $token) {
+ $params = array_merge($params, $token->params($includeToken));
+ }
+
+ return $params;
+ }
+
+ /**
+ * Fetch the URL we want to redirect to, excluding query string parameters. This may
+ * be the same URL (with a token to be added outside this method), or to a different
+ * URL if the current one has been suppressed
+ *
+ * @return string
+ */
+ public function getRedirectUrlBase()
+ {
+ // URLConfirmationTokens may alter the URL to suppress the URL they're protecting,
+ // so we need to ensure they're inspected last and therefore take priority
+ $tokens = iterator_to_array($this->filteredTokens(), false);
+ usort($tokens, function ($a, $b) {
+ return ($a instanceof URLConfirmationToken) ? 1 : 0;
+ });
+
+ $urlBase = Director::baseURL();
+ foreach ($tokens as $token) {
+ $urlBase = $token->getRedirectUrlBase();
+ }
+
+ return $urlBase;
+ }
+
+ /**
+ * Collate GET vars from all token providers that need to apply a token
+ *
+ * @return array
+ */
+ public function getRedirectUrlParams()
+ {
+ $params = [];
+ foreach ($this->filteredTokens() as $token) {
+ $params = array_merge($params, $token->getRedirectUrlParams());
+ }
+
+ return $params;
+ }
+
+ /**
+ * @return string
+ */
+ protected function redirectURL()
+ {
+ $params = http_build_query($this->getRedirectUrlParams());
+ return Controller::join_links($this->getRedirectUrlBase(), '?' . $params);
+ }
+
+ /**
+ * @return HTTPResponse
+ */
+ public function reloadWithTokens()
+ {
+ $location = $this->redirectURL();
+ $locationJS = Convert::raw2js($location);
+ $locationATT = Convert::raw2att($location);
+ $body = <<location.href='$locationJS';
+
+You are being redirected. If you are not redirected soon, click here to continue
+HTML;
+
+ // Build response
+ $result = new HTTPResponse($body);
+ $result->redirect($location);
+ return $result;
+ }
+}
diff --git a/src/Core/Startup/ErrorControlChainMiddleware.php b/src/Core/Startup/ErrorControlChainMiddleware.php
index e81444629..c29878e15 100644
--- a/src/Core/Startup/ErrorControlChainMiddleware.php
+++ b/src/Core/Startup/ErrorControlChainMiddleware.php
@@ -34,20 +34,18 @@ class ErrorControlChainMiddleware implements HTTPMiddleware
/**
* @param HTTPRequest $request
- * @return ConfirmationToken|null
+ * @return ConfirmationTokenChain
*/
- protected function prepareConfirmationTokenIfRequired(HTTPRequest $request)
+ protected function prepareConfirmationTokenChain(HTTPRequest $request)
{
- $token = URLConfirmationToken::prepare_tokens(['dev/build'], $request);
+ $chain = new ConfirmationTokenChain();
+ $chain->pushToken(new URLConfirmationToken('dev/build', $request));
- if (!$token) {
- $token = ParameterConfirmationToken::prepare_tokens(
- ['isTest', 'isDev', 'flush'],
- $request
- );
+ foreach (['isTest', 'isDev', 'flush'] as $parameter) {
+ $chain->pushToken(new ParameterConfirmationToken($parameter, $request));
}
- return $token;
+ return $chain;
}
public function process(HTTPRequest $request, callable $next)
@@ -55,19 +53,21 @@ class ErrorControlChainMiddleware implements HTTPMiddleware
$result = null;
// Prepare tokens and execute chain
- $confirmationToken = $this->prepareConfirmationTokenIfRequired($request);
- $chain = new ErrorControlChain();
- $chain
- ->then(function () use ($request, $chain, $confirmationToken, $next, &$result) {
- // If no redirection is necessary then we can disable error supression
- if (!$confirmationToken) {
- $chain->setSuppression(false);
+ $confirmationTokenChain = $this->prepareConfirmationTokenChain($request);
+ $errorControlChain = new ErrorControlChain();
+ $errorControlChain
+ ->then(function () use ($request, $errorControlChain, $confirmationTokenChain, $next, &$result) {
+ if ($confirmationTokenChain->suppressionRequired()) {
+ $confirmationTokenChain->suppressTokens();
+ } else {
+ // If no redirection is necessary then we can disable error supression
+ $errorControlChain->setSuppression(false);
}
try {
// Check if a token is requesting a redirect
- if ($confirmationToken && $confirmationToken->reloadRequired()) {
- $result = $this->safeReloadWithToken($request, $confirmationToken);
+ if ($confirmationTokenChain && $confirmationTokenChain->reloadRequired()) {
+ $result = $this->safeReloadWithTokens($request, $confirmationTokenChain);
} else {
// If no reload necessary, process application
$result = call_user_func($next, $request);
@@ -77,10 +77,16 @@ class ErrorControlChainMiddleware implements HTTPMiddleware
}
})
// Finally if a token was requested but there was an error while figuring out if it's allowed, do it anyway
- ->thenIfErrored(function () use ($confirmationToken) {
- if ($confirmationToken && $confirmationToken->reloadRequiredIfError()) {
- $result = $confirmationToken->reloadWithToken();
- $result->output();
+ ->thenIfErrored(function () use ($confirmationTokenChain) {
+ if ($confirmationTokenChain && $confirmationTokenChain->reloadRequiredIfError()) {
+ try {
+ // Reload requires manual boot
+ $this->getApplication()->getKernel()->boot(false);
+ } finally {
+ // Given we're in an error state here, try to continue even if the kernel boot fails
+ $result = $confirmationTokenChain->reloadWithTokens();
+ $result->output();
+ }
}
})
->execute();
@@ -92,10 +98,10 @@ class ErrorControlChainMiddleware implements HTTPMiddleware
* or authentication is impossible.
*
* @param HTTPRequest $request
- * @param ParameterConfirmationToken $reloadToken
+ * @param ConfirmationTokenChain $confirmationTokenChain
* @return HTTPResponse
*/
- protected function safeReloadWithToken(HTTPRequest $request, $reloadToken)
+ protected function safeReloadWithTokens(HTTPRequest $request, ConfirmationTokenChain $confirmationTokenChain)
{
// Safe reload requires manual boot
$this->getApplication()->getKernel()->boot(false);
@@ -104,9 +110,9 @@ class ErrorControlChainMiddleware implements HTTPMiddleware
$request->getSession()->init($request);
// Request with ErrorDirector
- $result = ErrorDirector::singleton()->handleRequestWithToken(
+ $result = ErrorDirector::singleton()->handleRequestWithTokenChain(
$request,
- $reloadToken,
+ $confirmationTokenChain,
$this->getApplication()->getKernel()
);
if ($result) {
@@ -114,8 +120,8 @@ class ErrorControlChainMiddleware implements HTTPMiddleware
}
// Fail and redirect the user to the login page
- $params = array_merge($request->getVars(), $reloadToken->params(false));
- $backURL = $reloadToken->currentURL() . '?' . http_build_query($params);
+ $params = array_merge($request->getVars(), $confirmationTokenChain->params(false));
+ $backURL = $confirmationTokenChain->getRedirectUrlBase() . '?' . http_build_query($params);
$loginPage = Director::absoluteURL(Security::config()->get('login_url'));
$loginPage .= "?BackURL=" . urlencode($backURL);
$result = new HTTPResponse();
diff --git a/src/Core/Startup/ErrorDirector.php b/src/Core/Startup/ErrorDirector.php
index 54001fd05..575df7183 100644
--- a/src/Core/Startup/ErrorDirector.php
+++ b/src/Core/Startup/ErrorDirector.php
@@ -21,18 +21,21 @@ class ErrorDirector extends Director
* Redirect with token if allowed, or null if not allowed
*
* @param HTTPRequest $request
- * @param ConfirmationToken $token
+ * @param ConfirmationTokenChain $confirmationTokenChain
* @param Kernel $kernel
* @return null|HTTPResponse Redirection response, or null if not able to redirect
*/
- public function handleRequestWithToken(HTTPRequest $request, ConfirmationToken $token, Kernel $kernel)
- {
+ public function handleRequestWithTokenChain(
+ HTTPRequest $request,
+ ConfirmationTokenChain $confirmationTokenChain,
+ Kernel $kernel
+ ) {
Injector::inst()->registerService($request, HTTPRequest::class);
// Next, check if we're in dev mode, or the database doesn't have any security data, or we are admin
- $reload = function (HTTPRequest $request) use ($token, $kernel) {
+ $reload = function (HTTPRequest $request) use ($confirmationTokenChain, $kernel) {
if ($kernel->getEnvironment() === Kernel::DEV || !Security::database_is_ready() || Permission::check('ADMIN')) {
- return $token->reloadWithToken();
+ return $confirmationTokenChain->reloadWithTokens();
}
return null;
};
diff --git a/src/Core/Startup/ParameterConfirmationToken.php b/src/Core/Startup/ParameterConfirmationToken.php
index 4e90f1ef7..bc751a2c3 100644
--- a/src/Core/Startup/ParameterConfirmationToken.php
+++ b/src/Core/Startup/ParameterConfirmationToken.php
@@ -3,6 +3,7 @@
namespace SilverStripe\Core\Startup;
use SilverStripe\Control\Controller;
+use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Convert;
@@ -15,7 +16,7 @@ use SilverStripe\Security\RandomGenerator;
*
* @internal This class is designed specifically for use pre-startup and may change without warning
*/
-class ParameterConfirmationToken extends ConfirmationToken
+class ParameterConfirmationToken extends AbstractConfirmationToken
{
/**
* The name of the parameter
@@ -140,19 +141,22 @@ class ParameterConfirmationToken extends ConfirmationToken
}
return $params;
}
+
+ public function getRedirectUrlBase()
+ {
+ return ($this->existsInReferer() && !$this->parameterProvided()) ? Director::baseURL() : $this->currentURL();
+ }
+
+ public function getRedirectUrlParams()
+ {
+ return ($this->existsInReferer() && !$this->parameterProvided())
+ ? $this->params()
+ : array_merge($this->request->getVars(), $this->params());
+ }
protected function redirectURL()
{
- // If url is encoded via BackURL, defer to home page (prevent redirect to form action)
- if ($this->existsInReferer() && !$this->parameterProvided()) {
- $url = BASE_URL ?: '/';
- $params = $this->params();
- } else {
- $url = $this->currentURL();
- $params = array_merge($this->request->getVars(), $this->params());
- }
-
- // Merge get params with current url
- return Controller::join_links($url, '?' . http_build_query($params));
+ $query = http_build_query($this->getRedirectUrlParams());
+ return Controller::join_links($this->getRedirectUrlBase(), '?' . $query);
}
}
diff --git a/src/Core/Startup/URLConfirmationToken.php b/src/Core/Startup/URLConfirmationToken.php
index 8176a41b8..bb509931e 100644
--- a/src/Core/Startup/URLConfirmationToken.php
+++ b/src/Core/Startup/URLConfirmationToken.php
@@ -12,7 +12,7 @@ use SilverStripe\Control\HTTPRequest;
*
* @internal This class is designed specifically for use pre-startup and may change without warning
*/
-class URLConfirmationToken extends ConfirmationToken
+class URLConfirmationToken extends AbstractConfirmationToken
{
/**
* @var string
@@ -60,7 +60,7 @@ class URLConfirmationToken extends ConfirmationToken
*/
protected function getURLExistsInBackURL(HTTPRequest $request)
{
- $backURL = $request->getVar('BackURL');
+ $backURL = ltrim($request->getVar('BackURL'), '/');
return (strpos($backURL, $this->urlToCheck) === 0);
}
@@ -119,18 +119,21 @@ class URLConfirmationToken extends ConfirmationToken
return Controller::join_links(Director::baseURL(), $this->currentURL);
}
+ public function getRedirectUrlBase()
+ {
+ return ($this->urlExistsInBackURL && !$this->urlMatches()) ? Director::baseURL() : $this->currentURL();
+ }
+
+ public function getRedirectUrlParams()
+ {
+ return ($this->urlExistsInBackURL && !$this->urlMatches())
+ ? $this->params()
+ : array_merge($this->request->getVars(), $this->params());
+ }
+
protected function redirectURL()
{
- // If url is encoded via BackURL, defer to home page (prevent redirect to form action)
- if ($this->urlExistsInBackURL && !$this->urlMatches()) {
- $url = BASE_URL ?: '/';
- $params = $this->params();
- } else {
- $url = $this->currentURL();
- $params = array_merge($this->request->getVars(), $this->params());
- }
-
- // Merge get params with current url
- return Controller::join_links($url, '?' . http_build_query($params));
+ $query = http_build_query($this->getRedirectUrlParams());
+ return Controller::join_links($this->getRedirectUrlBase(), '?' . $query);
}
}
diff --git a/tests/php/Core/Startup/ConfirmationTokenChainTest.php b/tests/php/Core/Startup/ConfirmationTokenChainTest.php
new file mode 100644
index 000000000..adb8fba36
--- /dev/null
+++ b/tests/php/Core/Startup/ConfirmationTokenChainTest.php
@@ -0,0 +1,185 @@
+createPartialMock(ParameterConfirmationToken::class, $methods);
+ $mock->expects($this->any())
+ ->method('reloadRequired')
+ ->will($this->returnValue($requiresReload));
+ return $mock;
+ }
+
+ protected function getTokenRequiringReloadIfError($requiresReload = true, $extraMethods = [])
+ {
+ $methods = array_merge(['reloadRequired', 'reloadRequiredIfError'], $extraMethods);
+ $mock = $this->createPartialMock(ParameterConfirmationToken::class, $methods);
+ $mock->expects($this->any())
+ ->method('reloadRequired')
+ ->will($this->returnValue(false));
+ $mock->expects($this->any())
+ ->method('reloadRequiredIfError')
+ ->will($this->returnValue($requiresReload));
+ return $mock;
+ }
+
+ public function testFilteredTokens()
+ {
+ $chain = new ConfirmationTokenChain();
+ $chain->pushToken($tokenRequiringReload = $this->getTokenRequiringReload());
+ $chain->pushToken($tokenNotRequiringReload = $this->getTokenRequiringReload(false));
+ $chain->pushToken($tokenRequiringReloadIfError = $this->getTokenRequiringReloadIfError());
+ $chain->pushToken($tokenNotRequiringReloadIfError = $this->getTokenRequiringReloadIfError(false));
+
+ $reflectionMethod = new \ReflectionMethod(ConfirmationTokenChain::class, 'filteredTokens');
+ $reflectionMethod->setAccessible(true);
+ $tokens = iterator_to_array($reflectionMethod->invoke($chain));
+
+ $this->assertContains($tokenRequiringReload, $tokens, 'Token requiring a reload was not returned');
+ $this->assertNotContains($tokenNotRequiringReload, $tokens, 'Token not requiring a reload was returned');
+ $this->assertContains($tokenRequiringReloadIfError, $tokens, 'Token requiring a reload on error was not returned');
+ $this->assertNotContains($tokenNotRequiringReloadIfError, $tokens, 'Token not requiring a reload on error was returned');
+ }
+
+ public function testSuppressionRequired()
+ {
+ $chain = new ConfirmationTokenChain();
+ $chain->pushToken($this->getTokenRequiringReload(false));
+ $this->assertFalse($chain->suppressionRequired(), 'Suppression incorrectly marked as required');
+
+ $chain = new ConfirmationTokenChain();
+ $chain->pushToken($this->getTokenRequiringReloadIfError(false));
+ $this->assertFalse($chain->suppressionRequired(), 'Suppression incorrectly marked as required');
+
+ $chain = new ConfirmationTokenChain();
+ $chain->pushToken($this->getTokenRequiringReload());
+ $this->assertTrue($chain->suppressionRequired(), 'Suppression not marked as required');
+
+ $chain = new ConfirmationTokenChain();
+ $chain->pushToken($this->getTokenRequiringReloadIfError());
+ $this->assertFalse($chain->suppressionRequired(), 'Suppression incorrectly marked as required');
+ }
+
+ public function testSuppressTokens()
+ {
+ $mockToken = $this->getTokenRequiringReload(true, ['suppress']);
+ $mockToken->expects($this->once())
+ ->method('suppress');
+ $secondMockToken = $this->getTokenRequiringReloadIfError(true, ['suppress']);
+ $secondMockToken->expects($this->once())
+ ->method('suppress');
+
+ $chain = new ConfirmationTokenChain();
+ $chain->pushToken($mockToken);
+ $chain->pushToken($secondMockToken);
+ $chain->suppressTokens();
+ }
+
+ public function testReloadRequired()
+ {
+ $mockToken = $this->getTokenRequiringReload(true);
+ $secondMockToken = $this->getTokenRequiringReload(false);
+
+ $chain = new ConfirmationTokenChain();
+ $chain->pushToken($mockToken);
+ $chain->pushToken($secondMockToken);
+ $this->assertTrue($chain->reloadRequired());
+ }
+
+ public function testReloadRequiredIfError()
+ {
+ $mockToken = $this->getTokenRequiringReloadIfError(true);
+ $secondMockToken = $this->getTokenRequiringReloadIfError(false);
+
+ $chain = new ConfirmationTokenChain();
+ $chain->pushToken($mockToken);
+ $chain->pushToken($secondMockToken);
+ $this->assertTrue($chain->reloadRequiredIfError());
+ }
+
+ public function testParams()
+ {
+ $mockToken = $this->getTokenRequiringReload(true, ['params']);
+ $mockToken->expects($this->once())
+ ->method('params')
+ ->with($this->isTrue())
+ ->will($this->returnValue(['mockTokenParam' => '1']));
+ $secondMockToken = $this->getTokenRequiringReload(true, ['params']);
+ $secondMockToken->expects($this->once())
+ ->method('params')
+ ->with($this->isTrue())
+ ->will($this->returnValue(['secondMockTokenParam' => '2']));
+
+ $chain = new ConfirmationTokenChain();
+ $chain->pushToken($mockToken);
+ $chain->pushToken($secondMockToken);
+ $this->assertEquals(['mockTokenParam' => '1', 'secondMockTokenParam' => '2'], $chain->params(true));
+
+ $mockToken = $this->getTokenRequiringReload(true, ['params']);
+ $mockToken->expects($this->once())
+ ->method('params')
+ ->with($this->isFalse())
+ ->will($this->returnValue(['mockTokenParam' => '1']));
+
+ $chain = new ConfirmationTokenChain();
+ $chain->pushToken($mockToken);
+ $this->assertEquals(['mockTokenParam' => '1'], $chain->params(false));
+ }
+
+ public function testGetRedirectUrlBase()
+ {
+ $mockUrlToken = $this->createPartialMock(URLConfirmationToken::class, ['reloadRequired', 'getRedirectUrlBase']);
+ $mockUrlToken->expects($this->any())
+ ->method('reloadRequired')
+ ->will($this->returnValue(true));
+ $mockUrlToken->expects($this->any())
+ ->method('getRedirectUrlBase')
+ ->will($this->returnValue('url-base'));
+
+ $mockParameterToken = $this->createPartialMock(ParameterConfirmationToken::class, ['reloadRequired', 'getRedirectUrlBase']);
+ $mockParameterToken->expects($this->any())
+ ->method('reloadRequired')
+ ->will($this->returnValue(true));
+ $mockParameterToken->expects($this->any())
+ ->method('getRedirectUrlBase')
+ ->will($this->returnValue('parameter-base'));
+
+ $chain = new ConfirmationTokenChain();
+ $chain->pushToken($mockParameterToken);
+ $chain->pushToken($mockUrlToken);
+ $this->assertEquals('url-base', $chain->getRedirectUrlBase(), 'URLConfirmationToken url base should take priority');
+
+ // Push them in reverse order to check priority still correct
+ $chain = new ConfirmationTokenChain();
+ $chain->pushToken($mockUrlToken);
+ $chain->pushToken($mockParameterToken);
+ $this->assertEquals('url-base', $chain->getRedirectUrlBase(), 'URLConfirmationToken url base should take priority');
+ }
+
+ public function testGetRedirectUrlParams()
+ {
+ $mockToken = $this->getTokenRequiringReload(true, ['getRedirectUrlParams']);
+ $mockToken->expects($this->once())
+ ->method('getRedirectUrlParams')
+ ->will($this->returnValue(['mockTokenParam' => '1']));
+
+ $secondMockToken = $this->getTokenRequiringReload(true, ['getRedirectUrlParams']);
+ $secondMockToken->expects($this->once())
+ ->method('getRedirectUrlParams')
+ ->will($this->returnValue(['secondMockTokenParam' => '2']));
+
+ $chain = new ConfirmationTokenChain();
+ $chain->pushToken($mockToken);
+ $chain->pushToken($secondMockToken);
+ $this->assertEquals(['mockTokenParam' => '1', 'secondMockTokenParam' => '2'], $chain->getRedirectUrlParams());
+ }
+}
diff --git a/tests/php/Core/Startup/ErrorControlChainMiddlewareTest.php b/tests/php/Core/Startup/ErrorControlChainMiddlewareTest.php
index 7df6f6ebf..a90799026 100644
--- a/tests/php/Core/Startup/ErrorControlChainMiddlewareTest.php
+++ b/tests/php/Core/Startup/ErrorControlChainMiddlewareTest.php
@@ -121,4 +121,56 @@ class ErrorControlChainMiddlewareTest extends SapphireTest
$this->assertNotContains('?devbuildtoken=', $location);
$this->assertContains('Security/login', $location);
}
+
+ public function testLiveBuildAndFlushAdmin()
+ {
+ // Mock admin
+ $adminID = $this->logInWithPermission('ADMIN');
+ $this->logOut();
+
+ // Mock app
+ $app = new HTTPApplication(new BlankKernel(BASE_PATH));
+ $app->getKernel()->setEnvironment(Kernel::LIVE);
+
+ // Test being logged in as admin
+ $chain = new ErrorControlChainMiddleware($app);
+ $request = new HTTPRequest('GET', '/dev/build/', ['flush' => '1']);
+ $request->setSession(new Session(['loggedInAs' => $adminID]));
+ $result = $chain->process($request, function () {
+ return null;
+ });
+
+ $this->assertInstanceOf(HTTPResponse::class, $result);
+ $location = $result->getHeader('Location');
+ $this->assertContains('/dev/build', $location);
+ $this->assertContains('flush=1', $location);
+ $this->assertContains('devbuildtoken=', $location);
+ $this->assertContains('flushtoken=', $location);
+ $this->assertNotContains('Security/login', $location);
+ }
+
+ public function testLiveBuildAndFlushUnauthenticated()
+ {
+ // Mock app
+ $app = new HTTPApplication(new BlankKernel(BASE_PATH));
+ $app->getKernel()->setEnvironment(Kernel::LIVE);
+
+ // Test being logged in as no one
+ Security::setCurrentUser(null);
+ $chain = new ErrorControlChainMiddleware($app);
+ $request = new HTTPRequest('GET', '/dev/build', ['flush' => '1']);
+ $request->setSession(new Session(['loggedInAs' => 0]));
+ $result = $chain->process($request, function () {
+ return null;
+ });
+
+ // Should be directed to login, not to flush
+ $this->assertInstanceOf(HTTPResponse::class, $result);
+ $location = $result->getHeader('Location');
+ $this->assertNotContains('/dev/build', $location);
+ $this->assertNotContains('flush=1', $location);
+ $this->assertNotContains('devbuildtoken=', $location);
+ $this->assertNotContains('flushtoken=', $location);
+ $this->assertContains('Security/login', $location);
+ }
}