mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Implement ConfirmationTokenChain to handle multiple tokens at once
This commit is contained in:
parent
214e28127f
commit
5563537cc8
@ -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
|
||||
*
|
178
src/Core/Startup/ConfirmationTokenChain.php
Normal file
178
src/Core/Startup/ConfirmationTokenChain.php
Normal file
@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Startup;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Core\Convert;
|
||||
|
||||
/**
|
||||
* A chain of confirmation tokens to be validated on each request. This allows the application to
|
||||
* check multiple tokens at once without having to potentially redirect the user for each of them
|
||||
*
|
||||
* @internal This class is designed specifically for use pre-startup and may change without warning
|
||||
*/
|
||||
class ConfirmationTokenChain
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $tokens = [];
|
||||
|
||||
/**
|
||||
* @param AbstractConfirmationToken $token
|
||||
*/
|
||||
public function pushToken(AbstractConfirmationToken $token)
|
||||
{
|
||||
$this->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 = <<<HTML
|
||||
<script>location.href='$locationJS';</script>
|
||||
<noscript><meta http-equiv="refresh" content="0; url=$locationATT"></noscript>
|
||||
You are being redirected. If you are not redirected soon, <a href="$locationATT">click here to continue</a>
|
||||
HTML;
|
||||
|
||||
// Build response
|
||||
$result = new HTTPResponse($body);
|
||||
$result->redirect($location);
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
$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
|
||||
if (!$confirmationToken) {
|
||||
$chain->setSuppression(false);
|
||||
$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,11 +77,17 @@ 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();
|
||||
->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();
|
||||
return $result;
|
||||
@ -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();
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
@ -141,18 +142,21 @@ class ParameterConfirmationToken extends ConfirmationToken
|
||||
return $params;
|
||||
}
|
||||
|
||||
protected function redirectURL()
|
||||
public function getRedirectUrlBase()
|
||||
{
|
||||
// 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());
|
||||
return ($this->existsInReferer() && !$this->parameterProvided()) ? Director::baseURL() : $this->currentURL();
|
||||
}
|
||||
|
||||
// Merge get params with current url
|
||||
return Controller::join_links($url, '?' . http_build_query($params));
|
||||
public function getRedirectUrlParams()
|
||||
{
|
||||
return ($this->existsInReferer() && !$this->parameterProvided())
|
||||
? $this->params()
|
||||
: array_merge($this->request->getVars(), $this->params());
|
||||
}
|
||||
|
||||
protected function redirectURL()
|
||||
{
|
||||
$query = http_build_query($this->getRedirectUrlParams());
|
||||
return Controller::join_links($this->getRedirectUrlBase(), '?' . $query);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
protected function redirectURL()
|
||||
public function getRedirectUrlBase()
|
||||
{
|
||||
// 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());
|
||||
return ($this->urlExistsInBackURL && !$this->urlMatches()) ? Director::baseURL() : $this->currentURL();
|
||||
}
|
||||
|
||||
// Merge get params with current url
|
||||
return Controller::join_links($url, '?' . http_build_query($params));
|
||||
public function getRedirectUrlParams()
|
||||
{
|
||||
return ($this->urlExistsInBackURL && !$this->urlMatches())
|
||||
? $this->params()
|
||||
: array_merge($this->request->getVars(), $this->params());
|
||||
}
|
||||
|
||||
protected function redirectURL()
|
||||
{
|
||||
$query = http_build_query($this->getRedirectUrlParams());
|
||||
return Controller::join_links($this->getRedirectUrlBase(), '?' . $query);
|
||||
}
|
||||
}
|
||||
|
185
tests/php/Core/Startup/ConfirmationTokenChainTest.php
Normal file
185
tests/php/Core/Startup/ConfirmationTokenChainTest.php
Normal file
@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Tests\Startup;
|
||||
|
||||
use SilverStripe\Core\Startup\ConfirmationTokenChain;
|
||||
use SilverStripe\Core\Startup\ParameterConfirmationToken;
|
||||
use SilverStripe\Core\Startup\URLConfirmationToken;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
class ConfirmationTokenChainTest extends SapphireTest
|
||||
{
|
||||
protected function getTokenRequiringReload($requiresReload = true, $extraMethods = [])
|
||||
{
|
||||
$methods = array_merge(['reloadRequired'], $extraMethods);
|
||||
$mock = $this->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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user