mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #4830 from open-sausages/pulls/3/fix-querystring-stage
API Disable unauthenticated get parameter access to site stage mode
This commit is contained in:
commit
0175167761
@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialises the versioned stage when a request is made.
|
* Initialises the versioned stage when a request is made.
|
||||||
*
|
*
|
||||||
@ -8,7 +9,37 @@
|
|||||||
class VersionedRequestFilter implements RequestFilter {
|
class VersionedRequestFilter implements RequestFilter {
|
||||||
|
|
||||||
public function preRequest(SS_HTTPRequest $request, Session $session, DataModel $model) {
|
public function preRequest(SS_HTTPRequest $request, Session $session, DataModel $model) {
|
||||||
Versioned::choose_site_stage($session);
|
// Bootstrap session so that Session::get() accesses the right instance
|
||||||
|
$dummyController = new Controller();
|
||||||
|
$dummyController->setSession($session);
|
||||||
|
$dummyController->setRequest($request);
|
||||||
|
$dummyController->pushCurrent();
|
||||||
|
|
||||||
|
// Block non-authenticated users from setting the stage mode
|
||||||
|
if(!Versioned::can_choose_site_stage($request)) {
|
||||||
|
$permissionMessage = sprintf(
|
||||||
|
_t(
|
||||||
|
"ContentController.DRAFT_SITE_ACCESS_RESTRICTION",
|
||||||
|
'You must log in with your CMS password in order to view the draft or archived content. '.
|
||||||
|
'<a href="%s">Click here to go back to the published site.</a>'
|
||||||
|
),
|
||||||
|
Controller::join_links(Director::baseURL(), $request->getURL(), "?stage=Live")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Force output since RequestFilter::preRequest doesn't support response overriding
|
||||||
|
$response = Security::permissionFailure($dummyController, $permissionMessage);
|
||||||
|
$session->inst_save();
|
||||||
|
$dummyController->popCurrent();
|
||||||
|
// Prevent output in testing
|
||||||
|
if(class_exists('SapphireTest', false) && SapphireTest::is_running_test()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$response->output();
|
||||||
|
die;
|
||||||
|
}
|
||||||
|
|
||||||
|
Versioned::choose_site_stage();
|
||||||
|
$dummyController->popCurrent();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1084,6 +1084,26 @@ class Versioned extends DataExtension implements TemplateGlobalProvider {
|
|||||||
//-----------------------------------------------------------------------------------------------//
|
//-----------------------------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the current user is able to set the given site stage / archive
|
||||||
|
*
|
||||||
|
* @param SS_HTTPRequest $request
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function can_choose_site_stage($request) {
|
||||||
|
// Request is allowed if stage isn't being modified
|
||||||
|
if((!$request->getVar('stage') || $request->getVar('stage') === static::get_live_stage())
|
||||||
|
&& !$request->getVar('archiveDate')
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check permissions with member ID in session.
|
||||||
|
$member = Member::currentUser();
|
||||||
|
$permissions = Config::inst()->get(get_called_class(), 'non_live_permissions');
|
||||||
|
return $member && Permission::checkMember($member, $permissions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Choose the stage the site is currently on.
|
* Choose the stage the site is currently on.
|
||||||
*
|
*
|
||||||
@ -1095,14 +1115,10 @@ class Versioned extends DataExtension implements TemplateGlobalProvider {
|
|||||||
*
|
*
|
||||||
* If neither of these are set, it checks the session, otherwise the stage
|
* If neither of these are set, it checks the session, otherwise the stage
|
||||||
* is set to 'Live'.
|
* is set to 'Live'.
|
||||||
*
|
|
||||||
* @param Session $session Optional session within which to store the resulting stage
|
|
||||||
*/
|
*/
|
||||||
public static function choose_site_stage($session = null) {
|
public static function choose_site_stage() {
|
||||||
// Check any pre-existing session mode
|
// Check any pre-existing session mode
|
||||||
$preexistingMode = $session
|
$preexistingMode = Session::get('readingMode');
|
||||||
? $session->inst_get('readingMode')
|
|
||||||
: Session::get('readingMode');
|
|
||||||
|
|
||||||
// Determine the reading mode
|
// Determine the reading mode
|
||||||
if(isset($_GET['stage'])) {
|
if(isset($_GET['stage'])) {
|
||||||
@ -1124,12 +1140,8 @@ class Versioned extends DataExtension implements TemplateGlobalProvider {
|
|||||||
if(($preexistingMode && $preexistingMode !== $mode)
|
if(($preexistingMode && $preexistingMode !== $mode)
|
||||||
|| (!$preexistingMode && $mode !== self::DEFAULT_MODE)
|
|| (!$preexistingMode && $mode !== self::DEFAULT_MODE)
|
||||||
) {
|
) {
|
||||||
if($session) {
|
|
||||||
$session->inst_set('readingMode', $mode);
|
|
||||||
} else {
|
|
||||||
Session::set('readingMode', $mode);
|
Session::set('readingMode', $mode);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(!headers_sent() && !Director::is_cli()) {
|
if(!headers_sent() && !Director::is_cli()) {
|
||||||
if(Versioned::current_stage() == 'Live') {
|
if(Versioned::current_stage() == 'Live') {
|
||||||
|
@ -208,6 +208,7 @@ class Security extends Controller implements TemplateGlobalProvider {
|
|||||||
*
|
*
|
||||||
* The alreadyLoggedIn value can contain a '%s' placeholder that will be replaced with a link
|
* The alreadyLoggedIn value can contain a '%s' placeholder that will be replaced with a link
|
||||||
* to log in.
|
* to log in.
|
||||||
|
* @return SS_HTTPResponse
|
||||||
*/
|
*/
|
||||||
public static function permissionFailure($controller = null, $messageSet = null) {
|
public static function permissionFailure($controller = null, $messageSet = null) {
|
||||||
self::set_ignore_disallowed_actions(true);
|
self::set_ignore_disallowed_actions(true);
|
||||||
@ -226,7 +227,8 @@ class Security extends Controller implements TemplateGlobalProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $response;
|
return $response;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
// Prepare the messageSet provided
|
// Prepare the messageSet provided
|
||||||
if(!$messageSet) {
|
if(!$messageSet) {
|
||||||
if($configMessageSet = static::config()->get('default_message_set')) {
|
if($configMessageSet = static::config()->get('default_message_set')) {
|
||||||
@ -293,13 +295,11 @@ class Security extends Controller implements TemplateGlobalProvider {
|
|||||||
// Audit logging hook
|
// Audit logging hook
|
||||||
$controller->extend('permissionDenied', $member);
|
$controller->extend('permissionDenied', $member);
|
||||||
|
|
||||||
$controller->redirect(
|
return $controller->redirect(
|
||||||
Config::inst()->get('Security', 'login_url')
|
Config::inst()->get('Security', 'login_url')
|
||||||
. "?BackURL=" . urlencode($_SERVER['REQUEST_URI'])
|
. "?BackURL=" . urlencode($_SERVER['REQUEST_URI'])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function init() {
|
public function init() {
|
||||||
parent::init();
|
parent::init();
|
||||||
|
@ -599,6 +599,8 @@ class VersionedTest extends SapphireTest {
|
|||||||
*/
|
*/
|
||||||
public function testReadingPersistent() {
|
public function testReadingPersistent() {
|
||||||
$session = Injector::inst()->create('Session', array());
|
$session = Injector::inst()->create('Session', array());
|
||||||
|
$adminID = $this->logInWithPermission('ADMIN');
|
||||||
|
$session->inst_set('loggedInAs', $adminID);
|
||||||
|
|
||||||
// Set to stage
|
// Set to stage
|
||||||
Director::test('/?stage=Stage', null, $session);
|
Director::test('/?stage=Stage', null, $session);
|
||||||
@ -630,10 +632,11 @@ class VersionedTest extends SapphireTest {
|
|||||||
|
|
||||||
// Test that session doesn't redundantly store the default stage if it doesn't need to
|
// Test that session doesn't redundantly store the default stage if it doesn't need to
|
||||||
$session2 = Injector::inst()->create('Session', array());
|
$session2 = Injector::inst()->create('Session', array());
|
||||||
|
$session2->inst_set('loggedInAs', $adminID);
|
||||||
Director::test('/', null, $session2);
|
Director::test('/', null, $session2);
|
||||||
$this->assertEmpty($session2->inst_changedData());
|
$this->assertArrayNotHasKey('readingMode', $session2->inst_changedData());
|
||||||
Director::test('/?stage=Live', null, $session2);
|
Director::test('/?stage=Live', null, $session2);
|
||||||
$this->assertEmpty($session2->inst_changedData());
|
$this->assertArrayNotHasKey('readingMode', $session2->inst_changedData());
|
||||||
|
|
||||||
// Test choose_site_stage
|
// Test choose_site_stage
|
||||||
Session::set('readingMode', 'Stage.Stage');
|
Session::set('readingMode', 'Stage.Stage');
|
||||||
@ -647,6 +650,15 @@ class VersionedTest extends SapphireTest {
|
|||||||
$this->assertEquals('Stage.Live', Versioned::get_reading_mode());
|
$this->assertEquals('Stage.Live', Versioned::get_reading_mode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that stage parameter is blocked by non-administrative users
|
||||||
|
*/
|
||||||
|
public function testReadingModeSecurity() {
|
||||||
|
$this->setExpectedException('SS_HTTPResponse_Exception', 'Invalid request');
|
||||||
|
$session = Injector::inst()->create('Session', array());
|
||||||
|
$result = Director::test('/?stage=Stage', null, $session);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures that the latest version of a record is the expected value
|
* Ensures that the latest version of a record is the expected value
|
||||||
*
|
*
|
||||||
|
Loading…
Reference in New Issue
Block a user