'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 HTTPResponse|DBHTMLText */ 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); } 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 $this->buildResponse($message); } 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 $this->buildResponse($message); } // Show a friendly message saying the login token has expired if ($token !== null && $member && !$member->validateAutoLoginToken($token)) { $customisedController = Controller::curr()->customise( array( 'Content' => 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 $customisedController->renderWith('changepassword'); } // 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 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 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. Session::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 * @return HTTPResponse */ public function doChangePassword(array $data) { $member = Security::getCurrentUser(); // The user was logged in, check the current password if ($member && ( empty($data['OldPassword']) || !$member->checkPassword($data['OldPassword'])->isValid() ) ) { $this->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(); } 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'])) { $this->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']) { $this->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()) { $this->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()->isValid()) { Injector::inst()->get(IdentityStore::class)->logIn($member, false, $this->getRequest()); } // TODO Add confirmation message to login redirect Session::clear('AutoLoginHash'); // Redirect to backurl $backURL = $this->getBackURL(); if ($backURL) { 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); } public function redirectBackToForm() { // Redirect back to form $url = $this->addBackURLParam(CMSSecurity::singleton()->Link('changepassword')); return $this->redirect($url); } }