diff --git a/lang/en.yml b/lang/en.yml index 8c6d71713..0b8844c32 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -294,6 +294,7 @@ en: CURRENT_PASSWORD: 'Current Password' EDIT_PASSWORD: 'New Password' EMAIL: Email + EMAIL_FAILED: 'There was an error when trying to email you a password reset link.' EMPTYNEWPASSWORD: "The new password can't be empty, please try again" ENTEREMAIL: 'Please enter an email address to get a password reset link.' ERRORLOCKEDOUT2: 'Your account has been temporarily disabled because of too many failed attempts at logging in. Please try again in {count} minutes.' diff --git a/src/Dev/Validation/DatabaseAdminExtension.php b/src/Dev/Validation/DatabaseAdminExtension.php index e8abf24df..9907f1d3c 100644 --- a/src/Dev/Validation/DatabaseAdminExtension.php +++ b/src/Dev/Validation/DatabaseAdminExtension.php @@ -9,7 +9,6 @@ use SilverStripe\ORM\DatabaseAdmin; /** * Hook up static validation to the deb/build process * - * @method DatabaseAdmin getOwner() */ class DatabaseAdminExtension extends Extension { diff --git a/src/ORM/DataObject.php b/src/ORM/DataObject.php index e2760f647..e445db6e0 100644 --- a/src/ORM/DataObject.php +++ b/src/ORM/DataObject.php @@ -99,7 +99,6 @@ use stdClass; * If any public method on this class is prefixed with an underscore, * the results are cached in memory through {@link cachedCall()}. * - * * @property int $ID ID of the DataObject, 0 if the DataObject doesn't exist in database. * @property int $OldID ID of object, if deleted * @property string $Title diff --git a/src/Security/Group.php b/src/Security/Group.php index 7bf3ed7c9..06b283d39 100755 --- a/src/Security/Group.php +++ b/src/Security/Group.php @@ -47,11 +47,12 @@ use SilverStripe\ORM\UnsavedRelationList; * * @property int $ParentID ID of parent group * - * @method Group Parent() Return parent group - * @method HasManyList Permissions() List of group permissions - * @method HasManyList Groups() List of child groups - * @method ManyManyList Roles() List of PermissionRoles * @mixin Hierarchy + * @method HasManyList Groups() + * @method ManyManyList Members() + * @method Group Parent() + * @method HasManyList Permissions() + * @method ManyManyList Roles() */ class Group extends DataObject { diff --git a/src/Security/InheritedPermissionsExtension.php b/src/Security/InheritedPermissionsExtension.php index 291e84bcd..5240246c7 100644 --- a/src/Security/InheritedPermissionsExtension.php +++ b/src/Security/InheritedPermissionsExtension.php @@ -10,10 +10,10 @@ use SilverStripe\ORM\ManyManyList; * * @property string $CanViewType * @property string $CanEditType - * @method ManyManyList ViewerGroups() - * @method ManyManyList EditorGroups() - * @method ManyManyList ViewerMembers() - * @method ManyManyList EditorMembers() + * @method ManyManyList EditorGroups() + * @method ManyManyList EditorMembers() + * @method ManyManyList ViewerGroups() + * @method ManyManyList ViewerMembers() */ class InheritedPermissionsExtension extends DataExtension { diff --git a/src/Security/LoginAttempt.php b/src/Security/LoginAttempt.php index 95eb30f1e..4ffda5c76 100644 --- a/src/Security/LoginAttempt.php +++ b/src/Security/LoginAttempt.php @@ -20,7 +20,7 @@ use SilverStripe\ORM\DataObject; * @property string $IP IP address of user attempting to login * @property int $MemberID ID of the Member * - * @method Member Member() Member object of the user trying to log in + * @method Member Member() */ class LoginAttempt extends DataObject { diff --git a/src/Security/Member.php b/src/Security/Member.php index d24c3d143..079bd32eb 100644 --- a/src/Security/Member.php +++ b/src/Security/Member.php @@ -4,6 +4,7 @@ namespace SilverStripe\Security; use IntlDateFormatter; use InvalidArgumentException; +use Psr\Log\LoggerInterface; use SilverStripe\Admin\LeftAndMain; use SilverStripe\CMS\Controllers\CMSMain; use SilverStripe\Control\Director; @@ -34,15 +35,15 @@ use SilverStripe\ORM\SS_List; use SilverStripe\ORM\UnsavedRelationList; use SilverStripe\ORM\ValidationException; use SilverStripe\ORM\ValidationResult; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mime\Exception\RfcComplianceException; use Closure; use RuntimeException; /** * The member class which represents the users of the system * - * @method HasManyList LoggedPasswords() - * @method HasManyList RememberLoginHashes() * @property string $FirstName * @property string $Surname * @property string $Email @@ -59,6 +60,9 @@ use RuntimeException; * @property int $FailedLoginCount * @property string $DateFormat * @property string $TimeFormat + * @method ManyManyList Groups() + * @method HasManyList LoggedPasswords() + * @method HasManyList RememberLoginHashes() */ class Member extends DataObject { @@ -780,18 +784,24 @@ class Member extends DataObject && static::config()->get('notify_password_change') && $this->isInDB() ) { - $email = Email::create() - ->setHTMLTemplate('SilverStripe\\Control\\Email\\ChangePasswordEmail') - ->setData($this) - ->setTo($this->Email) - ->setSubject(_t( - __CLASS__ . '.SUBJECTPASSWORDCHANGED', - "Your password has been changed", - 'Email subject' - )); + try { + $email = Email::create() + ->setHTMLTemplate('SilverStripe\\Control\\Email\\ChangePasswordEmail') + ->setData($this) + ->setTo($this->Email) + ->setSubject(_t( + __CLASS__ . '.SUBJECTPASSWORDCHANGED', + "Your password has been changed", + 'Email subject' + )); - $this->extend('updateChangedPasswordEmail', $email); - $email->send(); + $this->extend('updateChangedPasswordEmail', $email); + $email->send(); + } catch (TransportExceptionInterface | RfcComplianceException $e) { + /** @var LoggerInterface $logger */ + $logger = Injector::inst()->get(LoggerInterface::class . '.errorhandler'); + $logger->error('Error sending email in ' . __FILE__ . ' line ' . __LINE__ . ": {$e->getMessage()}"); + } } // The test on $this->ID is used for when records are initially created. Note that this only works with diff --git a/src/Security/MemberAuthenticator/LostPasswordHandler.php b/src/Security/MemberAuthenticator/LostPasswordHandler.php index a7a91a5b4..f5cb4538d 100644 --- a/src/Security/MemberAuthenticator/LostPasswordHandler.php +++ b/src/Security/MemberAuthenticator/LostPasswordHandler.php @@ -2,15 +2,19 @@ namespace SilverStripe\Security\MemberAuthenticator; +use Psr\Log\LoggerInterface; use SilverStripe\Control\Controller; use SilverStripe\Control\Email\Email; use SilverStripe\Control\HTTPResponse; use SilverStripe\Control\RequestHandler; use SilverStripe\Core\Convert; +use SilverStripe\Core\Injector\Injector; use SilverStripe\Forms\Form; use SilverStripe\ORM\FieldType\DBField; use SilverStripe\Security\Member; use SilverStripe\Security\Security; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mime\Exception\RfcComplianceException; /** * Handle login requests from MemberLoginForm @@ -173,7 +177,18 @@ class LostPasswordHandler extends RequestHandler if ($member) { $token = $member->generateAutologinTokenAndStoreHash(); - $this->sendEmail($member, $token); + $success = $this->sendEmail($member, $token); + if (!$success) { + $form->sessionMessage( + _t( + Member::class . '.EMAIL_FAILED', + 'There was an error when trying to email you a password reset link.' + ), + 'bad' + ); + + return $this->redirectToLostPassword(); + } } return $this->redirectToSuccess($data); @@ -225,20 +240,28 @@ class LostPasswordHandler extends RequestHandler */ 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); + try { + /** @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); - $member->extend('updateForgotPasswordEmail', $email); - return $email->send(); + $member->extend('updateForgotPasswordEmail', $email); + $email->send(); + return true; + } catch (TransportExceptionInterface | RfcComplianceException $e) { + /** @var LoggerInterface $logger */ + $logger = Injector::inst()->get(LoggerInterface::class . '.errorhandler'); + $logger->error('Error sending email in ' . __FILE__ . ' line ' . __LINE__ . ": {$e->getMessage()}"); + return false; + } } /** diff --git a/src/Security/MemberPassword.php b/src/Security/MemberPassword.php index 751fcd858..8488671f6 100644 --- a/src/Security/MemberPassword.php +++ b/src/Security/MemberPassword.php @@ -11,7 +11,7 @@ use SilverStripe\ORM\DataObject; * @property string $Salt * @property string $PasswordEncryption * @property int $MemberID ID of the Member - * @method Member Member() Owner of the password + * @method Member Member() */ class MemberPassword extends DataObject { diff --git a/src/Security/PermissionRole.php b/src/Security/PermissionRole.php index 92d8a6333..5f96f8770 100644 --- a/src/Security/PermissionRole.php +++ b/src/Security/PermissionRole.php @@ -20,8 +20,8 @@ use SilverStripe\ORM\ManyManyList; * @property string Title * @property string OnlyAdminCanApply * - * @method HasManyList Codes() List of PermissionRoleCode objects - * @method ManyManyList Groups() List of Group objects + * @method HasManyList Codes() + * @method ManyManyList Groups() */ class PermissionRole extends DataObject {