'changepassword', ]; /** * @param string $link The URL to recreate this request handler * @param MemberAuthenticator $authenticator */ public function __construct($link, MemberAuthenticator $authenticator) { $this->link = $link; $this->authenticator = $authenticator; parent::__construct(); } /** * Handle the change password request * @todo this could use some spring cleaning * * @return array|HTTPResponse */ public function changepassword() { $request = $this->getRequest(); // Extract the member from the URL. /** @var Member $member */ $member = null; if ($request->getVar('m') !== null) { $member = Member::get()->filter(['ID' => (int)$request->getVar('m')])->first(); } $token = $request->getVar('t'); // Check whether we are merely changin password, or resetting. if ($token !== null && $member && $member->validateAutoLoginToken($token)) { $this->setSessionToken($member, $token); // Redirect to myself, but without the hash in the URL return $this->redirect($this->link); } $session = $this->getRequest()->getSession(); if ($session->get('AutoLoginHash')) { $message = DBField::create_field( 'HTMLFragment', '

' . _t( 'SilverStripe\\Security\\Security.ENTERNEWPASSWORD', 'Please enter a new password.' ) . '

' ); // Subsequent request after the "first load with hash" (see previous if clause). return [ 'Content' => $message, 'Form' => $this->changePasswordForm() ]; } if (Security::getCurrentUser()) { // Logged in user requested a password change form. $message = DBField::create_field( 'HTMLFragment', '

' . _t( 'SilverStripe\\Security\\Security.CHANGEPASSWORDBELOW', 'You can change your password below.' ) . '

' ); return [ 'Content' => $message, 'Form' => $this->changePasswordForm() ]; } // Show a friendly message saying the login token has expired if ($token !== null && $member && !$member->validateAutoLoginToken($token)) { $message = DBField::create_field( 'HTMLFragment', _t( 'SilverStripe\\Security\\Security.NOTERESETLINKINVALID', '

The password reset link is invalid or expired.

' . '

You can request a new one here or change your password after' . ' you logged in.

', [ 'link1' => $this->link('lostpassword'), 'link2' => $this->link('login') ] ) ); return [ 'Content' => $message, ]; } // Someone attempted to go to changepassword without token or being logged in return Security::permissionFailure( Controller::curr(), _t( 'SilverStripe\\Security\\Security.ERRORPASSWORDPERMISSION', 'You must be logged in in order to change your password!' ) ); } /** * @param Member $member * @param string $token */ protected function setSessionToken($member, $token) { // if there is a current member, they should be logged out if ($curMember = Security::getCurrentUser()) { /** @var LogoutHandler $handler */ Injector::inst()->get(IdentityStore::class)->logOut(); } // Store the hash for the change password form. Will be unset after reload within the ChangePasswordForm. $this->getRequest()->getSession()->set('AutoLoginHash', $member->encryptWithUserSettings($token)); } /** * Return a link to this request handler. * The link returned is supplied in the constructor * @param null $action * @return string */ public function link($action = null) { if ($action) { return Controller::join_links($this->link, $action); } return $this->link; } /** * Factory method for the lost password form * * @skipUpgrade * @return ChangePasswordForm Returns the lost password form */ public function changePasswordForm() { return ChangePasswordForm::create( $this, 'ChangePasswordForm' ); } /** * Change the password * * @param array $data The user submitted data * @param ChangePasswordForm $form * @return HTTPResponse */ public function doChangePassword(array $data, $form) { $member = Security::getCurrentUser(); // The user was logged in, check the current password $oldPassword = isset($data['OldPassword']) ? $data['OldPassword'] : null; if ($member && !$this->checkPassword($member, $oldPassword)) { $form->sessionMessage( _t( 'SilverStripe\\Security\\Member.ERRORPASSWORDNOTMATCH', 'Your current password does not match, please try again' ), 'bad' ); // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. return $this->redirectBackToForm(); } $session = $this->getRequest()->getSession(); if (!$member) { if ($session->get('AutoLoginHash')) { $member = Member::member_from_autologinhash($session->get('AutoLoginHash')); } // The user is not logged in and no valid auto login hash is available if (!$member) { $session->clear('AutoLoginHash'); return $this->redirect($this->addBackURLParam(Security::singleton()->Link('login'))); } } // Check the new password if (empty($data['NewPassword1'])) { $form->sessionMessage( _t( 'SilverStripe\\Security\\Member.EMPTYNEWPASSWORD', "The new password can't be empty, please try again" ), 'bad' ); // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. return $this->redirectBackToForm(); } // Fail if passwords do not match if ($data['NewPassword1'] !== $data['NewPassword2']) { $form->sessionMessage( _t( 'SilverStripe\\Security\\Member.ERRORNEWPASSWORD', 'You have entered your new password differently, try again' ), 'bad' ); // redirect back to the form, instead of using redirectBack() which could send the user elsewhere. return $this->redirectBackToForm(); } // Check if the new password is accepted $validationResult = $member->changePassword($data['NewPassword1']); if (!$validationResult->isValid()) { $form->setSessionValidationResult($validationResult); return $this->redirectBackToForm(); } // Clear locked out status $member->LockedOutUntil = null; $member->FailedLoginCount = null; // Clear the members login hashes $member->AutoLoginHash = null; $member->AutoLoginExpired = DBDatetime::create()->now(); $member->write(); if ($member->canLogIn()) { /** @var IdentityStore $identityStore */ $identityStore = Injector::inst()->get(IdentityStore::class); $identityStore->logIn($member, false, $this->getRequest()); } // TODO Add confirmation message to login redirect $session->clear('AutoLoginHash'); // Redirect to backurl $backURL = $this->getBackURL(); if ($backURL // Don't redirect back to itself && $backURL !== Security::singleton()->Link('changepassword') ) { return $this->redirect($backURL); } // Redirect to default location - the login form saying "You are logged in as..." $url = Security::singleton()->Link('login'); return $this->redirect($url); } /** * Something went wrong, go back to the changepassword * * @return HTTPResponse */ public function redirectBackToForm() { // Redirect back to form $url = $this->addBackURLParam(Security::singleton()->Link('changepassword')); return $this->redirect($url); } /** * Check if password is ok * * @param Member $member * @param string $password * @return bool */ protected function checkPassword($member, $password) { if (empty($password)) { return false; } // With a valid user and password, check the password is correct $authenticators = Security::singleton()->getApplicableAuthenticators(Authenticator::CHECK_PASSWORD); foreach ($authenticators as $authenticator) { if (!$authenticator->checkPassword($member, $password)->isValid()) { return false; } } return true; } }