silverstripe /
silverstripe-mfa
| 1 | <?php |
||||
| 2 | |||||
| 3 | declare(strict_types=1); |
||||
| 4 | |||||
| 5 | namespace SilverStripe\MFA\Extension\AccountReset; |
||||
| 6 | |||||
| 7 | use Exception; |
||||
| 8 | use Psr\Log\LoggerInterface; |
||||
| 9 | use SilverStripe\Admin\SecurityAdmin; |
||||
| 10 | use SilverStripe\Control\Controller; |
||||
| 11 | use SilverStripe\Control\Email\Email; |
||||
| 12 | use SilverStripe\Control\HTTPRequest; |
||||
| 13 | use SilverStripe\Control\HTTPResponse; |
||||
| 14 | use SilverStripe\Core\Extension; |
||||
| 15 | use SilverStripe\MFA\Extension\MemberExtension as BaseMFAMemberExtension; |
||||
| 16 | use SilverStripe\MFA\JSONResponse; |
||||
| 17 | use SilverStripe\Security\Member; |
||||
| 18 | use SilverStripe\Security\Permission; |
||||
| 19 | use SilverStripe\Security\Security; |
||||
| 20 | use SilverStripe\Security\SecurityToken; |
||||
| 21 | |||||
| 22 | /** |
||||
| 23 | * This extension is applied to SecurityAdmin to provide an additional endpoint |
||||
| 24 | * for sending account reset requests. |
||||
| 25 | * |
||||
| 26 | * @package SilverStripe\MFA\Extension |
||||
| 27 | * @property SecurityAdmin $owner |
||||
| 28 | */ |
||||
| 29 | class SecurityAdminExtension extends Extension |
||||
| 30 | { |
||||
| 31 | use JSONResponse; |
||||
| 32 | |||||
| 33 | private static $allowed_actions = [ |
||||
|
0 ignored issues
–
show
introduced
by
Loading history...
|
|||||
| 34 | 'reset', |
||||
| 35 | ]; |
||||
| 36 | |||||
| 37 | /** |
||||
| 38 | * @var string[] |
||||
| 39 | */ |
||||
| 40 | private static $dependencies = [ |
||||
|
0 ignored issues
–
show
|
|||||
| 41 | 'Logger' => '%$' . LoggerInterface::class . '.account_reset', |
||||
| 42 | ]; |
||||
| 43 | |||||
| 44 | /** |
||||
| 45 | * @var LoggerInterface |
||||
| 46 | */ |
||||
| 47 | protected $logger; |
||||
| 48 | |||||
| 49 | public function reset(HTTPRequest $request): HTTPResponse |
||||
| 50 | { |
||||
| 51 | if (!$request->isPOST() || !$request->param('ID')) { |
||||
| 52 | return $this->jsonResponse( |
||||
| 53 | [ |
||||
| 54 | 'error' => _t(__CLASS__ . '.BAD_REQUEST', 'Invalid request') |
||||
| 55 | ], |
||||
| 56 | 400 |
||||
| 57 | ); |
||||
| 58 | } |
||||
| 59 | |||||
| 60 | $body = json_decode($request->getBody() ?? '', true); |
||||
| 61 | |||||
| 62 | if (!SecurityToken::inst()->check($body['csrf_token'] ?? null)) { |
||||
| 63 | return $this->jsonResponse( |
||||
| 64 | [ |
||||
| 65 | 'error' => _t(__CLASS__ . '.INVALID_CSRF_TOKEN', 'Invalid or missing CSRF token') |
||||
| 66 | ], |
||||
| 67 | 400 |
||||
| 68 | ); |
||||
| 69 | } |
||||
| 70 | |||||
| 71 | if (!Permission::check(BaseMFAMemberExtension::MFA_ADMINISTER_REGISTERED_METHODS)) { |
||||
| 72 | return $this->jsonResponse( |
||||
| 73 | [ |
||||
| 74 | 'error' => _t( |
||||
| 75 | __CLASS__ . '.INSUFFICIENT_PERMISSIONS', |
||||
| 76 | 'Insufficient permissions to reset user' |
||||
| 77 | ) |
||||
| 78 | ], |
||||
| 79 | 403 |
||||
| 80 | ); |
||||
| 81 | } |
||||
| 82 | |||||
| 83 | /** @var Member $memberToReset */ |
||||
| 84 | $memberToReset = Member::get()->byID($request->param('ID')); |
||||
|
0 ignored issues
–
show
$request->param('ID') of type string is incompatible with the type integer expected by parameter $id of SilverStripe\ORM\DataList::byID().
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 85 | |||||
| 86 | if ($memberToReset === null) { |
||||
| 87 | return $this->jsonResponse( |
||||
| 88 | [ |
||||
| 89 | 'error' => _t( |
||||
| 90 | __CLASS__ . '.INVALID_MEMBER', |
||||
| 91 | 'Requested member for reset not found' |
||||
| 92 | ) |
||||
| 93 | ], |
||||
| 94 | 403 |
||||
| 95 | ); |
||||
| 96 | } |
||||
| 97 | |||||
| 98 | $sent = $this->sendResetEmail($memberToReset); |
||||
| 99 | |||||
| 100 | if (!$sent) { |
||||
| 101 | return $this->jsonResponse( |
||||
| 102 | [ |
||||
| 103 | 'error' => _t( |
||||
| 104 | __CLASS__ . '.EMAIL_NOT_SENT', |
||||
| 105 | 'Email sending failed' |
||||
| 106 | ) |
||||
| 107 | ], |
||||
| 108 | 500 |
||||
| 109 | ); |
||||
| 110 | } |
||||
| 111 | |||||
| 112 | return $this->jsonResponse(['success' => true], 200); |
||||
| 113 | } |
||||
| 114 | |||||
| 115 | /** |
||||
| 116 | * Prepares and attempts to send the Account Reset request email. |
||||
| 117 | * |
||||
| 118 | * @param Member&MemberExtension $member |
||||
| 119 | * @return bool |
||||
| 120 | */ |
||||
| 121 | protected function sendResetEmail($member) |
||||
| 122 | { |
||||
| 123 | // Generate / store / obtain reset token |
||||
| 124 | $token = $member->generateAccountResetTokenAndStoreHash(); |
||||
| 125 | |||||
| 126 | // Create email and fire |
||||
| 127 | try { |
||||
| 128 | $email = Email::create() |
||||
| 129 | ->setHTMLTemplate('SilverStripe\\MFA\\Email\\AccountReset') |
||||
| 130 | ->setData($member) |
||||
| 131 | ->setSubject(_t( |
||||
| 132 | __CLASS__ . '.ACCOUNT_RESET_EMAIL_SUBJECT', |
||||
| 133 | 'Reset your account' |
||||
| 134 | )) |
||||
| 135 | ->addData('AccountResetLink', $this->getAccountResetLink($member, $token)) |
||||
| 136 | ->addData('Member', $member) |
||||
| 137 | ->setFrom(Email::config()->get('admin_email')) |
||||
| 138 | ->setTo($member->Email); |
||||
| 139 | |||||
| 140 | return $email->send(); |
||||
| 141 | } catch (Exception $e) { |
||||
| 142 | $this->logger->info('WARNING: Account Reset Email failed to send: ' . $e->getMessage()); |
||||
| 143 | return false; |
||||
| 144 | } |
||||
| 145 | } |
||||
| 146 | |||||
| 147 | /** |
||||
| 148 | * Generates a link to the Account Reset Handler endpoint to be sent to a Member. |
||||
| 149 | * |
||||
| 150 | * @param Member $member |
||||
| 151 | * @param string $token |
||||
| 152 | * @return string |
||||
| 153 | */ |
||||
| 154 | public function getAccountResetLink(Member $member, string $token): string |
||||
| 155 | { |
||||
| 156 | return Controller::join_links( |
||||
| 157 | Security::singleton()->Link('resetaccount'), |
||||
| 158 | "?m={$member->ID}&t={$token}" |
||||
| 159 | ); |
||||
| 160 | } |
||||
| 161 | |||||
| 162 | /** |
||||
| 163 | * @param LoggerInterface|null $logger |
||||
| 164 | * @return SecurityAdmin |
||||
| 165 | */ |
||||
| 166 | public function setLogger(?LoggerInterface $logger): ?SecurityAdmin |
||||
| 167 | { |
||||
| 168 | $this->logger = $logger; |
||||
| 169 | return $this->owner; |
||||
| 170 | } |
||||
| 171 | } |
||||
| 172 |