mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge tag '3.4.6' into 3.4
Release 3.4.6
This commit is contained in:
commit
cf8f781238
19
docs/en/04_Changelogs/3.4.6.md
Normal file
19
docs/en/04_Changelogs/3.4.6.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# 3.4.6
|
||||||
|
|
||||||
|
<!--- 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-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)
|
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)
|
@ -103,7 +103,7 @@ class DataDifferencer extends ViewableData {
|
|||||||
|
|
||||||
// Show changes between the two, if any exist
|
// Show changes between the two, if any exist
|
||||||
if($fromValue != $toValue) {
|
if($fromValue != $toValue) {
|
||||||
$diffed->setField($field, Diff::compareHTML($fromValue, $toValue));
|
$diffed->setField($field, DBField::create_field('HTMLText', Diff::compareHTML($fromValue, $toValue)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,7 +398,35 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
|||||||
* Returns true if this user is locked out
|
* Returns true if this user is locked out
|
||||||
*/
|
*/
|
||||||
public function isLockedOut() {
|
public function isLockedOut() {
|
||||||
return $this->LockedOutUntil && SS_Datetime::now()->Format('U') < strtotime($this->LockedOutUntil);
|
global $debug;
|
||||||
|
if ($this->LockedOutUntil && $this->dbObject('LockedOutUntil')->InFuture()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->config()->lock_out_after_incorrect_logins <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$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) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($attempts as $attempt) {
|
||||||
|
if ($attempt->Status === 'Success') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$lockedOutUntil = $attempts->first()->dbObject('Created')->Format('U') + ($this->config()->lock_out_delay_mins * 60);
|
||||||
|
if (SS_Datetime::now()->Format('U') < $lockedOutUntil) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1660,7 +1688,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
|||||||
public function registerFailedLogin() {
|
public function registerFailedLogin() {
|
||||||
if(self::config()->lock_out_after_incorrect_logins) {
|
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
|
// 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) {
|
if($this->FailedLoginCount >= self::config()->lock_out_after_incorrect_logins) {
|
||||||
$lockoutMins = self::config()->lock_out_delay_mins;
|
$lockoutMins = self::config()->lock_out_delay_mins;
|
||||||
@ -1679,6 +1707,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
|||||||
if(self::config()->lock_out_after_incorrect_logins) {
|
if(self::config()->lock_out_after_incorrect_logins) {
|
||||||
// Forgive all past login failures
|
// Forgive all past login failures
|
||||||
$this->FailedLoginCount = 0;
|
$this->FailedLoginCount = 0;
|
||||||
|
$this->LockedOutUntil = null;
|
||||||
$this->write();
|
$this->write();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,14 @@ class MemberAuthenticator extends Authenticator {
|
|||||||
if($member && !$asDefaultAdmin) {
|
if($member && !$asDefaultAdmin) {
|
||||||
$result = $member->checkPassword($data['Password']);
|
$result = $member->checkPassword($data['Password']);
|
||||||
$success = $result->valid();
|
$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 {
|
} else {
|
||||||
$result = new ValidationResult(false, _t('Member.ERRORWRONGCRED'));
|
$result = new ValidationResult(false, _t('Member.ERRORWRONGCRED'));
|
||||||
}
|
}
|
||||||
@ -94,7 +102,7 @@ class MemberAuthenticator extends Authenticator {
|
|||||||
* @param bool $success
|
* @param bool $success
|
||||||
*/
|
*/
|
||||||
protected static function record_login_attempt($data, $member, $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
|
// Check email is valid
|
||||||
$email = isset($data['Email']) ? $data['Email'] : null;
|
$email = isset($data['Email']) ? $data['Email'] : null;
|
||||||
|
@ -180,6 +180,45 @@ class MemberAuthenticatorTest extends SapphireTest {
|
|||||||
), $form);
|
), $form);
|
||||||
|
|
||||||
$this->assertTrue(Member::default_admin()->isLockedOut());
|
$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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -398,6 +398,7 @@ class SecurityTest extends FunctionalTest {
|
|||||||
public function testRepeatedLoginAttemptsLockingPeopleOut() {
|
public function testRepeatedLoginAttemptsLockingPeopleOut() {
|
||||||
$local = i18n::get_locale();
|
$local = i18n::get_locale();
|
||||||
i18n::set_locale('en_US');
|
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_after_incorrect_logins = 5;
|
||||||
Member::config()->lock_out_delay_mins = 15;
|
Member::config()->lock_out_delay_mins = 15;
|
||||||
@ -414,10 +415,9 @@ class SecurityTest extends FunctionalTest {
|
|||||||
);
|
);
|
||||||
$this->assertContains($this->loginErrorMessage(), Convert::raw2xml(_t('Member.ERRORWRONGCRED')));
|
$this->assertContains($this->loginErrorMessage(), Convert::raw2xml(_t('Member.ERRORWRONGCRED')));
|
||||||
} else {
|
} else {
|
||||||
// Fuzzy matching for time to avoid side effects from slow running tests
|
$this->assertEquals(
|
||||||
$this->assertGreaterThan(
|
SS_Datetime::now()->Format('U') + (15 * 60),
|
||||||
time() + 14*60,
|
$member->dbObject('LockedOutUntil')->Format('U'),
|
||||||
strtotime($member->LockedOutUntil),
|
|
||||||
'User has a lockout time set after too many failed attempts'
|
'User has a lockout time set after too many failed attempts'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -440,14 +440,12 @@ class SecurityTest extends FunctionalTest {
|
|||||||
'The user can\'t log in after being locked out, even with the right password'
|
'The user can\'t log in after being locked out, even with the right password'
|
||||||
);
|
);
|
||||||
|
|
||||||
// (We fake this by re-setting LockedOutUntil)
|
// Move into the future so we can login again
|
||||||
$member = DataObject::get_by_id("Member", $this->idFromFixture('Member', 'test'));
|
SS_Datetime::set_mock_now(DBField::create_field('SS_Datetime', '2017-06-22 00:00:00'));
|
||||||
$member->LockedOutUntil = date('Y-m-d H:i:s', time() - 30);
|
|
||||||
$member->write();
|
|
||||||
$this->doTestLoginForm('testuser@example.com' , '1nitialPassword');
|
$this->doTestLoginForm('testuser@example.com' , '1nitialPassword');
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->session()->inst_get('loggedInAs'),
|
|
||||||
$member->ID,
|
$member->ID,
|
||||||
|
$this->session()->inst_get('loggedInAs'),
|
||||||
'After lockout expires, the user can login again'
|
'After lockout expires, the user can login again'
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -467,8 +465,8 @@ class SecurityTest extends FunctionalTest {
|
|||||||
|
|
||||||
$this->doTestLoginForm('testuser@example.com' , '1nitialPassword');
|
$this->doTestLoginForm('testuser@example.com' , '1nitialPassword');
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
$this->session()->inst_get('loggedInAs'),
|
|
||||||
$member->ID,
|
$member->ID,
|
||||||
|
$this->session()->inst_get('loggedInAs'),
|
||||||
'The user can login successfully after lockout expires, if staying below the threshold'
|
'The user can login successfully after lockout expires, if staying below the threshold'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user