mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge remote-tracking branch 'security/3.5.4' into 3.6.0
This commit is contained in:
commit
cda7e8dc39
@ -726,16 +726,17 @@ abstract class Object {
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $arguments) {
|
||||
$class = get_class($this);
|
||||
// If the method cache was cleared by an an Object::add_extension() / Object::remove_extension()
|
||||
// call, then we should rebuild it.
|
||||
if(empty(self::$extra_methods[get_class($this)])) {
|
||||
if(empty(self::$extra_methods[$class])) {
|
||||
$this->defineMethods();
|
||||
}
|
||||
|
||||
$method = strtolower($method);
|
||||
|
||||
if(isset(self::$extra_methods[$this->class][$method])) {
|
||||
$config = self::$extra_methods[$this->class][$method];
|
||||
if(isset(self::$extra_methods[$class][$method])) {
|
||||
$config = self::$extra_methods[$class][$method];
|
||||
|
||||
switch(true) {
|
||||
case isset($config['property']) :
|
||||
@ -752,11 +753,11 @@ abstract class Object {
|
||||
|
||||
if($this->destroyed) {
|
||||
throw new Exception (
|
||||
"Object->__call(): attempt to call $method on a destroyed $this->class object"
|
||||
"Object->__call(): attempt to call $method on a destroyed $class object"
|
||||
);
|
||||
} else {
|
||||
throw new Exception (
|
||||
"Object->__call(): $this->class cannot pass control to $config[property]($config[index])."
|
||||
"Object->__call(): $class cannot pass control to $config[property]($config[index])."
|
||||
. ' Perhaps this object was mistakenly destroyed?'
|
||||
);
|
||||
}
|
||||
@ -770,13 +771,12 @@ abstract class Object {
|
||||
|
||||
default :
|
||||
throw new Exception (
|
||||
"Object->__call(): extra method $method is invalid on $this->class:"
|
||||
"Object->__call(): extra method $method is invalid on $class:"
|
||||
. var_export($config, true)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Please do not change the exception code number below.
|
||||
$class = get_class($this);
|
||||
throw new Exception("Object->__call(): the method '$method' does not exist on '$class', or the method is not public.", 2175);
|
||||
}
|
||||
}
|
||||
@ -793,7 +793,7 @@ abstract class Object {
|
||||
* @return bool
|
||||
*/
|
||||
public function hasMethod($method) {
|
||||
return method_exists($this, $method) || isset(self::$extra_methods[$this->class][strtolower($method)]);
|
||||
return method_exists($this, $method) || isset(self::$extra_methods[get_class($this)][strtolower($method)]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -803,14 +803,15 @@ abstract class Object {
|
||||
* @return array
|
||||
*/
|
||||
public function allMethodNames($custom = false) {
|
||||
if(!isset(self::$built_in_methods[$this->class])) {
|
||||
self::$built_in_methods[$this->class] = array_map('strtolower', get_class_methods($this));
|
||||
$class = get_class($this);
|
||||
if(!isset(self::$built_in_methods[$class])) {
|
||||
self::$built_in_methods[$class] = array_map('strtolower', get_class_methods($this));
|
||||
}
|
||||
|
||||
if($custom && isset(self::$extra_methods[$this->class])) {
|
||||
return array_merge(self::$built_in_methods[$this->class], array_keys(self::$extra_methods[$this->class]));
|
||||
if($custom && isset(self::$extra_methods[$class])) {
|
||||
return array_merge(self::$built_in_methods[$class], array_keys(self::$extra_methods[$class]));
|
||||
} else {
|
||||
return self::$built_in_methods[$this->class];
|
||||
return self::$built_in_methods[$class];
|
||||
}
|
||||
}
|
||||
|
||||
@ -826,11 +827,12 @@ abstract class Object {
|
||||
$this->addMethodsFrom('extension_instances', $key);
|
||||
}
|
||||
|
||||
if(isset($_REQUEST['debugmethods']) && isset(self::$built_in_methods[$this->class])) {
|
||||
$class = get_class($this);
|
||||
if(isset($_REQUEST['debugmethods']) && isset(self::$built_in_methods[$class])) {
|
||||
Debug::require_developer_login();
|
||||
|
||||
echo '<h2>Methods defined on ' . $this->class . '</h2><ul>';
|
||||
foreach(self::$built_in_methods[$this->class] as $method) {
|
||||
echo "<h2>Methods defined on $class</h2><ul>";
|
||||
foreach(self::$built_in_methods[$class] as $method) {
|
||||
echo "<li>$method</li>";
|
||||
}
|
||||
echo '</ul>';
|
||||
@ -863,11 +865,12 @@ abstract class Object {
|
||||
* @param string|int $index an index to use if the property is an array
|
||||
*/
|
||||
protected function addMethodsFrom($property, $index = null) {
|
||||
$class = get_class($this);
|
||||
$extension = ($index !== null) ? $this->{$property}[$index] : $this->$property;
|
||||
|
||||
if(!$extension) {
|
||||
throw new InvalidArgumentException (
|
||||
"Object->addMethodsFrom(): could not add methods from {$this->class}->{$property}[$index]"
|
||||
"Object->addMethodsFrom(): could not add methods from {$class}->{$property}[$index]"
|
||||
);
|
||||
}
|
||||
|
||||
@ -881,11 +884,11 @@ abstract class Object {
|
||||
|
||||
$newMethods = array_fill_keys($methods, $methodInfo);
|
||||
|
||||
if(isset(self::$extra_methods[$this->class])) {
|
||||
self::$extra_methods[$this->class] =
|
||||
array_merge(self::$extra_methods[$this->class], $newMethods);
|
||||
if(isset(self::$extra_methods[$class])) {
|
||||
self::$extra_methods[$class] =
|
||||
array_merge(self::$extra_methods[$class], $newMethods);
|
||||
} else {
|
||||
self::$extra_methods[$this->class] = $newMethods;
|
||||
self::$extra_methods[$class] = $newMethods;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -929,7 +932,7 @@ abstract class Object {
|
||||
* @param string $wrap the method name to wrap to
|
||||
*/
|
||||
protected function addWrapperMethod($method, $wrap) {
|
||||
self::$extra_methods[$this->class][strtolower($method)] = array (
|
||||
self::$extra_methods[get_class($this)][strtolower($method)] = array (
|
||||
'wrap' => $wrap,
|
||||
'method' => $method
|
||||
);
|
||||
@ -944,7 +947,7 @@ abstract class Object {
|
||||
* function
|
||||
*/
|
||||
protected function createMethod($method, $code) {
|
||||
self::$extra_methods[$this->class][strtolower($method)] = array (
|
||||
self::$extra_methods[get_class($this)][strtolower($method)] = array (
|
||||
'function' => create_function('$obj, $args', $code)
|
||||
);
|
||||
}
|
||||
|
@ -270,7 +270,7 @@ class PaginatedList extends SS_ListDecorator {
|
||||
* </code>
|
||||
*
|
||||
* @param int $context The number of pages to display around the current
|
||||
* page. The number should be event, as half the number of each pages
|
||||
* page. The number should be even, as half the number of each pages
|
||||
* are displayed on either side of the current one.
|
||||
* @return SS_List
|
||||
*/
|
||||
|
18
docs/en/04_Changelogs/rc/3.4.6-rc1.md
Normal file
18
docs/en/04_Changelogs/rc/3.4.6-rc1.md
Normal file
@ -0,0 +1,18 @@
|
||||
# 3.4.6-rc1
|
||||
|
||||
<!--- Changes below this line will be automatically regenerated -->
|
||||
|
||||
## Change Log
|
||||
|
||||
### Security
|
||||
|
||||
* 2017-05-24 [41270fc](https://github.com/silverstripe/silverstripe-cms/commit/41270fcf9980c4be2529d2750c717675548eb617) Only allow HTTP(S) links for external redirector pages (Daniel Hensby) - See [ss-2017-003](http://www.silverstripe.org/download/security-releases/ss-2017-003)
|
||||
* 2017-05-09 [447ce0f](https://github.com/silverstripe/silverstripe-framework/commit/447ce0f84f880c2bc969a89e4be528c53caeabe0) Lock out users who dont exist in the DB (Daniel Hensby) - See [ss-2017-002](http://www.silverstripe.org/download/security-releases/ss-2017-002)
|
||||
* 2017-05-09 [61cf72c](https://github.com/silverstripe/silverstripe-cms/commit/61cf72c08dafddef416d73f943ccd45e70c5d43d) Unescaped fields in CMSPageHistroyController::compare() (Daniel Hensby) - See [ss-2017-004](http://www.silverstripe.org/download/security-releases/ss-2017-004)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* 2017-05-03 [2d138b0](https://github.com/silverstripe/silverstripe-framework/commit/2d138b0ef06bd93958cc0678a0afa95560648fb9) class name reference consistency (Gregory Smirnov)
|
||||
* 2017-04-24 [1d36f35](https://github.com/silverstripe/silverstripe-framework/commit/1d36f354e8349616c7b39fcade859fbcf0f9c362) Create Image_Cached with Injector. (Gregory Smirnov)
|
||||
* 2017-02-15 [3072591](https://github.com/silverstripe/silverstripe-framework/commit/30725916dbb0ffc66b77f26c069a86581636ae55) Array to string conversion message after CSV export (#6622) (Juan van den Anker)
|
||||
* 2017-02-14 [7122e1f](https://github.com/silverstripe/silverstripe-framework/commit/7122e1fde79bdb9aad3c8714a6ce02b7ecedd735) Comments ignored by classmanifest (#6619) (Daniel Hensby)
|
9
docs/en/04_Changelogs/rc/3.4.6-rc2.md
Normal file
9
docs/en/04_Changelogs/rc/3.4.6-rc2.md
Normal file
@ -0,0 +1,9 @@
|
||||
# 3.4.6-rc2
|
||||
|
||||
<!--- Changes below this line will be automatically regenerated -->
|
||||
|
||||
## Change Log
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* 2017-05-28 [16a74bc](https://github.com/silverstripe/silverstripe-framework/commit/16a74bc8a9fdee7cfb4f6f24493c271f90a76341) DataDifferencer needs to expliclty cast HTMLText values (Daniel Hensby)
|
28
docs/en/04_Changelogs/rc/3.5.4-rc1.md
Normal file
28
docs/en/04_Changelogs/rc/3.5.4-rc1.md
Normal file
@ -0,0 +1,28 @@
|
||||
# 3.5.4-rc1
|
||||
|
||||
<!--- Changes below this line will be automatically regenerated -->
|
||||
|
||||
## Change Log
|
||||
|
||||
### Security
|
||||
|
||||
* 2017-05-24 [41270fc](https://github.com/silverstripe/silverstripe-cms/commit/41270fcf9980c4be2529d2750c717675548eb617) Only allow HTTP(S) links for external redirector pages (Daniel Hensby) - See [ss-2017-003](http://www.silverstripe.org/download/security-releases/ss-2017-003)
|
||||
* 2017-05-09 [447ce0f](https://github.com/silverstripe/silverstripe-framework/commit/447ce0f84f880c2bc969a89e4be528c53caeabe0) Lock out users who dont exist in the DB (Daniel Hensby) - See [ss-2017-002](http://www.silverstripe.org/download/security-releases/ss-2017-002)
|
||||
* 2017-05-09 [61cf72c](https://github.com/silverstripe/silverstripe-cms/commit/61cf72c08dafddef416d73f943ccd45e70c5d43d) Unescaped fields in CMSPageHistroyController::compare() (Daniel Hensby) - See [ss-2017-004](http://www.silverstripe.org/download/security-releases/ss-2017-004)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* 2017-05-28 [16a74bc](https://github.com/silverstripe/silverstripe-framework/commit/16a74bc8a9fdee7cfb4f6f24493c271f90a76341) DataDifferencer needs to expliclty cast HTMLText values (Daniel Hensby)
|
||||
* 2017-05-08 [1454072](https://github.com/silverstripe/silverstripe-cms/commit/14540729caa30dd2e782e4fd52afe518dc156ed8) Use framework 3.5 to test cms 3.5 (Sam Minnee)
|
||||
* 2017-05-03 [2d138b0](https://github.com/silverstripe/silverstripe-framework/commit/2d138b0ef06bd93958cc0678a0afa95560648fb9) class name reference consistency (Gregory Smirnov)
|
||||
* 2017-05-02 [2187c16](https://github.com/silverstripe/silverstripe-framework/commit/2187c160b936620621fe746a1ffe36af568b21ff) ing pagination api doc typo (3Dgoo)
|
||||
* 2017-04-28 [a511e35](https://github.com/silverstripe/silverstripe-framework/commit/a511e3511cace405dab7589a3406a0858cb6edf2) #6855: Mangled JS in Requirements, escaping replacement values prior to passing to preg_replace(). (Patrick Nelson)
|
||||
* 2017-04-24 [1d36f35](https://github.com/silverstripe/silverstripe-framework/commit/1d36f354e8349616c7b39fcade859fbcf0f9c362) Create Image_Cached with Injector. (Gregory Smirnov)
|
||||
* 2017-04-07 [55eb7eb](https://github.com/silverstripe/silverstripe-framework/commit/55eb7ebdcc9ba767f978dff510614bbd2e0c309d) Do not insert requirements more than once in includeInHTML (Robbie Averill)
|
||||
* 2017-04-05 [a7920b1](https://github.com/silverstripe/silverstripe-framework/commit/a7920b1f9866b6eb5f4bad9de84eef84b88673ad) regression from #6668 - ModelAdmin form widths (Loz Calver)
|
||||
* 2017-04-05 [197bc53](https://github.com/silverstripe/silverstripe-framework/commit/197bc53c4963898d2c10621ca6d6031fdb14fe85) Add transparency percent argument to Image::generatePad to ensure transparency works from ::Pad (Robbie Averill)
|
||||
* 2017-02-15 [3072591](https://github.com/silverstripe/silverstripe-framework/commit/30725916dbb0ffc66b77f26c069a86581636ae55) Array to string conversion message after CSV export (#6622) (Juan van den Anker)
|
||||
* 2017-02-14 [7122e1f](https://github.com/silverstripe/silverstripe-framework/commit/7122e1fde79bdb9aad3c8714a6ce02b7ecedd735) Comments ignored by classmanifest (#6619) (Daniel Hensby)
|
||||
* 2017-02-09 [6e2797f](https://github.com/silverstripe/silverstripe-framework/commit/6e2797ffc0e9632b60acc5a66e52aeb44f0e2b78) es for using dblib PDO driver. (Andrew O'Neil)
|
||||
* 2017-02-08 [c25c443](https://github.com/silverstripe/silverstripe-framework/commit/c25c443d95fc305fb3545b1393b7da85923dcf8b) Fix minor mysql 5.7 warning in SQLQueryTest (#6608) (Damian Mooyman)
|
||||
* 2017-01-18 [72b6fb4](https://github.com/silverstripe/silverstripe-framework/commit/72b6fb49b698bc3a51c8f6b32d2bf08213729493) bug: In addOrderBy method, _SortColumn will only keep the last one if there are more than 1 multi-word columns (Shawn)
|
@ -102,6 +102,29 @@ class GridFieldFilterHeader implements GridField_HTMLProvider, GridField_DataMan
|
||||
return $dataListClone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $column
|
||||
* @return string
|
||||
*/
|
||||
protected function columnToFilterField($class, $column)
|
||||
{
|
||||
if (strpos($column, '.') === false) {
|
||||
return $column;
|
||||
}
|
||||
/** @var DataObject $model */
|
||||
$model = singleton($class);
|
||||
$columnParts = explode('.', $column);
|
||||
if ($model->getRelationClass($columnParts[0])) {
|
||||
return $column;
|
||||
}
|
||||
return $columnParts[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GridField $gridField
|
||||
* @return array
|
||||
*/
|
||||
public function getHTMLFragments($gridField) {
|
||||
if(!$this->checkDataType($gridField->getList())) return;
|
||||
|
||||
@ -110,18 +133,21 @@ class GridFieldFilterHeader implements GridField_HTMLProvider, GridField_DataMan
|
||||
$columns = $gridField->getColumns();
|
||||
$filterArguments = $gridField->State->GridFieldFilterHeader->Columns->toArray();
|
||||
$currentColumn = 0;
|
||||
|
||||
foreach($columns as $columnField) {
|
||||
$currentColumn++;
|
||||
++$currentColumn;
|
||||
$metadata = $gridField->getColumnMetadata($columnField);
|
||||
$title = $metadata['title'];
|
||||
$fields = new FieldGroup();
|
||||
|
||||
if($title && $gridField->getList()->canFilterBy($columnField)) {
|
||||
$filterField = $this->columnToFilterField($gridField->getModelClass(), $columnField);
|
||||
|
||||
if($title && $gridField->getList()->canFilterBy($filterField)) {
|
||||
$value = '';
|
||||
if(isset($filterArguments[$columnField])) {
|
||||
$value = $filterArguments[$columnField];
|
||||
if(isset($filterArguments[$filterField])) {
|
||||
$value = $filterArguments[$filterField];
|
||||
}
|
||||
$field = new TextField('filter[' . $gridField->getName() . '][' . $columnField . ']', '', $value);
|
||||
$field = new TextField('filter[' . $gridField->getName() . '][' . $filterField . ']', '', $value);
|
||||
$field->addExtraClass('ss-gridfield-sort');
|
||||
$field->addExtraClass('no-change-track');
|
||||
|
||||
@ -133,7 +159,7 @@ class GridFieldFilterHeader implements GridField_HTMLProvider, GridField_DataMan
|
||||
GridField_FormAction::create($gridField, 'reset', false, 'reset', null)
|
||||
->addExtraClass('ss-gridfield-button-reset')
|
||||
->setAttribute('title', _t('GridField.ResetFilter', "Reset"))
|
||||
->setAttribute('id', 'action_reset_' . $gridField->getModelClass() . '_' . $columnField)
|
||||
->setAttribute('id', 'action_reset_' . $gridField->getModelClass() . '_' . $filterField)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ class DataDifferencer extends ViewableData {
|
||||
|
||||
// Show changes between the two, if any exist
|
||||
if($fromValue != $toValue) {
|
||||
$diffed->setField($field, Diff::compareHTML($fromValue, $toValue));
|
||||
$diffed->setField($field, DBField::create_field('HTMLText', Diff::compareHTML($fromValue, $toValue)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,10 +339,11 @@ class Image extends File implements Flushable {
|
||||
*
|
||||
* @param integer $width The width to size to
|
||||
* @param integer $height The height to size to
|
||||
* @param string $backgroundColor The background colour to use on padded sides (default white)
|
||||
* @param integer $transparencyPercent Level of transparency
|
||||
* @return Image|null
|
||||
*/
|
||||
public function Pad($width, $height, $backgroundColor='FFFFFF', $transparencyPercent = 0) {
|
||||
public function Pad($width, $height, $backgroundColor = 'FFFFFF', $transparencyPercent = 0) {
|
||||
return $this->isSize($width, $height) && !Config::inst()->get('Image', 'force_resample')
|
||||
? $this
|
||||
: $this->getFormattedImage('Pad', $width, $height, $backgroundColor, $transparencyPercent);
|
||||
@ -354,12 +355,16 @@ class Image extends File implements Flushable {
|
||||
* @param Image_Backend $backend
|
||||
* @param integer $width The width to size to
|
||||
* @param integer $height The height to size to
|
||||
* @param string $backgroundColor The background colour to use on padded sides (default white)
|
||||
* @param integer $transparencyPercent Level of transparency
|
||||
* @return Image_Backend
|
||||
* @deprecated 4.0 Generate methods are no longer applicable
|
||||
*/
|
||||
public function generatePad(Image_Backend $backend, $width, $height, $backgroundColor='FFFFFF') {
|
||||
public function generatePad(Image_Backend $backend, $width, $height, $backgroundColor = 'FFFFFF',
|
||||
$transparencyPercent = 0
|
||||
) {
|
||||
Deprecation::notice('4.0', 'Generate methods are no longer applicable');
|
||||
return $backend->paddedResize($width, $height, $backgroundColor);
|
||||
return $backend->paddedResize($width, $height, $backgroundColor, $transparencyPercent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -730,7 +735,7 @@ class Image extends File implements Flushable {
|
||||
call_user_func_array(array($this, "generateFormattedImage"), $args);
|
||||
}
|
||||
|
||||
$cached = new Image_Cached($cacheFile, false, $this);
|
||||
$cached = Injector::inst()->createWithArgs('Image_Cached', array($cacheFile, false, $this));
|
||||
return $cached;
|
||||
}
|
||||
}
|
||||
|
@ -398,9 +398,44 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
* Returns true if this user is locked out
|
||||
*/
|
||||
public function isLockedOut() {
|
||||
$state = ($this->LockedOutUntil && SS_Datetime::now()->Format('U') < strtotime($this->LockedOutUntil));
|
||||
$this->extend('updateIsLockedOut', $state);
|
||||
return $state;
|
||||
$state = true;
|
||||
if ($this->LockedOutUntil && $this->dbObject('LockedOutUntil')->InFuture()) {
|
||||
$state = true;
|
||||
} elseif ($this->config()->lock_out_after_incorrect_logins <= 0) {
|
||||
$state = false;
|
||||
} else {
|
||||
|
||||
$attempts = LoginAttempt::get()->filter($filter = array(
|
||||
'Email' => $this->{static::config()->unique_identifier_field},
|
||||
))->sort('Created', 'DESC')->limit($this->config()->lock_out_after_incorrect_logins);
|
||||
|
||||
if ($attempts->count() < $this->config()->lock_out_after_incorrect_logins) {
|
||||
$state = false;
|
||||
} else {
|
||||
|
||||
$success = false;
|
||||
foreach ($attempts as $attempt) {
|
||||
if ($attempt->Status === 'Success') {
|
||||
$success = true;
|
||||
$state = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
$lockedOutUntil = $attempts->first()->dbObject('Created')->Format('U')
|
||||
+ ($this->config()->lock_out_delay_mins * 60);
|
||||
if (SS_Datetime::now()->Format('U') < $lockedOutUntil) {
|
||||
$state = true;
|
||||
} else {
|
||||
$state = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->extend('updateIsLockedOut', $state);
|
||||
return $state;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1666,7 +1701,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
public function registerFailedLogin() {
|
||||
if(self::config()->lock_out_after_incorrect_logins) {
|
||||
// Keep a tally of the number of failed log-ins so that we can lock people out
|
||||
$this->FailedLoginCount = $this->FailedLoginCount + 1;
|
||||
++$this->FailedLoginCount;
|
||||
|
||||
if($this->FailedLoginCount >= self::config()->lock_out_after_incorrect_logins) {
|
||||
$lockoutMins = self::config()->lock_out_delay_mins;
|
||||
@ -1685,6 +1720,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
if(self::config()->lock_out_after_incorrect_logins) {
|
||||
// Forgive all past login failures
|
||||
$this->FailedLoginCount = 0;
|
||||
$this->LockedOutUntil = null;
|
||||
$this->write();
|
||||
}
|
||||
$this->extend('onAfterRegisterSuccessfulLogin');
|
||||
|
@ -70,6 +70,14 @@ class MemberAuthenticator extends Authenticator {
|
||||
if($member && !$asDefaultAdmin) {
|
||||
$result = $member->checkPassword($data['Password']);
|
||||
$success = $result->valid();
|
||||
} elseif (!$asDefaultAdmin) {
|
||||
// spoof a login attempt
|
||||
$member = Member::create();
|
||||
$member->Email = $email;
|
||||
$member->{Member::config()->unique_identifier_field} = $data['Password'] . '-wrong';
|
||||
$member->PasswordEncryption = 'none';
|
||||
$result = $member->checkPassword($data['Password']);
|
||||
$member = null;
|
||||
} else {
|
||||
$result = new ValidationResult(false, _t('Member.ERRORWRONGCRED'));
|
||||
}
|
||||
@ -94,7 +102,7 @@ class MemberAuthenticator extends Authenticator {
|
||||
* @param bool $success
|
||||
*/
|
||||
protected static function record_login_attempt($data, $member, $success) {
|
||||
if(!Security::config()->login_recording) return;
|
||||
if(!Security::config()->login_recording && !Member::config()->lock_out_after_incorrect_logins) return;
|
||||
|
||||
// Check email is valid
|
||||
$email = isset($data['Email']) ? $data['Email'] : null;
|
||||
|
@ -6,16 +6,9 @@ class CookieTest extends SapphireTest {
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
Injector::nest();
|
||||
Injector::inst()->registerService(new CookieJar($_COOKIE), 'Cookie_Backend');
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
//restore the cookie_backend
|
||||
Injector::unnest();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a new cookie inst will be loaded with the superglobal by default
|
||||
*/
|
||||
|
@ -134,8 +134,6 @@ class UploadTest extends SapphireTest {
|
||||
}
|
||||
|
||||
public function testGetAllowedMaxFileSize() {
|
||||
Config::nest();
|
||||
|
||||
// Check the max file size uses the config values
|
||||
$configMaxFileSizes = array(
|
||||
'[image]' => '1k',
|
||||
@ -176,15 +174,13 @@ class UploadTest extends SapphireTest {
|
||||
|
||||
$retrievedSize = $v->getAllowedMaxFileSize('txt');
|
||||
$this->assertEquals(4096, $retrievedSize, 'Max file size check on instance values failed (instance extension set check)');
|
||||
|
||||
|
||||
// Check a wildcard max file size against a file with an extension
|
||||
$v = new UploadTest_Validator();
|
||||
$v->setAllowedMaxFileSize(2000);
|
||||
|
||||
$retrievedSize = $v->getAllowedMaxFileSize('.jpg');
|
||||
$this->assertEquals(2000, $retrievedSize, 'Max file size check on instance values failed (wildcard max file size)');
|
||||
|
||||
Config::unnest();
|
||||
}
|
||||
|
||||
public function testAllowedSizeOnFileWithNoExtension() {
|
||||
|
@ -10,8 +10,6 @@ class FormFieldTest extends SapphireTest {
|
||||
);
|
||||
|
||||
public function testDefaultClasses() {
|
||||
Config::nest();
|
||||
|
||||
Config::inst()->update('FormField', 'default_classes', array(
|
||||
'class1',
|
||||
));
|
||||
@ -50,8 +48,6 @@ class FormFieldTest extends SapphireTest {
|
||||
//check default classes inherit
|
||||
$this->assertContains('class3', $field->extraClass(), 'Class list does not contain inherited class');
|
||||
$this->assertContains('textfield-class', $field->extraClass(), 'Class list does not contain expected class');
|
||||
|
||||
Config::unnest();
|
||||
}
|
||||
|
||||
public function testAddExtraClass() {
|
||||
|
@ -549,8 +549,6 @@ class FormTest extends FunctionalTest {
|
||||
}
|
||||
|
||||
public function testDefaultClasses() {
|
||||
Config::nest();
|
||||
|
||||
Config::inst()->update('Form', 'default_classes', array(
|
||||
'class1',
|
||||
));
|
||||
@ -579,8 +577,6 @@ class FormTest extends FunctionalTest {
|
||||
$form->removeExtraClass('class3');
|
||||
|
||||
$this->assertNotContains('class3', $form->extraClass(), 'Class list contains unexpected class');
|
||||
|
||||
Config::unnest();
|
||||
}
|
||||
|
||||
public function testAttributes() {
|
||||
|
@ -25,17 +25,10 @@ class HtmlEditorFieldToolbarTest extends SapphireTest {
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
Config::nest();
|
||||
Config::inst()->update('HtmlEditorField_Toolbar', 'fileurl_scheme_whitelist', array('http'));
|
||||
Config::inst()->update('HtmlEditorField_Toolbar', 'fileurl_domain_whitelist', array('example.com'));
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
Config::unnest();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testValidLocalReference() {
|
||||
list($file, $url) = $this->getToolbar()->viewfile_getLocalFileByURL('folder/subfolder/example.pdf');
|
||||
$this->assertEquals($this->objFromFixture('File', 'example_file'), $file);
|
||||
|
32
tests/forms/gridfield/GridFieldFilterHeaderTest.php
Normal file
32
tests/forms/gridfield/GridFieldFilterHeaderTest.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
class GridFieldFilterHeaderTest extends SapphireTest {
|
||||
|
||||
protected $extraDataObjects = array(
|
||||
'GridFieldFilterHeaderTest_DataObject',
|
||||
);
|
||||
|
||||
public function testColumnToFilterField() {
|
||||
$class = 'GridFieldFilterHeaderTest_DataObject';
|
||||
$header = new GridFieldFilterHeader();
|
||||
$method = new ReflectionMethod($header, 'columnToFilterField');
|
||||
$method->setAccessible(true);
|
||||
$this->assertEquals('Title', $method->invoke($header, $class,'Title.ATT'));
|
||||
$this->assertEquals('isTest', $method->invoke($header, $class, 'isTest.Nice'));
|
||||
$this->assertEquals('Self.isTest.Nice', $method->invoke($header, $class, 'Self.isTest.Nice'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GridFieldFilterHeaderTest_DataObject extends DataObject implements TestOnly {
|
||||
|
||||
private static $db = array(
|
||||
'Title' => 'Varchar',
|
||||
'isTest' => 'Boolean',
|
||||
);
|
||||
|
||||
private static $has_one = array(
|
||||
'Self' => 'GridFieldFilterHeaderTest_DataObject',
|
||||
);
|
||||
|
||||
}
|
@ -1126,7 +1126,6 @@ class DataObjectTest extends SapphireTest {
|
||||
}
|
||||
|
||||
public function testValidateModelDefinitionsFailsWithArray() {
|
||||
Config::nest();
|
||||
|
||||
$object = new DataObjectTest_Team;
|
||||
$method = $this->makeAccessible($object, 'validateModelDefinitions');
|
||||
@ -1134,54 +1133,33 @@ class DataObjectTest extends SapphireTest {
|
||||
Config::inst()->update('DataObjectTest_Team', 'has_one', array('NotValid' => array('NoArraysAllowed')));
|
||||
$this->setExpectedException('LogicException');
|
||||
|
||||
try {
|
||||
$method->invoke($object);
|
||||
} catch(Exception $e) {
|
||||
Config::unnest(); // Catch the exception so we can unnest config before failing the test
|
||||
throw $e;
|
||||
}
|
||||
$method->invoke($object);
|
||||
}
|
||||
|
||||
public function testValidateModelDefinitionsFailsWithIntKey() {
|
||||
Config::nest();
|
||||
|
||||
$object = new DataObjectTest_Team;
|
||||
$method = $this->makeAccessible($object, 'validateModelDefinitions');
|
||||
|
||||
Config::inst()->update('DataObjectTest_Team', 'has_many', array(12 => 'DataObjectTest_Player'));
|
||||
$this->setExpectedException('LogicException');
|
||||
|
||||
try {
|
||||
$method->invoke($object);
|
||||
} catch(Exception $e) {
|
||||
Config::unnest(); // Catch the exception so we can unnest config before failing the test
|
||||
throw $e;
|
||||
}
|
||||
$method->invoke($object);
|
||||
}
|
||||
|
||||
public function testValidateModelDefinitionsFailsWithIntValue() {
|
||||
Config::nest();
|
||||
|
||||
$object = new DataObjectTest_Team;
|
||||
$method = $this->makeAccessible($object, 'validateModelDefinitions');
|
||||
|
||||
Config::inst()->update('DataObjectTest_Team', 'many_many', array('Players' => 12));
|
||||
$this->setExpectedException('LogicException');
|
||||
|
||||
try {
|
||||
$method->invoke($object);
|
||||
} catch(Exception $e) {
|
||||
Config::unnest(); // Catch the exception so we can unnest config before failing the test
|
||||
throw $e;
|
||||
}
|
||||
$method->invoke($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* many_many_extraFields is allowed to have an array value, so shouldn't throw an exception
|
||||
*/
|
||||
public function testValidateModelDefinitionsPassesWithExtraFields() {
|
||||
Config::nest();
|
||||
|
||||
$object = new DataObjectTest_Team;
|
||||
$method = $this->makeAccessible($object, 'validateModelDefinitions');
|
||||
|
||||
@ -1191,12 +1169,9 @@ class DataObjectTest extends SapphireTest {
|
||||
try {
|
||||
$method->invoke($object);
|
||||
} catch(Exception $e) {
|
||||
Config::unnest();
|
||||
$this->fail('Exception should not be thrown');
|
||||
throw $e;
|
||||
}
|
||||
|
||||
Config::unnest();
|
||||
}
|
||||
|
||||
public function testNewClassInstance() {
|
||||
|
@ -37,6 +37,13 @@ class VersionableExtensionsTest extends SapphireTest
|
||||
parent::setUpOnce();
|
||||
}
|
||||
|
||||
public function tearDownOnce()
|
||||
{
|
||||
parent::tearDownOnce();
|
||||
|
||||
Config::unnest();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
@ -180,6 +180,45 @@ class MemberAuthenticatorTest extends SapphireTest {
|
||||
), $form);
|
||||
|
||||
$this->assertTrue(Member::default_admin()->isLockedOut());
|
||||
$this->assertEquals(Member::default_admin()->LockedOutUntil, '2016-04-18 00:10:00');
|
||||
$this->assertEquals('2016-04-18 00:10:00', Member::default_admin()->LockedOutUntil);
|
||||
}
|
||||
|
||||
public function testNonExistantMemberGetsLoginAttemptRecorded()
|
||||
{
|
||||
Config::inst()->update('Member', 'lock_out_after_incorrect_logins', 1);
|
||||
$email = 'notreal@example.com';
|
||||
$this->assertFalse(Member::get()->filter(array('Email' => $email))->exists());
|
||||
$this->assertCount(0, LoginAttempt::get());
|
||||
$response = MemberAuthenticator::authenticate(array(
|
||||
'Email' => $email,
|
||||
'Password' => 'password',
|
||||
));
|
||||
$this->assertNull($response);
|
||||
$this->assertCount(1, LoginAttempt::get());
|
||||
$attempt = LoginAttempt::get()->first();
|
||||
$this->assertEquals($email, $attempt->Email);
|
||||
$this->assertEquals('Failure', $attempt->Status);
|
||||
|
||||
}
|
||||
|
||||
public function testNonExistantMemberGetsLockedOut()
|
||||
{
|
||||
Config::inst()->update('Member', 'lock_out_after_incorrect_logins', 1);
|
||||
Config::inst()->update('Member', 'lock_out_delay_mins', 10);
|
||||
$email = 'notreal@example.com';
|
||||
|
||||
$this->assertFalse(Member::get()->filter(array('Email' => $email))->exists());
|
||||
|
||||
$response = MemberAuthenticator::authenticate(array(
|
||||
'Email' => $email,
|
||||
'Password' => 'password'
|
||||
));
|
||||
|
||||
$this->assertNull($response);
|
||||
$member = new Member();
|
||||
$member->Email = $email;
|
||||
|
||||
$this->assertTrue($member->isLockedOut());
|
||||
$this->assertFalse($member->canLogIn()->valid());
|
||||
}
|
||||
}
|
||||
|
@ -846,7 +846,6 @@ class MemberTest extends FunctionalTest {
|
||||
public function testFailedLoginCount() {
|
||||
$maxFailedLoginsAllowed = 3;
|
||||
//set up the config variables to enable login lockouts
|
||||
Config::nest();
|
||||
Config::inst()->update('Member', 'lock_out_after_incorrect_logins', $maxFailedLoginsAllowed);
|
||||
|
||||
$member = $this->objFromFixture('Member', 'test');
|
||||
|
@ -75,8 +75,6 @@ class SecurityTest extends FunctionalTest {
|
||||
}
|
||||
|
||||
public function testPermissionFailureSetsCorrectFormMessages() {
|
||||
Config::nest();
|
||||
|
||||
// Controller that doesn't attempt redirections
|
||||
$controller = new SecurityTest_NullController();
|
||||
$controller->setResponse(new SS_HTTPResponse());
|
||||
@ -111,8 +109,6 @@ class SecurityTest extends FunctionalTest {
|
||||
array('default' => 'default', 'alreadyLoggedIn' => 'One-off failure message'));
|
||||
$this->assertContains('One-off failure message', $controller->getResponse()->getBody(),
|
||||
"Message set passed to Security::permissionFailure() didn't override Config values");
|
||||
|
||||
Config::unnest();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -402,6 +398,7 @@ class SecurityTest extends FunctionalTest {
|
||||
public function testRepeatedLoginAttemptsLockingPeopleOut() {
|
||||
$local = i18n::get_locale();
|
||||
i18n::set_locale('en_US');
|
||||
SS_Datetime::set_mock_now(DBField::create_field('SS_Datetime', '2017-05-22 00:00:00'));
|
||||
|
||||
Member::config()->lock_out_after_incorrect_logins = 5;
|
||||
Member::config()->lock_out_delay_mins = 15;
|
||||
@ -418,10 +415,9 @@ class SecurityTest extends FunctionalTest {
|
||||
);
|
||||
$this->assertContains($this->loginErrorMessage(), Convert::raw2xml(_t('Member.ERRORWRONGCRED')));
|
||||
} else {
|
||||
// Fuzzy matching for time to avoid side effects from slow running tests
|
||||
$this->assertGreaterThan(
|
||||
time() + 14*60,
|
||||
strtotime($member->LockedOutUntil),
|
||||
$this->assertEquals(
|
||||
SS_Datetime::now()->Format('U') + (15 * 60),
|
||||
$member->dbObject('LockedOutUntil')->Format('U'),
|
||||
'User has a lockout time set after too many failed attempts'
|
||||
);
|
||||
}
|
||||
@ -444,14 +440,12 @@ class SecurityTest extends FunctionalTest {
|
||||
'The user can\'t log in after being locked out, even with the right password'
|
||||
);
|
||||
|
||||
// (We fake this by re-setting LockedOutUntil)
|
||||
$member = DataObject::get_by_id("Member", $this->idFromFixture('Member', 'test'));
|
||||
$member->LockedOutUntil = date('Y-m-d H:i:s', time() - 30);
|
||||
$member->write();
|
||||
// Move into the future so we can login again
|
||||
SS_Datetime::set_mock_now(DBField::create_field('SS_Datetime', '2017-06-22 00:00:00'));
|
||||
$this->doTestLoginForm('testuser@example.com' , '1nitialPassword');
|
||||
$this->assertEquals(
|
||||
$this->session()->inst_get('loggedInAs'),
|
||||
$member->ID,
|
||||
$this->session()->inst_get('loggedInAs'),
|
||||
'After lockout expires, the user can login again'
|
||||
);
|
||||
|
||||
@ -471,8 +465,8 @@ class SecurityTest extends FunctionalTest {
|
||||
|
||||
$this->doTestLoginForm('testuser@example.com' , '1nitialPassword');
|
||||
$this->assertEquals(
|
||||
$this->session()->inst_get('loggedInAs'),
|
||||
$member->ID,
|
||||
$this->session()->inst_get('loggedInAs'),
|
||||
'The user can login successfully after lockout expires, if staying below the threshold'
|
||||
);
|
||||
|
||||
|
@ -869,10 +869,10 @@ class Requirements_Backend {
|
||||
|
||||
// Forcefully put the scripts at the bottom of the body instead of before the first
|
||||
// script tag.
|
||||
$replacements["/(<\/body[^>]*>)/i"] = $jsRequirements . "\\1";
|
||||
$replacements["/(<\/body[^>]*>)/i"] = $this->escapeReplacement($jsRequirements) . "\\1";
|
||||
|
||||
// Put CSS at the bottom of the head
|
||||
$replacements["/(<\/head>)/i"] = $requirements . "\\1";
|
||||
$replacements["/(<\/head>)/i"] = $this->escapeReplacement($requirements) . "\\1";
|
||||
} elseif ($this->write_js_to_body) {
|
||||
$jsRequirements = $this->removeNewlinesFromCode($jsRequirements);
|
||||
|
||||
@ -894,14 +894,14 @@ class Requirements_Backend {
|
||||
if ($canWriteToBody) {
|
||||
$content = substr($content, 0, $p1) . $jsRequirements . substr($content, $p1);
|
||||
} else {
|
||||
$replacements["/(<\/body[^>]*>)/i"] = $jsRequirements . "\\1";
|
||||
$replacements["/(<\/body[^>]*>)/i"] = $this->escapeReplacement($jsRequirements) . "\\1";
|
||||
}
|
||||
|
||||
// Put CSS at the bottom of the head
|
||||
$replacements["/(<\/head>)/i"] = $requirements . "\\1";
|
||||
$replacements["/(<\/head>)/i"] = $this->escapeReplacement($requirements) . "\\1";
|
||||
} else {
|
||||
// Put CSS and Javascript together before the closing head tag
|
||||
$replacements["/(<\/head>)/i"] = $requirements . $jsRequirements. "\\1";
|
||||
$replacements["/(<\/head>)/i"] = $this->escapeReplacement($requirements . $jsRequirements) . "\\1";
|
||||
}
|
||||
|
||||
if (!empty($replacements)) {
|
||||
@ -923,6 +923,16 @@ class Requirements_Backend {
|
||||
return preg_replace('/>\n*/', '>', $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely escape a literal string for use in preg_replace replacement
|
||||
*
|
||||
* @param string $replacement
|
||||
* @return string
|
||||
*/
|
||||
protected function escapeReplacement($replacement) {
|
||||
return addcslashes($replacement, '\\$');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach requirements inclusion to X-Include-JS and X-Include-CSS headers on the given
|
||||
* HTTP Response
|
||||
|
Loading…
Reference in New Issue
Block a user