mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
NEW Allow admins to require password reset for members
This came from silverstripe/silverstripe-security-extensions
This commit is contained in:
parent
0fee1aa584
commit
8ddedb038e
@ -13,6 +13,7 @@ use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\TestMailer;
|
||||
use SilverStripe\Forms\CheckboxField;
|
||||
use SilverStripe\Forms\ConfirmedPasswordField;
|
||||
use SilverStripe\Forms\DropdownField;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
@ -398,7 +399,40 @@ class Member extends DataObject
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isPasswordExpired()
|
||||
/**
|
||||
* Used to get the value for the reset password on next login checkbox
|
||||
*/
|
||||
public function getRequiresPasswordChangeOnNextLogin(): bool
|
||||
{
|
||||
return $this->isPasswordExpired();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password expiry to "now" to require a change of password next log in
|
||||
*
|
||||
* @param int|null $dataValue 1 is checked, 0/null is not checked {@see CheckboxField::dataValue}
|
||||
*/
|
||||
public function saveRequiresPasswordChangeOnNextLogin(?int $dataValue): static
|
||||
{
|
||||
if (!$this->canEdit()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$currentValue = $this->PasswordExpiry;
|
||||
$currentDate = $this->dbObject('PasswordExpiry');
|
||||
|
||||
if ($dataValue && (!$currentValue || $currentDate->inFuture())) {
|
||||
// Only alter future expiries - this way an admin could see how long ago a password expired still
|
||||
$this->PasswordExpiry = DBDatetime::now()->Rfc2822();
|
||||
} elseif (!$dataValue && $this->isPasswordExpired()) {
|
||||
// Only unset if the expiry date is in the past
|
||||
$this->PasswordExpiry = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isPasswordExpired(): bool
|
||||
{
|
||||
if (!$this->PasswordExpiry) {
|
||||
return false;
|
||||
@ -1363,6 +1397,19 @@ class Member extends DataObject
|
||||
if ($permissionsTab) {
|
||||
$permissionsTab->addExtraClass('readonly');
|
||||
}
|
||||
|
||||
$currentUser = Security::getCurrentUser();
|
||||
// We can allow an admin to require a user to change their password. But:
|
||||
// - Don't show a read only field if the user cannot edit this record
|
||||
// - Don't show if a user views their own profile (just let them reset their own password)
|
||||
if ($currentUser && ($currentUser->ID !== $this->ID) && $this->canEdit()) {
|
||||
$requireNewPassword = CheckboxField::create(
|
||||
'RequiresPasswordChangeOnNextLogin',
|
||||
_t(__CLASS__ . '.RequiresPasswordChangeOnNextLogin', 'Requires password change on next log in')
|
||||
);
|
||||
$fields->insertAfter('Password', $requireNewPassword);
|
||||
$fields->dataFieldByName('Password')->addExtraClass('form-field--no-divider mb-0 pb-0');
|
||||
}
|
||||
});
|
||||
|
||||
return parent::getCMSFields();
|
||||
|
@ -7,6 +7,9 @@ use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\FunctionalTest;
|
||||
use SilverStripe\Forms\CheckboxField;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\Forms\ListboxField;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
@ -402,6 +405,161 @@ class MemberTest extends FunctionalTest
|
||||
$this->assertFalse($member->isPasswordExpired());
|
||||
}
|
||||
|
||||
public function testAdminCanRequirePasswordChangeOnNextLogIn()
|
||||
{
|
||||
/** @var Member&MemberExtension $targetMember */
|
||||
$targetMember = $this->objFromFixture(Member::class, 'someone');
|
||||
$this->logInWithPermission('ADMIN');
|
||||
$field = $targetMember->getCMSFields()->dataFieldByName('RequiresPasswordChangeOnNextLogin');
|
||||
$this->assertNotNull($field);
|
||||
$this->assertInstanceOf(CheckboxField::class, $field, 'The field should be an instance of ' . CheckboxField::class);
|
||||
}
|
||||
|
||||
public function testUserCannotRequireTheirOwnPasswordChangeOnNextLogIn()
|
||||
{
|
||||
/** @var Member&MemberExtension $targetMember */
|
||||
$targetMember = $this->objFromFixture(Member::class, 'someone');
|
||||
$this->logInAs($targetMember);
|
||||
$field = $targetMember->getCMSFields()->dataFieldByName('RequiresPasswordChangeOnNextLogin');
|
||||
$this->assertNull($field);
|
||||
}
|
||||
|
||||
public function testUserCannotRequireOthersToPasswordChangeOnNextLogIn()
|
||||
{
|
||||
/** @var Member&MemberExtension $targetMember */
|
||||
$targetMember = $this->objFromFixture(Member::class, 'anyone');
|
||||
$this->logInAs('someone');
|
||||
$field = $targetMember->getCMSFields()->dataFieldByName('RequiresPasswordChangeOnNextLogin');
|
||||
$this->assertNull($field);
|
||||
}
|
||||
|
||||
public function testCheckingRequiresPasswordChangeOnNextLoginWillSetPasswordExpiryToNow()
|
||||
{
|
||||
$mockDate = '2019-03-02 00:00:00';
|
||||
DBDateTime::set_mock_now($mockDate);
|
||||
|
||||
/** @var Member&MemberExtension $targetMember */
|
||||
$targetMember = $this->objFromFixture(Member::class, 'someone');
|
||||
|
||||
$this->assertNull($targetMember->PasswordExpiry);
|
||||
|
||||
$this->logInWithPermission('ADMIN');
|
||||
$fields = $targetMember->getCMSFields();
|
||||
$form = new Form(null, 'SomeForm', $fields, new FieldList());
|
||||
$field = $fields->dataFieldByName('RequiresPasswordChangeOnNextLogin');
|
||||
$field->setValue(1);
|
||||
$form->saveInto($targetMember);
|
||||
|
||||
$this->assertEquals($mockDate, $targetMember->PasswordExpiry);
|
||||
}
|
||||
|
||||
public function testCheckingPasswordChangeUpdatesFutureExpiriesToNow()
|
||||
{
|
||||
$mockDate = '2019-03-02 00:00:00';
|
||||
DBDateTime::set_mock_now($mockDate);
|
||||
|
||||
/** @var Member&MemberExtension $targetMember */
|
||||
$targetMember = $this->objFromFixture(Member::class, 'willexpire');
|
||||
|
||||
$this->assertTrue($targetMember->dbObject('PasswordExpiry')->inFuture());
|
||||
|
||||
$this->logInWithPermission('ADMIN');
|
||||
$fields = $targetMember->getCMSFields();
|
||||
$form = new Form(null, 'SomeForm', $fields, new FieldList());
|
||||
$field = $fields->dataFieldByName('RequiresPasswordChangeOnNextLogin');
|
||||
$field->setValue(1);
|
||||
$form->saveInto($targetMember);
|
||||
|
||||
$this->assertEquals($mockDate, $targetMember->PasswordExpiry);
|
||||
}
|
||||
|
||||
public function testCheckingPasswordChangeDoesNotAlterPastDates()
|
||||
{
|
||||
$mockDate = '2019-03-02 00:00:00';
|
||||
DBDateTime::set_mock_now($mockDate);
|
||||
|
||||
/** @var Member&MemberExtension $targetMember */
|
||||
$targetMember = $this->objFromFixture(Member::class, 'expired');
|
||||
$originalValue = $targetMember->PasswordExpiry;
|
||||
|
||||
$this->assertTrue($targetMember->dbObject('PasswordExpiry')->inPast());
|
||||
|
||||
$this->logInWithPermission('ADMIN');
|
||||
$fields = $targetMember->getCMSFields();
|
||||
$form = new Form(null, 'SomeForm', $fields, new FieldList());
|
||||
$field = $fields->dataFieldByName('RequiresPasswordChangeOnNextLogin');
|
||||
$field->setValue(1);
|
||||
$form->saveInto($targetMember);
|
||||
|
||||
$this->assertEquals($originalValue, $targetMember->PasswordExpiry);
|
||||
}
|
||||
|
||||
public function testSavingUncheckedPasswordChangeNullsPastDates()
|
||||
{
|
||||
$mockDate = '2019-03-02 00:00:00';
|
||||
DBDateTime::set_mock_now($mockDate);
|
||||
|
||||
/** @var Member&MemberExtension $targetMember */
|
||||
$targetMember = $this->objFromFixture(Member::class, 'expired');
|
||||
|
||||
$this->logInWithPermission('ADMIN');
|
||||
$fields = $targetMember->getCMSFields();
|
||||
$form = new Form(null, 'SomeForm', $fields, new FieldList());
|
||||
$field = $fields->dataFieldByName('RequiresPasswordChangeOnNextLogin');
|
||||
$field->setValue(0);
|
||||
$form->saveInto($targetMember);
|
||||
|
||||
$this->assertNull($targetMember->PasswordExpiry);
|
||||
}
|
||||
|
||||
public function testSavingUncheckedPasswordChangeDoesNotAlterFutureDates()
|
||||
{
|
||||
$mockDate = '2019-03-02 00:00:00';
|
||||
DBDateTime::set_mock_now($mockDate);
|
||||
|
||||
/** @var Member&MemberExtension $targetMember */
|
||||
$targetMember = $this->objFromFixture(Member::class, 'willexpire');
|
||||
$originalValue = $targetMember->PasswordExpiry;
|
||||
|
||||
$this->logInWithPermission('ADMIN');
|
||||
$fields = $targetMember->getCMSFields();
|
||||
$form = new Form(null, 'SomeForm', $fields, new FieldList());
|
||||
$field = $fields->dataFieldByName('RequiresPasswordChangeOnNextLogin');
|
||||
$field->setValue(0);
|
||||
$form->saveInto($targetMember);
|
||||
|
||||
$this->assertNotNull($targetMember->PasswordExpiry);
|
||||
$this->assertEquals($originalValue, $targetMember->PasswordExpiry);
|
||||
}
|
||||
|
||||
public function testSavingChangePasswordOnNextLoginIsNotPossibleIfTheCurrentMemberCannotEditTheMemberBeingSaved()
|
||||
{
|
||||
/** @var Member&MemberExtension $targetMember */
|
||||
$targetMember = $this->objFromFixture(Member::class, 'expired');
|
||||
$originalValue = $targetMember->PasswordExpiry;
|
||||
|
||||
$this->logInAs('someone');
|
||||
$fields = $targetMember->saveRequiresPasswordChangeOnNextLogin(0);
|
||||
|
||||
$this->assertEquals($originalValue, $targetMember->PasswordExpiry);
|
||||
}
|
||||
|
||||
public function testGetRequiresPasswordChangeOnNextLogin()
|
||||
{
|
||||
$this->assertTrue(
|
||||
$this->objFromFixture(Member::class, 'expired')->getRequiresPasswordChangeOnNextLogin(),
|
||||
'PasswordExpiry date in the past should require a change'
|
||||
);
|
||||
$this->assertFalse(
|
||||
$this->objFromFixture(Member::class, 'willexpire')->getRequiresPasswordChangeOnNextLogin(),
|
||||
'PasswordExpiry date in the past should NOT require a change'
|
||||
);
|
||||
$this->assertFalse(
|
||||
$this->objFromFixture(Member::class, 'someone')->getRequiresPasswordChangeOnNextLogin(),
|
||||
'PasswordExpiry is NULL should NOT require a change'
|
||||
);
|
||||
}
|
||||
|
||||
public function testInGroups()
|
||||
{
|
||||
/** @var Member $staffmember */
|
||||
@ -869,7 +1027,7 @@ class MemberTest extends FunctionalTest
|
||||
public function testMap_in_groupsReturnsAll()
|
||||
{
|
||||
$members = Member::map_in_groups();
|
||||
$this->assertEquals(13, $members->count(), 'There are 12 members in the mock plus a fake admin');
|
||||
$this->assertEquals(17, $members->count(), 'There are 16 members in the mock plus a fake admin');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,6 +57,20 @@
|
||||
Surname: User
|
||||
Email: noexpiry@silverstripe.com
|
||||
Password: 1nitialPassword
|
||||
someone:
|
||||
FirstName: 'Someone'
|
||||
Email: 'someone@example.com'
|
||||
anyone:
|
||||
FirstName: 'Anyone'
|
||||
Email: 'anyone@example.com'
|
||||
expired:
|
||||
Firstname: 'Expired'
|
||||
Email: 'expired@example.com'
|
||||
PasswordExpiry: '2018-01-01'
|
||||
willexpire:
|
||||
Firstname: 'William'
|
||||
Email: 'william@example.com'
|
||||
PasswordExpiry: '3018-01-01'
|
||||
staffmember:
|
||||
Email: staffmember@test.com
|
||||
Groups: '=>SilverStripe\Security\Group.staffgroup'
|
||||
|
Loading…
Reference in New Issue
Block a user