Move LostPasswordHandler in to it's own class.

- Moved the Authenticators from statics to normal
- Moved MemberLoginForm methods to the getFormFields as they make more sense there
- Did some spring-cleaning on the LostPasswordHandler
- Removed the BuildResponse from ChangePasswordHandler after spring cleaning
This commit is contained in:
Simon Erkelens 2017-06-08 19:12:28 +12:00
parent 082db89550
commit 5fce3308b4
5 changed files with 141 additions and 104 deletions

View File

@ -59,7 +59,7 @@ class ChangePasswordHandler extends RequestHandler
* Handle the change password request * Handle the change password request
* @todo this could use some spring cleaning * @todo this could use some spring cleaning
* *
* @return HTTPResponse|DBHTMLText * @return array|HTTPResponse
*/ */
public function changepassword() public function changepassword()
{ {
@ -91,7 +91,10 @@ class ChangePasswordHandler extends RequestHandler
); );
// Subsequent request after the "first load with hash" (see previous if clause). // Subsequent request after the "first load with hash" (see previous if clause).
return $this->buildResponse($message); return [
'Content' => $message,
'Form' => $this->changePasswordForm()
];
} }
if (Security::getCurrentUser()) { if (Security::getCurrentUser()) {
@ -104,12 +107,14 @@ class ChangePasswordHandler extends RequestHandler
) . '</p>' ) . '</p>'
); );
return $this->buildResponse($message); return [
'Content' => $message,
'Form' => $this->changePasswordForm()
];
} }
// Show a friendly message saying the login token has expired // Show a friendly message saying the login token has expired
if ($token !== null && $member && !$member->validateAutoLoginToken($token)) { if ($token !== null && $member && !$member->validateAutoLoginToken($token)) {
$customisedController = Controller::curr()->customise( $message = [
array(
'Content' => DBField::create_field( 'Content' => DBField::create_field(
'HTMLFragment', 'HTMLFragment',
_t( _t(
@ -118,15 +123,16 @@ class ChangePasswordHandler extends RequestHandler
. '<p>You can request a new one <a href="{link1}">here</a> or change your password after' . '<p>You can request a new one <a href="{link1}">here</a> or change your password after'
. ' you <a href="{link2}">logged in</a>.</p>', . ' you <a href="{link2}">logged in</a>.</p>',
[ [
'link1' => $this->Link('lostpassword'), 'link1' => $this->link('lostpassword'),
'link2' => $this->Link('login') 'link2' => $this->link('login')
] ]
) )
) )
) ];
);
return $customisedController->renderWith('changepassword'); return [
'Content' => $message,
];
} }
// Someone attempted to go to changepassword without token or being logged in // Someone attempted to go to changepassword without token or being logged in
@ -139,21 +145,6 @@ class ChangePasswordHandler extends RequestHandler
); );
} }
/**
* @param DBField $message
* @return DBHTMLText
*/
protected function buildResponse($message)
{
$customisedController = Controller::curr()->customise(
[
'Content' => $message,
'Form' => $this->changePasswordForm()
]
);
return $customisedController->renderWith(Security::singleton()->getTemplatesFor('changepassword'));
}
/** /**
* @param Member $member * @param Member $member
@ -204,9 +195,10 @@ class ChangePasswordHandler extends RequestHandler
* Change the password * Change the password
* *
* @param array $data The user submitted data * @param array $data The user submitted data
* @param ChangePasswordForm $form
* @return HTTPResponse * @return HTTPResponse
*/ */
public function doChangePassword(array $data) public function doChangePassword(array $data, $form)
{ {
$member = Security::getCurrentUser(); $member = Security::getCurrentUser();
// The user was logged in, check the current password // The user was logged in, check the current password
@ -215,12 +207,12 @@ class ChangePasswordHandler extends RequestHandler
!$member->checkPassword($data['OldPassword'])->isValid() !$member->checkPassword($data['OldPassword'])->isValid()
) )
) { ) {
$this->form->sessionMessage( $form->sessionMessage(
_t( _t(
'SilverStripe\\Security\\Member.ERRORPASSWORDNOTMATCH', 'SilverStripe\\Security\\Member.ERRORPASSWORDNOTMATCH',
"Your current password does not match, please try again" 'Your current password does not match, please try again'
), ),
"bad" 'bad'
); );
// redirect back to the form, instead of using redirectBack() which could send the user elsewhere. // redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
@ -242,12 +234,12 @@ class ChangePasswordHandler extends RequestHandler
// Check the new password // Check the new password
if (empty($data['NewPassword1'])) { if (empty($data['NewPassword1'])) {
$this->form->sessionMessage( $form->sessionMessage(
_t( _t(
'SilverStripe\\Security\\Member.EMPTYNEWPASSWORD', 'SilverStripe\\Security\\Member.EMPTYNEWPASSWORD',
"The new password can't be empty, please try again" "The new password can't be empty, please try again"
), ),
"bad" 'bad'
); );
// redirect back to the form, instead of using redirectBack() which could send the user elsewhere. // redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
@ -256,12 +248,12 @@ class ChangePasswordHandler extends RequestHandler
// Fail if passwords do not match // Fail if passwords do not match
if ($data['NewPassword1'] !== $data['NewPassword2']) { if ($data['NewPassword1'] !== $data['NewPassword2']) {
$this->form->sessionMessage( $form->sessionMessage(
_t( _t(
'SilverStripe\\Security\\Member.ERRORNEWPASSWORD', 'SilverStripe\\Security\\Member.ERRORNEWPASSWORD',
"You have entered your new password differently, try again" 'You have entered your new password differently, try again'
), ),
"bad" 'bad'
); );
// redirect back to the form, instead of using redirectBack() which could send the user elsewhere. // redirect back to the form, instead of using redirectBack() which could send the user elsewhere.
@ -271,7 +263,7 @@ class ChangePasswordHandler extends RequestHandler
// Check if the new password is accepted // Check if the new password is accepted
$validationResult = $member->changePassword($data['NewPassword1']); $validationResult = $member->changePassword($data['NewPassword1']);
if (!$validationResult->isValid()) { if (!$validationResult->isValid()) {
$this->form->setSessionValidationResult($validationResult); $form->setSessionValidationResult($validationResult);
return $this->redirectBackToForm(); return $this->redirectBackToForm();
} }
@ -303,10 +295,15 @@ class ChangePasswordHandler extends RequestHandler
return $this->redirect($url); return $this->redirect($url);
} }
/**
* Something went wrong, go back to the changepassword
*
* @return HTTPResponse
*/
public function redirectBackToForm() public function redirectBackToForm()
{ {
// Redirect back to form // Redirect back to form
$url = $this->addBackURLParam(CMSSecurity::singleton()->Link('changepassword')); $url = $this->addBackURLParam(Security::singleton()->Link('changepassword'));
return $this->redirect($url); return $this->redirect($url);
} }

View File

@ -0,0 +1,45 @@
<?php
namespace SilverStripe\Security\MemberAuthenticator;
use SilverStripe\Forms\EmailField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\FormAction;
/**
* Class LostPasswordForm handles the requests for lost password form generation
*
* We need the MemberLoginForm for the getFormFields logic.
*/
class LostPasswordForm extends MemberLoginForm
{
/**
* Create a single EmailField form that has the capability
* of using the MemberLoginForm Authenticator
*
* @return FieldList
*/
public function getFormFields()
{
return FieldList::create(
EmailField::create('Email', _t('SilverStripe\\Security\\Member.EMAIL', 'Email'))
);
}
/**
* Give the member a friendly button to push
*
* @return FieldList
*/
public function getFormActions()
{
return FieldList::create(
FormAction::create(
'forgotPassword',
_t('SilverStripe\\Security\\Security.BUTTONSEND', 'Send me the password reset link')
)
);
}
}

View File

@ -135,19 +135,12 @@ class LostPasswordHandler extends RequestHandler
*/ */
public function lostPasswordForm() public function lostPasswordForm()
{ {
return MemberLoginForm::create( return LostPasswordForm::create(
$this, $this,
$this->authenticatorClass, $this->authenticatorClass,
'lostPasswordForm', 'lostPasswordForm',
new FieldList( null,
new EmailField('Email', _t('SilverStripe\\Security\\Member.EMAIL', 'Email')) null,
),
new FieldList(
new FormAction(
'forgotPassword',
_t('SilverStripe\\Security\\Security.BUTTONSEND', 'Send me the password reset link')
)
),
false false
); );
} }
@ -164,21 +157,6 @@ class LostPasswordHandler extends RequestHandler
return $this->redirect($this->addBackURLParam($lostPasswordLink)); return $this->redirect($this->addBackURLParam($lostPasswordLink));
} }
/**
* Log out form handler method
*
* This method is called when the user clicks on "logout" on the form
* created when the parameter <i>$checkCurrentUser</i> of the
* {@link __construct constructor} was set to TRUE and the user was
* currently logged in.
*
* @return HTTPResponse
*/
public function logout()
{
return Security::singleton()->logout();
}
/** /**
* Forgot password form handler method. * Forgot password form handler method.
* Called when the user clicks on "I've lost my password". * Called when the user clicks on "I've lost my password".
@ -189,13 +167,14 @@ class LostPasswordHandler extends RequestHandler
* *
* @skipUpgrade * @skipUpgrade
* @param array $data Submitted data * @param array $data Submitted data
* @param LostPasswordForm $form
* @return HTTPResponse * @return HTTPResponse
*/ */
public function forgotPassword($data) public function forgotPassword($data, $form)
{ {
// Ensure password is given // Ensure password is given
if (empty($data['Email'])) { if (empty($data['Email'])) {
$this->form->sessionMessage( $form->sessionMessage(
_t( _t(
'SilverStripe\\Security\\Member.ENTEREMAIL', 'SilverStripe\\Security\\Member.ENTEREMAIL',
'Please enter an email address to get a password reset link.' 'Please enter an email address to get a password reset link.'
@ -220,17 +199,7 @@ class LostPasswordHandler extends RequestHandler
if ($member) { if ($member) {
$token = $member->generateAutologinTokenAndStoreHash(); $token = $member->generateAutologinTokenAndStoreHash();
Email::create() $this->sendEmail($member, $token);
->setHTMLTemplate('SilverStripe\\Control\\Email\\ForgotPasswordEmail')
->setData($member)
->setSubject(_t(
'SilverStripe\\Security\\Member.SUBJECTPASSWORDRESET',
"Your password reset link",
'Email subject'
))
->addData('PasswordResetLink', Security::getPasswordResetLink($member, $token))
->setTo($member->Email)
->send();
} }
// Avoid information disclosure by displaying the same status, // Avoid information disclosure by displaying the same status,
@ -243,4 +212,26 @@ class LostPasswordHandler extends RequestHandler
return $this->redirect($this->addBackURLParam($link)); return $this->redirect($this->addBackURLParam($link));
} }
/**
* Send the email to the member that requested a reset link
* @param Member $member
* @param string $token
* @return bool
*/
protected function sendEmail($member, $token)
{
/** @var Email $email */
$email = Email::create()
->setHTMLTemplate('SilverStripe\\Control\\Email\\ForgotPasswordEmail')
->setData($member)
->setSubject(_t(
'SilverStripe\\Security\\Member.SUBJECTPASSWORDRESET',
"Your password reset link",
'Email subject'
))
->addData('PasswordResetLink', Security::getPasswordResetLink($member, $token))
->setTo($member->Email);
return $email->send();
}
} }

View File

@ -74,6 +74,7 @@ class MemberLoginForm extends BaseLoginForm
$checkCurrentUser = true $checkCurrentUser = true
) { ) {
$this->controller = $controller;
$this->authenticator_class = $authenticatorClass; $this->authenticator_class = $authenticatorClass;
$customCSS = project() . '/css/member_login.css'; $customCSS = project() . '/css/member_login.css';
@ -81,20 +82,17 @@ class MemberLoginForm extends BaseLoginForm
Requirements::css($customCSS); Requirements::css($customCSS);
} }
if ($controller->request->getVar('BackURL')) {
$backURL = $controller->request->getVar('BackURL');
} else {
$backURL = Session::get('BackURL');
}
if ($checkCurrentUser && Security::getCurrentUser()) { if ($checkCurrentUser && Security::getCurrentUser()) {
// @todo find a more elegant way to handle this // @todo find a more elegant way to handle this
$logoutAction = Security::logout_url(); $logoutAction = Security::logout_url();
$fields = FieldList::create( $fields = FieldList::create(
HiddenField::create("AuthenticationMethod", null, $this->authenticator_class, $this) HiddenField::create('AuthenticationMethod', null, $this->authenticator_class, $this)
); );
$actions = FieldList::create( $actions = FieldList::create(
FormAction::create("logout", _t('SilverStripe\\Security\\Member.BUTTONLOGINOTHER', "Log in as someone else")) FormAction::create('logout', _t(
'SilverStripe\\Security\\Member.BUTTONLOGINOTHER',
'Log in as someone else'
))
); );
} else { } else {
if (!$fields) { if (!$fields) {
@ -105,10 +103,6 @@ class MemberLoginForm extends BaseLoginForm
} }
} }
if (isset($backURL)) {
$fields->push(HiddenField::create('BackURL', 'BackURL', $backURL));
}
// Reduce attack surface by enforcing POST requests // Reduce attack surface by enforcing POST requests
$this->setFormMethod('POST', true); $this->setFormMethod('POST', true);
@ -127,6 +121,12 @@ class MemberLoginForm extends BaseLoginForm
*/ */
protected function getFormFields() protected function getFormFields()
{ {
if ($this->controller->request->getVar('BackURL')) {
$backURL = $this->controller->request->getVar('BackURL');
} else {
$backURL = Session::get('BackURL');
}
$label = Member::singleton()->fieldLabel(Member::config()->unique_identifier_field); $label = Member::singleton()->fieldLabel(Member::config()->unique_identifier_field);
$fields = FieldList::create( $fields = FieldList::create(
HiddenField::create("AuthenticationMethod", null, $this->authenticator_class, $this), HiddenField::create("AuthenticationMethod", null, $this->authenticator_class, $this),
@ -138,7 +138,7 @@ class MemberLoginForm extends BaseLoginForm
); );
$emailField->setAttribute('autofocus', 'true'); $emailField->setAttribute('autofocus', 'true');
if (Security::config()->remember_username) { if (Security::config()->get('remember_username')) {
$emailField->setValue(Session::get('SessionForms.MemberLoginForm.Email')); $emailField->setValue(Session::get('SessionForms.MemberLoginForm.Email'));
} else { } else {
// Some browsers won't respect this attribute unless it's added to the form // Some browsers won't respect this attribute unless it's added to the form
@ -160,6 +160,10 @@ class MemberLoginForm extends BaseLoginForm
); );
} }
if (isset($backURL)) {
$fields->push(HiddenField::create('BackURL', 'BackURL', $backURL));
}
return $fields; return $fields;
} }

View File

@ -214,7 +214,7 @@ class Security extends Controller implements TemplateGlobalProvider
/** /**
* @var Authenticator[] available authenticators * @var Authenticator[] available authenticators
*/ */
private static $authenticators = []; private $authenticators = [];
/** /**
* @var Member Currently logged in user (if available) * @var Member Currently logged in user (if available)
@ -224,17 +224,17 @@ class Security extends Controller implements TemplateGlobalProvider
/** /**
* @return array * @return array
*/ */
public static function getAuthenticators() public function getAuthenticators()
{ {
return self::$authenticators; return $this->authenticators;
} }
/** /**
* @param array|Authenticator $authenticators * @param array|Authenticator $authenticators
*/ */
public static function setAuthenticators(array $authenticators) public function setAuthenticators(array $authenticators)
{ {
self::$authenticators = $authenticators; $this->authenticators = $authenticators;
} }
/** /**
@ -274,7 +274,7 @@ class Security extends Controller implements TemplateGlobalProvider
*/ */
protected function getAuthenticator($name = 'default') protected function getAuthenticator($name = 'default')
{ {
$authenticators = static::$authenticators; $authenticators = $this->authenticators;
if (isset($authenticators[$name])) { if (isset($authenticators[$name])) {
return $authenticators[$name]; return $authenticators[$name];
@ -291,7 +291,7 @@ class Security extends Controller implements TemplateGlobalProvider
*/ */
public function getApplicableAuthenticators($service = Authenticator::LOGIN) public function getApplicableAuthenticators($service = Authenticator::LOGIN)
{ {
$authenticators = static::$authenticators; $authenticators = $this->authenticators;
/** @var Authenticator $class */ /** @var Authenticator $class */
foreach ($authenticators as $name => $class) { foreach ($authenticators as $name => $class) {
@ -312,7 +312,7 @@ class Security extends Controller implements TemplateGlobalProvider
*/ */
public function hasAuthenticator($authenticator) public function hasAuthenticator($authenticator)
{ {
$authenticators = static::$authenticators; $authenticators = $this->authenticators;
return !empty($authenticators[$authenticator]); return !empty($authenticators[$authenticator]);
} }