mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Security.remember_username to disable login form autocompletion
This commit is contained in:
parent
ee487e28f1
commit
997077ae83
@ -39,6 +39,8 @@ class FunctionalTest extends SapphireTest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* CSSContentParser for the most recently requested page.
|
* CSSContentParser for the most recently requested page.
|
||||||
|
*
|
||||||
|
* @var CSSContentParser
|
||||||
*/
|
*/
|
||||||
protected $cssParser = null;
|
protected $cssParser = null;
|
||||||
|
|
||||||
@ -176,6 +178,8 @@ class FunctionalTest extends SapphireTest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a CSSContentParser for the most recent content.
|
* Return a CSSContentParser for the most recent content.
|
||||||
|
*
|
||||||
|
* @return CSSContentParser
|
||||||
*/
|
*/
|
||||||
public function cssParser() {
|
public function cssParser() {
|
||||||
if(!$this->cssParser) $this->cssParser = new CSSContentParser($this->mainSession->lastContent());
|
if(!$this->cssParser) $this->cssParser = new CSSContentParser($this->mainSession->lastContent());
|
||||||
|
9
docs/en/changelogs/3.1.5.md
Normal file
9
docs/en/changelogs/3.1.5.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 3.1.5
|
||||||
|
|
||||||
|
## Upgrading
|
||||||
|
|
||||||
|
* If running an application in an environment where user security is critical, it may be necessary to
|
||||||
|
assign the config value `Security.remember_username` to false. This will disable persistence of
|
||||||
|
user login name between sessions, and disable browser auto-completion on the username field.
|
||||||
|
Note that users of certain browsers who have previously autofilled and saved login credentials
|
||||||
|
will need to clear their password autofill history before this setting is properly respected.
|
@ -445,6 +445,7 @@ In addition, you can tighten password security with the following configuration
|
|||||||
the user is blocked from further attempts for the timespan defined in `$lock_out_delay_mins`
|
the user is blocked from further attempts for the timespan defined in `$lock_out_delay_mins`
|
||||||
* `Member.lock_out_delay_mins`: Minutes of enforced lockout after incorrect password attempts.
|
* `Member.lock_out_delay_mins`: Minutes of enforced lockout after incorrect password attempts.
|
||||||
Only applies if `lock_out_after_incorrect_logins` is greater than 0.
|
Only applies if `lock_out_after_incorrect_logins` is greater than 0.
|
||||||
|
* `Security.remember_username`: Set to false to disable autocomplete on login form
|
||||||
|
|
||||||
## Clickjacking: Prevent iframe Inclusion
|
## Clickjacking: Prevent iframe Inclusion
|
||||||
|
|
||||||
|
@ -80,9 +80,16 @@ class MemberLoginForm extends LoginForm {
|
|||||||
new HiddenField("AuthenticationMethod", null, $this->authenticator_class, $this),
|
new HiddenField("AuthenticationMethod", null, $this->authenticator_class, $this),
|
||||||
// Regardless of what the unique identifer field is (usually 'Email'), it will be held in the
|
// Regardless of what the unique identifer field is (usually 'Email'), it will be held in the
|
||||||
// 'Email' value, below:
|
// 'Email' value, below:
|
||||||
new TextField("Email", $label, Session::get('SessionForms.MemberLoginForm.Email'), null, $this),
|
$emailField = new TextField("Email", $label, null, null, $this),
|
||||||
new PasswordField("Password", _t('Member.PASSWORD', 'Password'))
|
new PasswordField("Password", _t('Member.PASSWORD', 'Password'))
|
||||||
);
|
);
|
||||||
|
if(Security::config()->remember_username) {
|
||||||
|
$emailField->setValue(Session::get('SessionForms.MemberLoginForm.Email'));
|
||||||
|
} else {
|
||||||
|
// Some browsers won't respect this attribute unless it's added to the form
|
||||||
|
$this->setAttribute('autocomplete', 'off');
|
||||||
|
$emailField->setAttribute('autocomplete', 'off');
|
||||||
|
}
|
||||||
if(Security::config()->autologin_enabled) {
|
if(Security::config()->autologin_enabled) {
|
||||||
$fields->push(new CheckboxField(
|
$fields->push(new CheckboxField(
|
||||||
"Remember",
|
"Remember",
|
||||||
|
@ -63,6 +63,15 @@ class Security extends Controller {
|
|||||||
*/
|
*/
|
||||||
private static $autologin_enabled = true;
|
private static $autologin_enabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if login username may be remembered between login sessions
|
||||||
|
* If set to false this will disable autocomplete and prevent username persisting in the session
|
||||||
|
*
|
||||||
|
* @config
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private static $remember_username = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Location of word list to use for generating passwords
|
* Location of word list to use for generating passwords
|
||||||
*
|
*
|
||||||
|
@ -16,6 +16,8 @@ class SecurityTest extends FunctionalTest {
|
|||||||
|
|
||||||
protected $priorUniqueIdentifierField = null;
|
protected $priorUniqueIdentifierField = null;
|
||||||
|
|
||||||
|
protected $priorRememberUsername = null;
|
||||||
|
|
||||||
public function setUp() {
|
public function setUp() {
|
||||||
// This test assumes that MemberAuthenticator is present and the default
|
// This test assumes that MemberAuthenticator is present and the default
|
||||||
$this->priorAuthenticators = Authenticator::get_authenticators();
|
$this->priorAuthenticators = Authenticator::get_authenticators();
|
||||||
@ -29,6 +31,7 @@ class SecurityTest extends FunctionalTest {
|
|||||||
|
|
||||||
// And that the unique identified field is 'Email'
|
// And that the unique identified field is 'Email'
|
||||||
$this->priorUniqueIdentifierField = Member::config()->unique_identifier_field;
|
$this->priorUniqueIdentifierField = Member::config()->unique_identifier_field;
|
||||||
|
$this->priorRememberUsername = Security::config()->remember_username;
|
||||||
Member::config()->unique_identifier_field = 'Email';
|
Member::config()->unique_identifier_field = 'Email';
|
||||||
|
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
@ -48,6 +51,7 @@ class SecurityTest extends FunctionalTest {
|
|||||||
|
|
||||||
// Restore unique identifier field
|
// Restore unique identifier field
|
||||||
Member::config()->unique_identifier_field = $this->priorUniqueIdentifierField;
|
Member::config()->unique_identifier_field = $this->priorUniqueIdentifierField;
|
||||||
|
Security::config()->remember_username = $this->priorRememberUsername;
|
||||||
|
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
@ -124,6 +128,32 @@ class SecurityTest extends FunctionalTest {
|
|||||||
$this->session()->inst_set('loggedInAs', null);
|
$this->session()->inst_set('loggedInAs', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testLoginUsernamePersists() {
|
||||||
|
// Test that username does not persist
|
||||||
|
$this->session()->inst_set('SessionForms.MemberLoginForm.Email', 'myuser@silverstripe.com');
|
||||||
|
Security::config()->remember_username = false;
|
||||||
|
$this->get(Config::inst()->get('Security', 'login_url'));
|
||||||
|
$items = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm #Email input.text');
|
||||||
|
$this->assertEquals(1, count($items));
|
||||||
|
$this->assertEmpty((string)$items[0]->attributes()->value);
|
||||||
|
$this->assertEquals('off', (string)$items[0]->attributes()->autocomplete);
|
||||||
|
$form = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm');
|
||||||
|
$this->assertEquals(1, count($form));
|
||||||
|
$this->assertEquals('off', (string)$form[0]->attributes()->autocomplete);
|
||||||
|
|
||||||
|
// Test that username does persist when necessary
|
||||||
|
$this->session()->inst_set('SessionForms.MemberLoginForm.Email', 'myuser@silverstripe.com');
|
||||||
|
Security::config()->remember_username = true;
|
||||||
|
$this->get(Config::inst()->get('Security', 'login_url'));
|
||||||
|
$items = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm #Email input.text');
|
||||||
|
$this->assertEquals(1, count($items));
|
||||||
|
$this->assertEquals('myuser@silverstripe.com', (string)$items[0]->attributes()->value);
|
||||||
|
$this->assertNotEquals('off', (string)$items[0]->attributes()->autocomplete);
|
||||||
|
$form = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm');
|
||||||
|
$this->assertEquals(1, count($form));
|
||||||
|
$this->assertNotEquals('off', (string)$form[0]->attributes()->autocomplete);
|
||||||
|
}
|
||||||
|
|
||||||
public function testExternalBackUrlRedirectionDisallowed() {
|
public function testExternalBackUrlRedirectionDisallowed() {
|
||||||
// Test internal relative redirect
|
// Test internal relative redirect
|
||||||
$response = $this->doTestLoginForm('noexpiry@silverstripe.com', '1nitialPassword', 'testpage');
|
$response = $this->doTestLoginForm('noexpiry@silverstripe.com', '1nitialPassword', 'testpage');
|
||||||
|
Loading…
Reference in New Issue
Block a user