mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge remote-tracking branch 'origin/3.1' into 3
Conflicts: admin/code/ModelAdmin.php control/Director.php model/SQLQuery.php security/Member.php tests/control/HTTPTest.php tests/model/SQLQueryTest.php tests/security/SecurityTest.php tests/view/SSViewerTest.php
This commit is contained in:
commit
43f49e8434
@ -322,25 +322,28 @@ abstract class ModelAdmin extends LeftAndMain {
|
|||||||
* @return Form
|
* @return Form
|
||||||
*/
|
*/
|
||||||
public function ImportForm() {
|
public function ImportForm() {
|
||||||
$modelName = $this->modelClass;
|
$modelSNG = singleton($this->modelClass);
|
||||||
|
$modelName = $modelSNG->i18n_singular_name();
|
||||||
// check if a import form should be generated
|
// check if a import form should be generated
|
||||||
if(!$this->showImportForm || (is_array($this->showImportForm) && !in_array($modelName,$this->showImportForm))) {
|
if(!$this->showImportForm ||
|
||||||
|
(is_array($this->showImportForm) && !in_array($this->modelClass, $this->showImportForm))
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$importers = $this->getModelImporters();
|
$importers = $this->getModelImporters();
|
||||||
if(!$importers || !isset($importers[$modelName])) return false;
|
if(!$importers || !isset($importers[$this->modelClass])) return false;
|
||||||
|
|
||||||
if(!singleton($modelName)->canCreate(Member::currentUser())) return false;
|
if(!$modelSNG->canCreate(Member::currentUser())) return false;
|
||||||
|
|
||||||
$fields = new FieldList(
|
$fields = new FieldList(
|
||||||
new HiddenField('ClassName', _t('ModelAdmin.CLASSTYPE'), $modelName),
|
new HiddenField('ClassName', _t('ModelAdmin.CLASSTYPE'), $this->modelClass),
|
||||||
new FileField('_CsvFile', false)
|
new FileField('_CsvFile', false)
|
||||||
);
|
);
|
||||||
|
|
||||||
// get HTML specification for each import (column names etc.)
|
// get HTML specification for each import (column names etc.)
|
||||||
$importerClass = $importers[$modelName];
|
$importerClass = $importers[$this->modelClass];
|
||||||
$importer = new $importerClass($modelName);
|
$importer = new $importerClass($this->modelClass);
|
||||||
$spec = $importer->getImportSpec();
|
$spec = $importer->getImportSpec();
|
||||||
$specFields = new ArrayList();
|
$specFields = new ArrayList();
|
||||||
foreach($spec['fields'] as $name => $desc) {
|
foreach($spec['fields'] as $name => $desc) {
|
||||||
|
@ -438,12 +438,21 @@ class Director implements TemplateGlobalProvider {
|
|||||||
public static function absoluteURL($url, $relativeToSiteBase = false) {
|
public static function absoluteURL($url, $relativeToSiteBase = false) {
|
||||||
if(!isset($_SERVER['REQUEST_URI'])) return false;
|
if(!isset($_SERVER['REQUEST_URI'])) return false;
|
||||||
|
|
||||||
|
//a url of . or ./ is the same as an empty url
|
||||||
|
if ($url == '.' || $url == './') {
|
||||||
|
$url = '';
|
||||||
|
}
|
||||||
|
|
||||||
if(strpos($url,'/') === false && !$relativeToSiteBase) {
|
if(strpos($url,'/') === false && !$relativeToSiteBase) {
|
||||||
$url = dirname($_SERVER['REQUEST_URI'] . 'x') . '/' . $url;
|
//if there's no URL we want to force a trailing slash on the link
|
||||||
|
if (!$url) {
|
||||||
|
$url = '/';
|
||||||
|
}
|
||||||
|
$url = Controller::join_links(dirname($_SERVER['REQUEST_URI'] . 'x'), $url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(substr($url,0,4) != "http") {
|
if(substr($url,0,4) != "http") {
|
||||||
if($url[0] != "/") $url = Director::baseURL() . $url;
|
if(strpos($url, '/') !== 0) $url = Director::baseURL() . $url;
|
||||||
// Sometimes baseURL() can return a full URL instead of just a path
|
// Sometimes baseURL() can return a full URL instead of just a path
|
||||||
if(substr($url,0,4) != "http") $url = self::protocolAndHost() . $url;
|
if(substr($url,0,4) != "http") $url = self::protocolAndHost() . $url;
|
||||||
}
|
}
|
||||||
@ -802,14 +811,10 @@ class Director implements TemplateGlobalProvider {
|
|||||||
* @param string $destURL - The URL to redirect to
|
* @param string $destURL - The URL to redirect to
|
||||||
*/
|
*/
|
||||||
protected static function force_redirect($destURL) {
|
protected static function force_redirect($destURL) {
|
||||||
$response = new SS_HTTPResponse(
|
$response = new SS_HTTPResponse();
|
||||||
"<h1>Your browser is not accepting header redirects</h1>".
|
$response->redirect($destURL, 301);
|
||||||
"<p>Please <a href=\"$destURL\">click here</a>",
|
|
||||||
301
|
|
||||||
);
|
|
||||||
|
|
||||||
HTTP::add_cache_headers($response);
|
HTTP::add_cache_headers($response);
|
||||||
$response->addHeader('Location', $destURL);
|
|
||||||
|
|
||||||
// TODO: Use an exception - ATM we can be called from _config.php, before Director#handleRequest's try block
|
// TODO: Use an exception - ATM we can be called from _config.php, before Director#handleRequest's try block
|
||||||
$response->output();
|
$response->output();
|
||||||
|
@ -361,7 +361,8 @@ class HTTP {
|
|||||||
if(
|
if(
|
||||||
$body &&
|
$body &&
|
||||||
Director::is_https() &&
|
Director::is_https() &&
|
||||||
strstr($_SERVER["HTTP_USER_AGENT"], 'MSIE')==true &&
|
isset($_SERVER['HTTP_USER_AGENT']) &&
|
||||||
|
strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')==true &&
|
||||||
strstr($contentDisposition, 'attachment;')==true
|
strstr($contentDisposition, 'attachment;')==true
|
||||||
) {
|
) {
|
||||||
// IE6-IE8 have problems saving files when https and no-cache are used
|
// IE6-IE8 have problems saving files when https and no-cache are used
|
||||||
|
@ -230,15 +230,32 @@ class Convert {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an XML string to a PHP array
|
* Converts an XML string to a PHP array
|
||||||
|
* See http://phpsecurity.readthedocs.org/en/latest/Injection-Attacks.html#xml-external-entity-injection
|
||||||
*
|
*
|
||||||
* @uses recursiveXMLToArray()
|
* @uses recursiveXMLToArray()
|
||||||
* @param string
|
* @param string $val
|
||||||
*
|
* @param boolean $disableDoctypes Disables the use of DOCTYPE, and will trigger an error if encountered.
|
||||||
|
* false by default.
|
||||||
|
* @param boolean $disableExternals Disables the loading of external entities. false by default.
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function xml2array($val) {
|
public static function xml2array($val, $disableDoctypes = false, $disableExternals = false) {
|
||||||
$xml = new SimpleXMLElement($val);
|
// Check doctype
|
||||||
return self::recursiveXMLToArray($xml);
|
if($disableDoctypes && preg_match('/\<\!DOCTYPE.+]\>/', $val)) {
|
||||||
|
throw new InvalidArgumentException('XML Doctype parsing disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable external entity loading
|
||||||
|
if($disableExternals) $oldVal = libxml_disable_entity_loader($disableExternals);
|
||||||
|
try {
|
||||||
|
$xml = new SimpleXMLElement($val);
|
||||||
|
$result = self::recursiveXMLToArray($xml);
|
||||||
|
} catch(Exception $ex) {
|
||||||
|
if($disableExternals) libxml_disable_entity_loader($oldVal);
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
if($disableExternals) libxml_disable_entity_loader($oldVal);
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,7 +12,7 @@ information.
|
|||||||
|
|
||||||
All data tables in SilverStripe are defined as subclasses of [api:DataObject]. The [api:DataObject] class represents a
|
All data tables in SilverStripe are defined as subclasses of [api:DataObject]. The [api:DataObject] class represents a
|
||||||
single row in a database table, following the ["Active Record"](http://en.wikipedia.org/wiki/Active_record_pattern)
|
single row in a database table, following the ["Active Record"](http://en.wikipedia.org/wiki/Active_record_pattern)
|
||||||
design pattern. Database Columns are is defined as [Data Types](data_types_and_casting) in the static `$db` variable
|
design pattern. Database Columns are defined as [Data Types](data_types_and_casting) in the static `$db` variable
|
||||||
along with any [relationships](relations) defined as `$has_one`, `$has_many`, `$many_many` properties on the class.
|
along with any [relationships](relations) defined as `$has_one`, `$has_many`, `$many_many` properties on the class.
|
||||||
|
|
||||||
Let's look at a simple example:
|
Let's look at a simple example:
|
||||||
@ -401,7 +401,7 @@ Remove both Sam and Sig..
|
|||||||
'Surname' => 'Minnée',
|
'Surname' => 'Minnée',
|
||||||
));
|
));
|
||||||
|
|
||||||
And removing Sig and Sam with that are either age 17 or 74.
|
And removing Sig and Sam with that are either age 17 or 43.
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
$players = Player::get()->exclude(array(
|
$players = Player::get()->exclude(array(
|
||||||
@ -409,7 +409,7 @@ And removing Sig and Sam with that are either age 17 or 74.
|
|||||||
'Age' => array(17, 43)
|
'Age' => array(17, 43)
|
||||||
));
|
));
|
||||||
|
|
||||||
// SELECT * FROM Player WHERE ("FirstName" NOT IN ('Sam','Sig) OR "Age" NOT IN ('17', '74));
|
// SELECT * FROM Player WHERE ("FirstName" NOT IN ('Sam','Sig) OR "Age" NOT IN ('17', '43'));
|
||||||
|
|
||||||
You can use [SearchFilters](searchfilters) to add additional behavior to your `exclude` command.
|
You can use [SearchFilters](searchfilters) to add additional behavior to your `exclude` command.
|
||||||
|
|
||||||
@ -548,7 +548,7 @@ The data for the following classes would be stored across the following tables:
|
|||||||
- LastEdited: Datetime
|
- LastEdited: Datetime
|
||||||
- Title: Varchar
|
- Title: Varchar
|
||||||
- Content: Text
|
- Content: Text
|
||||||
NewsArticle:
|
NewsPage:
|
||||||
- ID: Int
|
- ID: Int
|
||||||
- Summary: Text
|
- Summary: Text
|
||||||
|
|
||||||
@ -558,7 +558,7 @@ Accessing the data is transparent to the developer.
|
|||||||
$news = NewsPage::get();
|
$news = NewsPage::get();
|
||||||
|
|
||||||
foreach($news as $article) {
|
foreach($news as $article) {
|
||||||
echo $news->Title;
|
echo $article->Title;
|
||||||
}
|
}
|
||||||
|
|
||||||
The way the ORM stores the data is this:
|
The way the ORM stores the data is this:
|
||||||
@ -575,7 +575,7 @@ example above, NewsSection didn't have its own data, so an extra table would be
|
|||||||
* In all the tables, ID is the primary key. A matching ID number is used for all parts of a particular record:
|
* In all the tables, ID is the primary key. A matching ID number is used for all parts of a particular record:
|
||||||
record #2 in Page refers to the same object as record #2 in `[api:SiteTree]`.
|
record #2 in Page refers to the same object as record #2 in `[api:SiteTree]`.
|
||||||
|
|
||||||
To retrieve a news article, SilverStripe joins the [api:SiteTree], [api:Page] and NewsArticle tables by their ID fields.
|
To retrieve a news article, SilverStripe joins the [api:SiteTree], [api:Page] and NewsPage tables by their ID fields.
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ summary: Add Indexes to your Data Model to optimize database queries.
|
|||||||
|
|
||||||
It is sometimes desirable to add indexes to your data model, whether to optimize queries or add a uniqueness constraint
|
It is sometimes desirable to add indexes to your data model, whether to optimize queries or add a uniqueness constraint
|
||||||
to a field. This is done through the `DataObject::$indexes` map, which maps index names to descriptor arrays that
|
to a field. This is done through the `DataObject::$indexes` map, which maps index names to descriptor arrays that
|
||||||
represent each index. There's several supported notations:
|
represent each index. There're several supported notations:
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
<?php
|
<?php
|
||||||
@ -19,7 +19,7 @@ represent each index. There's several supported notations:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
The `<index-name>` can be an an arbitrary identifier in order to allow for more than one index on a specific database
|
The `<index-name>` can be an arbitrary identifier in order to allow for more than one index on a specific database
|
||||||
column. The "advanced" notation supports more `<type>` notations. These vary between database drivers, but all of them
|
column. The "advanced" notation supports more `<type>` notations. These vary between database drivers, but all of them
|
||||||
support the following:
|
support the following:
|
||||||
|
|
||||||
@ -27,8 +27,8 @@ support the following:
|
|||||||
* `unique`: Index plus uniqueness constraint on the value
|
* `unique`: Index plus uniqueness constraint on the value
|
||||||
* `fulltext`: Fulltext content index
|
* `fulltext`: Fulltext content index
|
||||||
|
|
||||||
In order to use more database specific or complex index notations, we also support raw SQL for as a value in the
|
In order to use more database specific or complex index notations, we also support raw SQL as a value in the
|
||||||
`$indexes` definition. Keep in mind this will likely make your code less portable between databases.
|
`$indexes` definition. Keep in mind that using raw SQL is likely to make your code less portable between DBMSs.
|
||||||
|
|
||||||
**mysite/code/MyTestObject.php**
|
**mysite/code/MyTestObject.php**
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# Dynamic Default Values
|
# Dynamic Default Values
|
||||||
|
|
||||||
The [api:DataObject::$defaults] array allows you to specify simple static values to be the default value for when a
|
The [api:DataObject::$defaults] array allows you to specify simple static values to be the default values when a
|
||||||
record is created, but in many situations default values needs to be dynamically calculated. In order to do this, the
|
record is created, but in many situations default values need to be dynamically calculated. In order to do this, the
|
||||||
`[api:DataObject->populateDefaults()]` method will need to be overloaded.
|
[api:DataObject->populateDefaults()] method will need to be overloaded.
|
||||||
|
|
||||||
This method is called whenever a new record is instantiated, and you must be sure to call the method on the parent
|
This method is called whenever a new record is instantiated, and you must be sure to call the method on the parent
|
||||||
object!
|
object!
|
||||||
|
@ -39,7 +39,7 @@ routing.
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert" markdown="1">
|
<div class="alert" markdown="1">
|
||||||
Make sure that after you have modified the `routes.yml` file, that you clear your SilverStripe caches using `flush=1`.
|
Make sure that after you have modified the `routes.yml` file, that you clear your SilverStripe caches using `?flush=1`.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
**mysite/_config/routes.yml**
|
**mysite/_config/routes.yml**
|
||||||
@ -70,7 +70,7 @@ Action methods can return one of four main things:
|
|||||||
* an array. In this case the values in the array are available in the templates and the controller completes as usual by returning a [api:SS_HTTPResponse] with the body set to the current template.
|
* an array. In this case the values in the array are available in the templates and the controller completes as usual by returning a [api:SS_HTTPResponse] with the body set to the current template.
|
||||||
* `HTML`. SilverStripe will wrap the `HTML` into a `SS_HTTPResponse` and set the status code to 200.
|
* `HTML`. SilverStripe will wrap the `HTML` into a `SS_HTTPResponse` and set the status code to 200.
|
||||||
* an [api:SS_HTTPResponse] containing a manually defined `status code` and `body`.
|
* an [api:SS_HTTPResponse] containing a manually defined `status code` and `body`.
|
||||||
* an [api:SS_HTTPResponse_Exception]. A special type of response which indicates a error. By returning the exception, the execution pipeline can adapt and display any error handlers.
|
* an [api:SS_HTTPResponse_Exception]. A special type of response which indicates an error. By returning the exception, the execution pipeline can adapt and display any error handlers.
|
||||||
|
|
||||||
**mysite/code/controllers/TeamController.php**
|
**mysite/code/controllers/TeamController.php**
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ If a template of that name does not exist, then SilverStripe will fall back to t
|
|||||||
Controller actions can use `renderWith` to override this template selection process as in the previous example with
|
Controller actions can use `renderWith` to override this template selection process as in the previous example with
|
||||||
`htmlaction`. `MyCustomTemplate.ss` would be used rather than `TeamsController`.
|
`htmlaction`. `MyCustomTemplate.ss` would be used rather than `TeamsController`.
|
||||||
|
|
||||||
For more information about templates, inheritance and how to rendering into views, See the
|
For more information about templates, inheritance and how to render into views, See the
|
||||||
[Templates and Views](../templates) documentation.
|
[Templates and Views](../templates) documentation.
|
||||||
|
|
||||||
## Link
|
## Link
|
||||||
@ -154,7 +154,7 @@ Each controller should define a `Link()` method. This should be used to avoid ha
|
|||||||
**mysite/code/controllers/TeamController.php**
|
**mysite/code/controllers/TeamController.php**
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
public function Link($action = null) {
|
public function Link($action = null) {
|
||||||
return Controller::join_links('teams', $action);
|
return Controller::join_links('teams', $action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ In practice, this looks like:
|
|||||||
FormAction::create("doSayHello")->setTitle("Say hello")
|
FormAction::create("doSayHello")->setTitle("Say hello")
|
||||||
);
|
);
|
||||||
|
|
||||||
$required = new RequiredFields('Name')
|
$required = new RequiredFields('Name');
|
||||||
|
|
||||||
$form = new Form($this, 'HelloForm', $fields, $actions, $required);
|
$form = new Form($this, 'HelloForm', $fields, $actions, $required);
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ throughout the site. Out of the box this includes selecting the current site the
|
|||||||
|
|
||||||
<% with $SiteConfig %>
|
<% with $SiteConfig %>
|
||||||
$Title $AnotherField
|
$Title $AnotherField
|
||||||
<% end_loop %>
|
<% end_with %>
|
||||||
|
|
||||||
To access variables in the PHP:
|
To access variables in the PHP:
|
||||||
|
|
||||||
@ -61,10 +61,10 @@ Then activate the extension.
|
|||||||
|
|
||||||
<div class="notice" markdown="1">
|
<div class="notice" markdown="1">
|
||||||
After adding the class and the YAML change, make sure to rebuild your database by visiting http://yoursite.com/dev/build.
|
After adding the class and the YAML change, make sure to rebuild your database by visiting http://yoursite.com/dev/build.
|
||||||
You may also need to reload the screen with a `flush=1` i.e http://yoursite.com/admin/settings?flush=1.
|
You may also need to reload the screen with a `?flush=1` i.e http://yoursite.com/admin/settings?flush=1.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
You can define as many extensions for `SiteConfig` as you need. For example, if you're developing a module and what to
|
You can define as many extensions for `SiteConfig` as you need. For example, if you're developing a module and want to
|
||||||
provide the users a place to configure settings then the `SiteConfig` panel is the place to go it.
|
provide the users a place to configure settings then the `SiteConfig` panel is the place to go it.
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
@ -12,7 +12,7 @@ response and modify the session within a test.
|
|||||||
:::php
|
:::php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class HomePageTest extends SapphireTest {
|
class HomePageTest extends FunctionalTest {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test generation of the view
|
* Test generation of the view
|
||||||
@ -24,7 +24,7 @@ response and modify the session within a test.
|
|||||||
$this->assertEquals(200, $page->getStatusCode());
|
$this->assertEquals(200, $page->getStatusCode());
|
||||||
|
|
||||||
// We should see a login form
|
// We should see a login form
|
||||||
$login = $this->submitForm("#LoginForm", null, array(
|
$login = $this->submitForm("LoginFormID", null, array(
|
||||||
'Email' => 'test@test.com',
|
'Email' => 'test@test.com',
|
||||||
'Password' => 'wrongpassword'
|
'Password' => 'wrongpassword'
|
||||||
));
|
));
|
||||||
|
71
docs/en/04_Changelogs/3.1.11.md
Normal file
71
docs/en/04_Changelogs/3.1.11.md
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# 3.1.11
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
This release resolves a high level security issue in the SiteTree class, as well as
|
||||||
|
the CMS controller classes which act on these objects during creation.
|
||||||
|
|
||||||
|
This release also resolves an issue affecting GridField on sites running in
|
||||||
|
an environment with Suhosin enabled.
|
||||||
|
|
||||||
|
## Upgrading
|
||||||
|
|
||||||
|
### SiteTree::canCreate Permissions
|
||||||
|
|
||||||
|
Any user code which overrides the `SiteTree::canCreate` method should be investigated to
|
||||||
|
ensure it continues to work correctly. In particular, a second parameter may now be passed
|
||||||
|
to this method in order to determine if page creation is allowed in any given context, whether
|
||||||
|
it be at the root level, or as a child of a parent page.
|
||||||
|
|
||||||
|
The creation of pages at the root level is now corrected to follow the rules specified
|
||||||
|
by the SiteConfig, which in turn has been updated to ensure only valid CMS users are
|
||||||
|
granted this permission (when applicable).
|
||||||
|
|
||||||
|
The creation of pages beneath parent pages will now inherit from the ability to edit
|
||||||
|
this parent page.
|
||||||
|
|
||||||
|
User code which is not updated, but relies on the old implementation of SiteTree::canCreate will
|
||||||
|
now assume creation at the top level.
|
||||||
|
|
||||||
|
For example see the below code as an example
|
||||||
|
|
||||||
|
E.g.
|
||||||
|
|
||||||
|
:::php
|
||||||
|
<?php
|
||||||
|
class SingletonPage extends Page {
|
||||||
|
public function canCreate($member) {
|
||||||
|
if(static::get()->count()) return false;
|
||||||
|
|
||||||
|
$context = func_num_args() > 1 ? func_get_arg(1) : array();
|
||||||
|
return parent::canCreate($member, $context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
For more information on the reason for this change please see the security announcement below.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
* 2015-03-11 [3df41e1](https://github.com/silverstripe/silverstripe-cms/commit/3df41e1) Fix SiteTree / SiteConfig permissions (Damian Mooyman) - See announcement [ss-2015-008](http://www.silverstripe.org/software/download/security-releases/ss-2015-008-sitetree-creation-permission-vulnerability)
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
* 2015-03-09 [1770fab](https://github.com/silverstripe/sapphire/commit/1770fab) Fix gridfield generating invalid session keys (Damian Mooyman)
|
||||||
|
* 2015-03-05 [87adc44](https://github.com/silverstripe/sapphire/commit/87adc44) Fix serialised stateid exceeding request length (Damian Mooyman)
|
||||||
|
* 2015-03-04 [eb35f26](https://github.com/silverstripe/sapphire/commit/eb35f26) Corrected padding on non-sortable columns. (Sam Minnee)
|
||||||
|
* 2015-03-03 [6e0afd5](https://github.com/silverstripe/sapphire/commit/6e0afd5) Prevent unnecessary call to config system which doesn't exist yet (micmania1)
|
||||||
|
* 2015-03-03 [4709b90](https://github.com/silverstripe/sapphire/commit/4709b90) UploadField description alignment (Loz Calver)
|
||||||
|
* 2015-03-02 [f234301](https://github.com/silverstripe/sapphire/commit/f234301) DataQuery::applyRelation using incorrect foreign key (fixes #3954) (Loz Calver)
|
||||||
|
* 2015-03-02 [f9d493d](https://github.com/silverstripe/sapphire/commit/f9d493d) Fixes case insensitive search for postgres databases (Jean-Fabien Barrois)
|
||||||
|
* 2015-02-27 [4c5a07e](https://github.com/silverstripe/sapphire/commit/4c5a07e) Updated docs (Michael Strong)
|
||||||
|
* 2015-02-25 [3a7e24a](https://github.com/silverstripe/sapphire/commit/3a7e24a) Unable to access a list of all many_many_extraFields (Loz Calver)
|
||||||
|
* 2015-02-13 [998c055](https://github.com/silverstripe/sapphire/commit/998c055) Misleading error message in SSViewer (Loz Calver)
|
||||||
|
* 2015-02-10 [bbe2799](https://github.com/silverstripe/sapphire/commit/bbe2799) Use correct query when searching for items managed by a tree dropdown field #3173 (Jean-Fabien Barrois)
|
||||||
|
* 2015-01-13 [ab24ed3](https://github.com/silverstripe/sapphire/commit/ab24ed3) Use i18n_plural_name() instead of plural_name() (Elvinas L.)
|
||||||
|
* 2014-11-17 [a142ffd](https://github.com/silverstripe/silverstripe-cms/commit/a142ffd) VirtualPages use correct casting for 'virtual' database fields (Loz Calver)
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.11)
|
||||||
|
* [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.1.11)
|
||||||
|
* [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.1.11)
|
43
docs/en/04_Changelogs/3.1.12.md
Normal file
43
docs/en/04_Changelogs/3.1.12.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# 3.1.12
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
This security release resolves some XSS and an XML vulnerability in the Framework.
|
||||||
|
|
||||||
|
## Upgrading
|
||||||
|
|
||||||
|
If your code relies on `Convert::xml2array` there are some important things to consider with regards to
|
||||||
|
certain vulnerabilities. In this release additional options have been added to this method to assist
|
||||||
|
users in guarding against these risks, although each option has been turned off by default.
|
||||||
|
|
||||||
|
Please refer to http://phpsecurity.readthedocs.org/en/latest/Injection-Attacks.html#xml-external-entity-injection
|
||||||
|
on details of some of the specific reasons behind the need for these changes and how you can guard
|
||||||
|
against them in your code.
|
||||||
|
|
||||||
|
Specifically this method has these two new parameters:
|
||||||
|
|
||||||
|
* The `$disableDoctypes` parameter has been added to disallow parsing of XML content containing
|
||||||
|
a <!DOCTYPE > header, which may potentially contain unguarded or recursive entity definitions.
|
||||||
|
* The `$disableExternals` parameter allows XML parsing to ignore any externally referenced
|
||||||
|
dependency within the file, ensuring that injected XML is unable to invoke data from potentially
|
||||||
|
hazardous sources.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
* 2015-03-20 [ee9bddb](https://github.com/silverstripe/sapphire/commit/ee9bddb) Fix SS-2015-010 (Damian Mooyman) - See announcement [ss-2015-010](http://www.silverstripe.org/software/download/security-releases/ss-2015-010-xss-in-directorforce-redirect)
|
||||||
|
* 2015-03-20 [7f983c2](https://github.com/silverstripe/sapphire/commit/7f983c2) Fix SS-2014-017 (Damian Mooyman) - See announcement [ss-2014-017](http://www.silverstripe.org/software/download/security-releases/ss-2014-017-xml-quadratic-blowup-attack)
|
||||||
|
* 2015-03-20 [604c328](https://github.com/silverstripe/sapphire/commit/604c328) Fixed XSS vulnerability relating to rewrite_hash (Christopher Pitt) - See announcements [ss-2014-015](http://www.silverstripe.org/software/download/security-releases/ss-2014-015-ie-requests-not-properly-behaving-with-rewritehashlinks), [ss-2015-009](http://www.silverstripe.org/software/download/security-releases/ss-2015-009-xss-in-rewritten-hash-links)
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
* 2015-03-18 [b34c236](https://github.com/silverstripe/sapphire/commit/b34c236) Fix joins on tables containing "select" being mistaken for sub-selects Fix PHPDoc on SQLQuery::addFrom and SQLQuery::setFrom Fixes #3965 (Damian Mooyman)
|
||||||
|
* 2015-03-11 [a61c08d](https://github.com/silverstripe/sapphire/commit/a61c08d) Security::$default_message_set Config value unusable (Loz Calver)
|
||||||
|
* 2015-03-10 [9651889](https://github.com/silverstripe/sapphire/commit/9651889) Fix yaml generation to conform to version 1.1, accepted by transifex (Damian Mooyman)
|
||||||
|
* 2015-02-25 [f5f41b2](https://github.com/silverstripe/sapphire/commit/f5f41b2) Ensuring custom CMS validator uses Object->hasMethod() to respect extension decorator pattern. (Patrick Nelson)
|
||||||
|
* 2015-01-13 [9da7e90](https://github.com/silverstripe/silverstripe-cms/commit/9da7e90) . Missing translation entity (Elvinas L.)
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
* [framework](https://github.com/silverstripe/silverstripe-framework/releases/tag/3.1.12)
|
||||||
|
* [cms](https://github.com/silverstripe/silverstripe-cms/releases/tag/3.1.12)
|
||||||
|
* [installer](https://github.com/silverstripe/silverstripe-installer/releases/tag/3.1.12)
|
@ -188,6 +188,7 @@ class GridFieldDetailForm implements GridField_URLHandler {
|
|||||||
*/
|
*/
|
||||||
public function setItemEditFormCallback(Closure $cb) {
|
public function setItemEditFormCallback(Closure $cb) {
|
||||||
$this->itemEditFormCallback = $cb;
|
$this->itemEditFormCallback = $cb;
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -137,6 +137,12 @@
|
|||||||
onclick: function(e){
|
onclick: function(e){
|
||||||
var filterState='show'; //filterstate should equal current state.
|
var filterState='show'; //filterstate should equal current state.
|
||||||
|
|
||||||
|
// If the button is disabled, do nothing.
|
||||||
|
if (this.button('option', 'disabled')) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(this.hasClass('ss-gridfield-button-close') || !(this.closest('.ss-gridfield').hasClass('show-filter'))){
|
if(this.hasClass('ss-gridfield-button-close') || !(this.closest('.ss-gridfield').hasClass('show-filter'))){
|
||||||
filterState='hidden';
|
filterState='hidden';
|
||||||
}
|
}
|
||||||
@ -146,6 +152,34 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't allow users to submit empty values in grid field auto complete inputs.
|
||||||
|
*/
|
||||||
|
$('.ss-gridfield .add-existing-autocompleter').entwine({
|
||||||
|
onbuttoncreate: function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.toggleDisabled();
|
||||||
|
|
||||||
|
this.find('input[type="text"]').on('keyup', function () {
|
||||||
|
self.toggleDisabled();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onunmatch: function () {
|
||||||
|
this.find('input[type="text"]').off('keyup');
|
||||||
|
},
|
||||||
|
toggleDisabled: function () {
|
||||||
|
var $button = this.find('.ss-ui-button'),
|
||||||
|
$input = this.find('input[type="text"]'),
|
||||||
|
inputHasValue = $input.val() !== '',
|
||||||
|
buttonDisabled = $button.is(':disabled');
|
||||||
|
|
||||||
|
if ((inputHasValue && buttonDisabled) || (!inputHasValue && !buttonDisabled)) {
|
||||||
|
$button.button("option", "disabled", !buttonDisabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Covers both tabular delete button, and the button on the detail form
|
// Covers both tabular delete button, and the button on the detail form
|
||||||
$('.ss-gridfield .col-buttons .action.gridfield-button-delete, .cms-edit-form .Actions button.action.action-delete').entwine({
|
$('.ss-gridfield .col-buttons .action.gridfield-button-delete, .cms-edit-form .Actions button.action.action-delete').entwine({
|
||||||
onclick: function(e){
|
onclick: function(e){
|
||||||
|
@ -271,8 +271,13 @@ abstract class SQLConditionalExpression extends SQLExpression {
|
|||||||
$filter = "(" . implode(") AND (", $join['filter']) . ")";
|
$filter = "(" . implode(") AND (", $join['filter']) . ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
$table = strpos(strtoupper($join['table']), 'SELECT') ? $join['table'] : "\"" . $join['table'] . "\"";
|
// Ensure tables are quoted, unless the table is actually a sub-select
|
||||||
$aliasClause = ($alias != $join['table']) ? " AS \"$alias\"" : "";
|
$table = preg_match('/\bSELECT\b/i', $join['table'])
|
||||||
|
? $join['table']
|
||||||
|
: "\"{$join['table']}\"";
|
||||||
|
$aliasClause = ($alias != $join['table'])
|
||||||
|
? " AS \"{$alias}\""
|
||||||
|
: "";
|
||||||
$joins[$alias] = strtoupper($join['type']) . " JOIN " . $table . "$aliasClause ON $filter";
|
$joins[$alias] = strtoupper($join['type']) . " JOIN " . $table . "$aliasClause ON $filter";
|
||||||
if(!empty($join['parameters'])) {
|
if(!empty($join['parameters'])) {
|
||||||
$parameters = array_merge($parameters, $join['parameters']);
|
$parameters = array_merge($parameters, $join['parameters']);
|
||||||
|
@ -50,9 +50,19 @@ class BasicAuth {
|
|||||||
$isRunningTests = (class_exists('SapphireTest', false) && SapphireTest::is_running_test());
|
$isRunningTests = (class_exists('SapphireTest', false) && SapphireTest::is_running_test());
|
||||||
if(!Security::database_is_ready() || (Director::is_cli() && !$isRunningTests)) return true;
|
if(!Security::database_is_ready() || (Director::is_cli() && !$isRunningTests)) return true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enable HTTP Basic authentication workaround for PHP running in CGI mode with Apache
|
||||||
|
* Depending on server configuration the auth header may be in HTTP_AUTHORIZATION or
|
||||||
|
* REDIRECT_HTTP_AUTHORIZATION
|
||||||
|
*
|
||||||
|
* The follow rewrite rule must be in the sites .htaccess file to enable this workaround
|
||||||
|
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||||
|
*/
|
||||||
|
$authHeader = (isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] :
|
||||||
|
(isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) ? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] : null));
|
||||||
$matches = array();
|
$matches = array();
|
||||||
if (isset($_SERVER['HTTP_AUTHORIZATION']) &&
|
if ($authHeader &&
|
||||||
preg_match('/Basic\s+(.*)$/i', $_SERVER['HTTP_AUTHORIZATION'], $matches)) {
|
preg_match('/Basic\s+(.*)$/i', $authHeader, $matches)) {
|
||||||
list($name, $password) = explode(':', base64_decode($matches[1]));
|
list($name, $password) = explode(':', base64_decode($matches[1]));
|
||||||
$_SERVER['PHP_AUTH_USER'] = strip_tags($name);
|
$_SERVER['PHP_AUTH_USER'] = strip_tags($name);
|
||||||
$_SERVER['PHP_AUTH_PW'] = strip_tags($password);
|
$_SERVER['PHP_AUTH_PW'] = strip_tags($password);
|
||||||
|
@ -514,39 +514,44 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
|||||||
// Don't bother trying this multiple times
|
// Don't bother trying this multiple times
|
||||||
self::$_already_tried_to_auto_log_in = true;
|
self::$_already_tried_to_auto_log_in = true;
|
||||||
|
|
||||||
if(strpos(Cookie::get('alc_enc'), ':') && !Session::get("loggedInAs")) {
|
if(strpos(Cookie::get('alc_enc'), ':') === false
|
||||||
list($uid, $token) = explode(':', Cookie::get('alc_enc'), 2);
|
|| Session::get("loggedInAs")
|
||||||
|
|| !Security::database_is_ready()
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$member = DataObject::get_by_id("Member", $uid);
|
list($uid, $token) = explode(':', Cookie::get('alc_enc'), 2);
|
||||||
|
|
||||||
// check if autologin token matches
|
$member = DataObject::get_by_id("Member", $uid);
|
||||||
if($member) {
|
|
||||||
$hash = $member->encryptWithUserSettings($token);
|
// check if autologin token matches
|
||||||
if(!$member->RememberLoginToken || $member->RememberLoginToken !== $hash) {
|
if($member) {
|
||||||
$member = null;
|
$hash = $member->encryptWithUserSettings($token);
|
||||||
}
|
if(!$member->RememberLoginToken || $member->RememberLoginToken !== $hash) {
|
||||||
|
$member = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($member) {
|
||||||
|
self::session_regenerate_id();
|
||||||
|
Session::set("loggedInAs", $member->ID);
|
||||||
|
// This lets apache rules detect whether the user has logged in
|
||||||
|
if(Member::config()->login_marker_cookie) {
|
||||||
|
Cookie::set(Member::config()->login_marker_cookie, 1, 0, null, null, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($member) {
|
$generator = new RandomGenerator();
|
||||||
self::session_regenerate_id();
|
$token = $generator->randomToken('sha1');
|
||||||
Session::set("loggedInAs", $member->ID);
|
$hash = $member->encryptWithUserSettings($token);
|
||||||
// This lets apache rules detect whether the user has logged in
|
$member->RememberLoginToken = $hash;
|
||||||
if(Member::config()->login_marker_cookie) {
|
Cookie::set('alc_enc', $member->ID . ':' . $token, 90, null, null, false, true);
|
||||||
Cookie::set(Member::config()->login_marker_cookie, 1, 0, null, null, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$generator = new RandomGenerator();
|
$member->NumVisit++;
|
||||||
$token = $generator->randomToken('sha1');
|
$member->write();
|
||||||
$hash = $member->encryptWithUserSettings($token);
|
|
||||||
$member->RememberLoginToken = $hash;
|
|
||||||
Cookie::set('alc_enc', $member->ID . ':' . $token, 90, null, null, false, true);
|
|
||||||
|
|
||||||
$member->NumVisit++;
|
// Audit logging hook
|
||||||
$member->write();
|
$member->extend('memberAutoLoggedIn');
|
||||||
|
|
||||||
// Audit logging hook
|
|
||||||
$member->extend('memberAutoLoggedIn');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,9 +97,10 @@ class Security extends Controller implements TemplateGlobalProvider {
|
|||||||
/**
|
/**
|
||||||
* Default message set used in permission failures.
|
* Default message set used in permission failures.
|
||||||
*
|
*
|
||||||
|
* @config
|
||||||
* @var array|string
|
* @var array|string
|
||||||
*/
|
*/
|
||||||
private static $default_message_set = '';
|
private static $default_message_set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Random secure token, can be used as a crypto key internally.
|
* Random secure token, can be used as a crypto key internally.
|
||||||
@ -198,9 +199,6 @@ class Security extends Controller implements TemplateGlobalProvider {
|
|||||||
* If you pass an array, you can use the
|
* If you pass an array, you can use the
|
||||||
* following keys:
|
* following keys:
|
||||||
* - default: The default message
|
* - default: The default message
|
||||||
* - logInAgain: The message to show
|
|
||||||
* if the user has just
|
|
||||||
* logged out and the
|
|
||||||
* - alreadyLoggedIn: The message to
|
* - alreadyLoggedIn: The message to
|
||||||
* show if the user
|
* show if the user
|
||||||
* is already logged
|
* is already logged
|
||||||
@ -231,8 +229,8 @@ class Security extends Controller implements TemplateGlobalProvider {
|
|||||||
} else {
|
} else {
|
||||||
// Prepare the messageSet provided
|
// Prepare the messageSet provided
|
||||||
if(!$messageSet) {
|
if(!$messageSet) {
|
||||||
if(self::$default_message_set) {
|
if($configMessageSet = static::config()->get('default_message_set')) {
|
||||||
$messageSet = self::$default_message_set;
|
$messageSet = $configMessageSet;
|
||||||
} else {
|
} else {
|
||||||
$messageSet = array(
|
$messageSet = array(
|
||||||
'default' => _t(
|
'default' => _t(
|
||||||
@ -246,11 +244,6 @@ class Security extends Controller implements TemplateGlobalProvider {
|
|||||||
. "can access that page, you can log in again below.",
|
. "can access that page, you can log in again below.",
|
||||||
|
|
||||||
"%s will be replaced with a link to log in."
|
"%s will be replaced with a link to log in."
|
||||||
),
|
|
||||||
'logInAgain' => _t(
|
|
||||||
'Security.LOGGEDOUT',
|
|
||||||
"You have been logged out. If you would like to log in again, enter "
|
|
||||||
. "your credentials below."
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -97,6 +97,16 @@ class DirectorTest extends SapphireTest {
|
|||||||
$_SERVER['REQUEST_URI'] = "$rootURL/mysite/sub-page/";
|
$_SERVER['REQUEST_URI'] = "$rootURL/mysite/sub-page/";
|
||||||
Config::inst()->update('Director', 'alternate_base_url', '/mysite/');
|
Config::inst()->update('Director', 'alternate_base_url', '/mysite/');
|
||||||
|
|
||||||
|
//test empty URL
|
||||||
|
$this->assertEquals("$rootURL/mysite/sub-page/", Director::absoluteURL(''));
|
||||||
|
|
||||||
|
//test absolute - /
|
||||||
|
$this->assertEquals("$rootURL/", Director::absoluteURL('/'));
|
||||||
|
|
||||||
|
//test relative
|
||||||
|
$this->assertEquals("$rootURL/mysite/sub-page/", Director::absoluteURL('./'));
|
||||||
|
$this->assertEquals("$rootURL/mysite/sub-page/", Director::absoluteURL('.'));
|
||||||
|
|
||||||
// Test already absolute url
|
// Test already absolute url
|
||||||
$this->assertEquals($rootURL, Director::absoluteURL($rootURL));
|
$this->assertEquals($rootURL, Director::absoluteURL($rootURL));
|
||||||
$this->assertEquals($rootURL, Director::absoluteURL($rootURL, true));
|
$this->assertEquals($rootURL, Director::absoluteURL($rootURL, true));
|
||||||
@ -135,8 +145,10 @@ class DirectorTest extends SapphireTest {
|
|||||||
|
|
||||||
// absolute base URLs - you should end them in a /
|
// absolute base URLs - you should end them in a /
|
||||||
Config::inst()->update('Director', 'alternate_base_url', 'http://www.example.org/');
|
Config::inst()->update('Director', 'alternate_base_url', 'http://www.example.org/');
|
||||||
|
$_SERVER['REQUEST_URI'] = "http://www.example.org/";
|
||||||
$this->assertEquals('http://www.example.org/', Director::baseURL());
|
$this->assertEquals('http://www.example.org/', Director::baseURL());
|
||||||
$this->assertEquals('http://www.example.org/', Director::absoluteBaseURL());
|
$this->assertEquals('http://www.example.org/', Director::absoluteBaseURL());
|
||||||
|
$this->assertEquals('http://www.example.org/', Director::absoluteURL(''));
|
||||||
$this->assertEquals('http://www.example.org/subfolder/test', Director::absoluteURL('subfolder/test'));
|
$this->assertEquals('http://www.example.org/subfolder/test', Director::absoluteURL('subfolder/test'));
|
||||||
|
|
||||||
// Setting it to false restores functionality
|
// Setting it to false restores functionality
|
||||||
|
@ -160,6 +160,26 @@ class HTTPTest extends FunctionalTest {
|
|||||||
*/
|
*/
|
||||||
public function testAbsoluteURLsAttributes() {
|
public function testAbsoluteURLsAttributes() {
|
||||||
$this->withBaseURL('http://www.silverstripe.org/', function($test){
|
$this->withBaseURL('http://www.silverstripe.org/', function($test){
|
||||||
|
//empty links
|
||||||
|
$test->assertEquals(
|
||||||
|
'<a href="http://www.silverstripe.org/">test</a>',
|
||||||
|
HTTP::absoluteURLs('<a href="">test</a>')
|
||||||
|
);
|
||||||
|
|
||||||
|
$test->assertEquals(
|
||||||
|
'<a href="http://www.silverstripe.org/">test</a>',
|
||||||
|
HTTP::absoluteURLs('<a href="/">test</a>')
|
||||||
|
);
|
||||||
|
|
||||||
|
//relative
|
||||||
|
$test->assertEquals(
|
||||||
|
'<a href="http://www.silverstripe.org/">test</a>',
|
||||||
|
HTTP::absoluteURLs('<a href="./">test</a>')
|
||||||
|
);
|
||||||
|
$test->assertEquals(
|
||||||
|
'<a href="http://www.silverstripe.org/">test</a>',
|
||||||
|
HTTP::absoluteURLs('<a href=".">test</a>')
|
||||||
|
);
|
||||||
|
|
||||||
// links
|
// links
|
||||||
$test->assertEquals(
|
$test->assertEquals(
|
||||||
|
@ -280,4 +280,55 @@ PHP
|
|||||||
Convert::raw2json($value)
|
Convert::raw2json($value)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testXML2Array() {
|
||||||
|
// Ensure an XML file at risk of entity expansion can be avoided safely
|
||||||
|
$inputXML = <<<XML
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE results [<!ENTITY long "SOME_SUPER_LONG_STRING">]>
|
||||||
|
<results>
|
||||||
|
<result>Now include &long; lots of times to expand the in-memory size of this XML structure</result>
|
||||||
|
<result>&long;&long;&long;</result>
|
||||||
|
</results>
|
||||||
|
XML
|
||||||
|
;
|
||||||
|
try {
|
||||||
|
Convert::xml2array($inputXML, true);
|
||||||
|
} catch(Exception $ex) {}
|
||||||
|
$this->assertTrue(
|
||||||
|
isset($ex)
|
||||||
|
&& $ex instanceof InvalidArgumentException
|
||||||
|
&& $ex->getMessage() === 'XML Doctype parsing disabled'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test without doctype validation
|
||||||
|
$expected = array(
|
||||||
|
'result' => array(
|
||||||
|
"Now include SOME_SUPER_LONG_STRING lots of times to expand the in-memory size of this XML structure",
|
||||||
|
array(
|
||||||
|
'long' => array(
|
||||||
|
array(
|
||||||
|
'long' => 'SOME_SUPER_LONG_STRING'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'long' => 'SOME_SUPER_LONG_STRING'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'long' => 'SOME_SUPER_LONG_STRING'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$result = Convert::xml2array($inputXML, false, true);
|
||||||
|
$this->assertEquals(
|
||||||
|
$expected,
|
||||||
|
$result
|
||||||
|
);
|
||||||
|
$result = Convert::xml2array($inputXML, false, false);
|
||||||
|
$this->assertEquals(
|
||||||
|
$expected,
|
||||||
|
$result
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -357,23 +357,34 @@ class SQLQueryTest extends SapphireTest {
|
|||||||
|
|
||||||
public function testJoinSubSelect() {
|
public function testJoinSubSelect() {
|
||||||
|
|
||||||
|
// Test sub-select works
|
||||||
$query = new SQLQuery();
|
$query = new SQLQuery();
|
||||||
$query->setFrom('MyTable');
|
$query->setFrom('"MyTable"');
|
||||||
$query->addInnerJoin('(SELECT * FROM MyOtherTable)',
|
$query->addInnerJoin('(SELECT * FROM "MyOtherTable")',
|
||||||
'Mot.MyTableID = MyTable.ID', 'Mot');
|
'"Mot"."MyTableID" = "MyTable"."ID"', 'Mot');
|
||||||
$query->addLeftJoin('(SELECT MyLastTable.MyOtherTableID, COUNT(1) as MyLastTableCount FROM MyLastTable '
|
$query->addLeftJoin('(SELECT "MyLastTable"."MyOtherTableID", COUNT(1) as "MyLastTableCount" '
|
||||||
. 'GROUP BY MyOtherTableID)',
|
. 'FROM "MyLastTable" GROUP BY "MyOtherTableID")',
|
||||||
'Mlt.MyOtherTableID = Mot.ID', 'Mlt');
|
'"Mlt"."MyOtherTableID" = "Mot"."ID"', 'Mlt');
|
||||||
$query->setOrderBy('COALESCE(Mlt.MyLastTableCount, 0) DESC');
|
$query->setOrderBy('COALESCE("Mlt"."MyLastTableCount", 0) DESC');
|
||||||
|
|
||||||
$this->assertSQLEquals('SELECT *, COALESCE(Mlt.MyLastTableCount, 0) AS "_SortColumn0" FROM MyTable '.
|
$this->assertSQLEquals('SELECT *, COALESCE("Mlt"."MyLastTableCount", 0) AS "_SortColumn0" FROM "MyTable" '.
|
||||||
'INNER JOIN (SELECT * FROM MyOtherTable) AS "Mot" ON Mot.MyTableID = MyTable.ID ' .
|
'INNER JOIN (SELECT * FROM "MyOtherTable") AS "Mot" ON "Mot"."MyTableID" = "MyTable"."ID" ' .
|
||||||
'LEFT JOIN (SELECT MyLastTable.MyOtherTableID, COUNT(1) as MyLastTableCount FROM MyLastTable '
|
'LEFT JOIN (SELECT "MyLastTable"."MyOtherTableID", COUNT(1) as "MyLastTableCount" FROM "MyLastTable" '
|
||||||
. 'GROUP BY MyOtherTableID) AS "Mlt" ON Mlt.MyOtherTableID = Mot.ID ' .
|
. 'GROUP BY "MyOtherTableID") AS "Mlt" ON "Mlt"."MyOtherTableID" = "Mot"."ID" ' .
|
||||||
'ORDER BY "_SortColumn0" DESC',
|
'ORDER BY "_SortColumn0" DESC',
|
||||||
$query->sql($parameters)
|
$query->sql($parameters)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Test that table names do not get mistakenly identified as sub-selects
|
||||||
|
$query = new SQLQuery();
|
||||||
|
$query->setFrom('"MyTable"');
|
||||||
|
$query->addInnerJoin('NewsArticleSelected', '"News"."MyTableID" = "MyTable"."ID"', 'News');
|
||||||
|
$this->assertSQLEquals(
|
||||||
|
'SELECT * FROM "MyTable" INNER JOIN "NewsArticleSelected" AS "News" ON '.
|
||||||
|
'"News"."MyTableID" = "MyTable"."ID"',
|
||||||
|
$query->sql()
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetWhereAny() {
|
public function testSetWhereAny() {
|
||||||
|
@ -74,6 +74,47 @@ class SecurityTest extends FunctionalTest {
|
|||||||
$this->autoFollowRedirection = true;
|
$this->autoFollowRedirection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testPermissionFailureSetsCorrectFormMessages() {
|
||||||
|
Config::nest();
|
||||||
|
|
||||||
|
// Controller that doesn't attempt redirections
|
||||||
|
$controller = new SecurityTest_NullController();
|
||||||
|
$controller->response = new SS_HTTPResponse();
|
||||||
|
|
||||||
|
Security::permissionFailure($controller, array('default' => 'Oops, not allowed'));
|
||||||
|
$this->assertEquals('Oops, not allowed', Session::get('Security.Message.message'));
|
||||||
|
|
||||||
|
// Test that config values are used correctly
|
||||||
|
Config::inst()->update('Security', 'default_message_set', 'stringvalue');
|
||||||
|
Security::permissionFailure($controller);
|
||||||
|
$this->assertEquals('stringvalue', Session::get('Security.Message.message'),
|
||||||
|
'Default permission failure message value was not present');
|
||||||
|
|
||||||
|
Config::inst()->remove('Security', 'default_message_set');
|
||||||
|
Config::inst()->update('Security', 'default_message_set', array('default' => 'arrayvalue'));
|
||||||
|
Security::permissionFailure($controller);
|
||||||
|
$this->assertEquals('arrayvalue', Session::get('Security.Message.message'),
|
||||||
|
'Default permission failure message value was not present');
|
||||||
|
|
||||||
|
// Test that non-default messages work.
|
||||||
|
// NOTE: we inspect the response body here as the session message has already
|
||||||
|
// been fetched and output as part of it, so has been removed from the session
|
||||||
|
$this->logInWithPermission('EDITOR');
|
||||||
|
|
||||||
|
Config::inst()->update('Security', 'default_message_set',
|
||||||
|
array('default' => 'default', 'alreadyLoggedIn' => 'You are already logged in!'));
|
||||||
|
Security::permissionFailure($controller);
|
||||||
|
$this->assertContains('You are already logged in!', $controller->response->getBody(),
|
||||||
|
'Custom permission failure message was ignored');
|
||||||
|
|
||||||
|
Security::permissionFailure($controller,
|
||||||
|
array('default' => 'default', 'alreadyLoggedIn' => 'One-off failure message'));
|
||||||
|
$this->assertContains('One-off failure message', $controller->response->getBody(),
|
||||||
|
"Message set passed to Security::permissionFailure() didn't override Config values");
|
||||||
|
|
||||||
|
Config::unnest();
|
||||||
|
}
|
||||||
|
|
||||||
public function testLogInAsSomeoneElse() {
|
public function testLogInAsSomeoneElse() {
|
||||||
$member = DataObject::get_one('Member');
|
$member = DataObject::get_one('Member');
|
||||||
|
|
||||||
@ -517,3 +558,11 @@ class SecurityTest_SecuredController extends Controller implements TestOnly {
|
|||||||
return 'Success';
|
return 'Success';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SecurityTest_NullController extends Controller implements TestOnly {
|
||||||
|
|
||||||
|
public function redirect($url, $code = 302) {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -1117,6 +1117,8 @@ after')
|
|||||||
$orig = Config::inst()->get('SSViewer', 'rewrite_hash_links');
|
$orig = Config::inst()->get('SSViewer', 'rewrite_hash_links');
|
||||||
Config::inst()->update('SSViewer', 'rewrite_hash_links', true);
|
Config::inst()->update('SSViewer', 'rewrite_hash_links', true);
|
||||||
|
|
||||||
|
$_SERVER['REQUEST_URI'] = 'http://path/to/file?foo"onclick="alert(\'xss\')""';
|
||||||
|
|
||||||
// Emulate SSViewer::process()
|
// Emulate SSViewer::process()
|
||||||
$base = Convert::raw2att($_SERVER['REQUEST_URI']);
|
$base = Convert::raw2att($_SERVER['REQUEST_URI']);
|
||||||
|
|
||||||
@ -1127,6 +1129,8 @@ after')
|
|||||||
<html>
|
<html>
|
||||||
<head><% base_tag %></head>
|
<head><% base_tag %></head>
|
||||||
<body>
|
<body>
|
||||||
|
<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>
|
||||||
|
$ExternalInsertedLink
|
||||||
<a class="inline" href="#anchor">InlineLink</a>
|
<a class="inline" href="#anchor">InlineLink</a>
|
||||||
$InsertedLink
|
$InsertedLink
|
||||||
<svg><use xlink:href="#sprite"></use></svg>
|
<svg><use xlink:href="#sprite"></use></svg>
|
||||||
@ -1135,15 +1139,24 @@ after')
|
|||||||
$tmpl = new SSViewer($tmplFile);
|
$tmpl = new SSViewer($tmplFile);
|
||||||
$obj = new ViewableData();
|
$obj = new ViewableData();
|
||||||
$obj->InsertedLink = '<a class="inserted" href="#anchor">InsertedLink</a>';
|
$obj->InsertedLink = '<a class="inserted" href="#anchor">InsertedLink</a>';
|
||||||
|
$obj->ExternalInsertedLink = '<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>';
|
||||||
$result = $tmpl->process($obj);
|
$result = $tmpl->process($obj);
|
||||||
$this->assertContains(
|
$this->assertContains(
|
||||||
'<a class="inserted" href="' . $base . '#anchor">InsertedLink</a>',
|
'<a class="inserted" href="' . $base . '#anchor">InsertedLink</a>',
|
||||||
$result
|
$result
|
||||||
);
|
);
|
||||||
|
$this->assertContains(
|
||||||
|
'<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>',
|
||||||
|
$result
|
||||||
|
);
|
||||||
$this->assertContains(
|
$this->assertContains(
|
||||||
'<a class="inline" href="' . $base . '#anchor">InlineLink</a>',
|
'<a class="inline" href="' . $base . '#anchor">InlineLink</a>',
|
||||||
$result
|
$result
|
||||||
);
|
);
|
||||||
|
$this->assertContains(
|
||||||
|
'<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>',
|
||||||
|
$result
|
||||||
|
);
|
||||||
$this->assertContains(
|
$this->assertContains(
|
||||||
'<svg><use xlink:href="#sprite"></use></svg>',
|
'<svg><use xlink:href="#sprite"></use></svg>',
|
||||||
$result,
|
$result,
|
||||||
@ -1176,7 +1189,7 @@ after')
|
|||||||
$obj->InsertedLink = '<a class="inserted" href="#anchor">InsertedLink</a>';
|
$obj->InsertedLink = '<a class="inserted" href="#anchor">InsertedLink</a>';
|
||||||
$result = $tmpl->process($obj);
|
$result = $tmpl->process($obj);
|
||||||
$this->assertContains(
|
$this->assertContains(
|
||||||
'<a class="inserted" href="<?php echo strip_tags(',
|
'<a class="inserted" href="<?php echo Convert::raw2att(',
|
||||||
$result
|
$result
|
||||||
);
|
);
|
||||||
// TODO Fix inline links in PHP mode
|
// TODO Fix inline links in PHP mode
|
||||||
|
@ -4675,7 +4675,7 @@ class SSTemplateParser extends Parser implements TemplateParser {
|
|||||||
$text = preg_replace(
|
$text = preg_replace(
|
||||||
'/(<a[^>]+href *= *)"#/i',
|
'/(<a[^>]+href *= *)"#/i',
|
||||||
'\\1"\' . (Config::inst()->get(\'SSViewer\', \'rewrite_hash_links\') ?' .
|
'\\1"\' . (Config::inst()->get(\'SSViewer\', \'rewrite_hash_links\') ?' .
|
||||||
' strip_tags( $_SERVER[\'REQUEST_URI\'] ) : "") .
|
' Convert::raw2att( $_SERVER[\'REQUEST_URI\'] ) : "") .
|
||||||
\'#',
|
\'#',
|
||||||
$text
|
$text
|
||||||
);
|
);
|
||||||
|
@ -1129,7 +1129,7 @@ class SSTemplateParser extends Parser implements TemplateParser {
|
|||||||
$text = preg_replace(
|
$text = preg_replace(
|
||||||
'/(<a[^>]+href *= *)"#/i',
|
'/(<a[^>]+href *= *)"#/i',
|
||||||
'\\1"\' . (Config::inst()->get(\'SSViewer\', \'rewrite_hash_links\') ?' .
|
'\\1"\' . (Config::inst()->get(\'SSViewer\', \'rewrite_hash_links\') ?' .
|
||||||
' strip_tags( $_SERVER[\'REQUEST_URI\'] ) : "") .
|
' Convert::raw2att( $_SERVER[\'REQUEST_URI\'] ) : "") .
|
||||||
\'#',
|
\'#',
|
||||||
$text
|
$text
|
||||||
);
|
);
|
||||||
|
@ -1115,9 +1115,9 @@ class SSViewer implements Flushable {
|
|||||||
if($this->rewriteHashlinks && $rewrite) {
|
if($this->rewriteHashlinks && $rewrite) {
|
||||||
if(strpos($output, '<base') !== false) {
|
if(strpos($output, '<base') !== false) {
|
||||||
if($rewrite === 'php') {
|
if($rewrite === 'php') {
|
||||||
$thisURLRelativeToBase = "<?php echo strip_tags(\$_SERVER['REQUEST_URI']); ?>";
|
$thisURLRelativeToBase = "<?php echo Convert::raw2att(\$_SERVER['REQUEST_URI']); ?>";
|
||||||
} else {
|
} else {
|
||||||
$thisURLRelativeToBase = strip_tags($_SERVER['REQUEST_URI']);
|
$thisURLRelativeToBase = Convert::raw2att($_SERVER['REQUEST_URI']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$output = preg_replace('/(<a[^>]+href *= *)"#/i', '\\1"' . $thisURLRelativeToBase . '#', $output);
|
$output = preg_replace('/(<a[^>]+href *= *)"#/i', '\\1"' . $thisURLRelativeToBase . '#', $output);
|
||||||
|
Loading…
Reference in New Issue
Block a user