Passed
Push — master ( 1d8b09...436f7b )
by Roeland
25:45 queued 13:05
created
core/Controller/LostController.php 1 patch
Indentation   +340 added lines, -340 removed lines patch added patch discarded remove patch
@@ -64,344 +64,344 @@
 block discarded – undo
64 64
  * @package OC\Core\Controller
65 65
  */
66 66
 class LostController extends Controller {
67
-	/** @var IURLGenerator */
68
-	protected $urlGenerator;
69
-	/** @var IUserManager */
70
-	protected $userManager;
71
-	/** @var Defaults */
72
-	protected $defaults;
73
-	/** @var IL10N */
74
-	protected $l10n;
75
-	/** @var string */
76
-	protected $from;
77
-	/** @var IManager */
78
-	protected $encryptionManager;
79
-	/** @var IConfig */
80
-	protected $config;
81
-	/** @var ISecureRandom */
82
-	protected $secureRandom;
83
-	/** @var IMailer */
84
-	protected $mailer;
85
-	/** @var ITimeFactory */
86
-	protected $timeFactory;
87
-	/** @var ICrypto */
88
-	protected $crypto;
89
-	/** @var ILogger */
90
-	private $logger;
91
-	/** @var Manager */
92
-	private $twoFactorManager;
93
-	/** @var IInitialStateService */
94
-	private $initialStateService;
95
-
96
-	/**
97
-	 * @param string $appName
98
-	 * @param IRequest $request
99
-	 * @param IURLGenerator $urlGenerator
100
-	 * @param IUserManager $userManager
101
-	 * @param Defaults $defaults
102
-	 * @param IL10N $l10n
103
-	 * @param IConfig $config
104
-	 * @param ISecureRandom $secureRandom
105
-	 * @param string $defaultMailAddress
106
-	 * @param IManager $encryptionManager
107
-	 * @param IMailer $mailer
108
-	 * @param ITimeFactory $timeFactory
109
-	 * @param ICrypto $crypto
110
-	 */
111
-	public function __construct($appName,
112
-								IRequest $request,
113
-								IURLGenerator $urlGenerator,
114
-								IUserManager $userManager,
115
-								Defaults $defaults,
116
-								IL10N $l10n,
117
-								IConfig $config,
118
-								ISecureRandom $secureRandom,
119
-								$defaultMailAddress,
120
-								IManager $encryptionManager,
121
-								IMailer $mailer,
122
-								ITimeFactory $timeFactory,
123
-								ICrypto $crypto,
124
-								ILogger $logger,
125
-								Manager $twoFactorManager,
126
-								IInitialStateService $initialStateService) {
127
-		parent::__construct($appName, $request);
128
-		$this->urlGenerator = $urlGenerator;
129
-		$this->userManager = $userManager;
130
-		$this->defaults = $defaults;
131
-		$this->l10n = $l10n;
132
-		$this->secureRandom = $secureRandom;
133
-		$this->from = $defaultMailAddress;
134
-		$this->encryptionManager = $encryptionManager;
135
-		$this->config = $config;
136
-		$this->mailer = $mailer;
137
-		$this->timeFactory = $timeFactory;
138
-		$this->crypto = $crypto;
139
-		$this->logger = $logger;
140
-		$this->twoFactorManager = $twoFactorManager;
141
-		$this->initialStateService = $initialStateService;
142
-	}
143
-
144
-	/**
145
-	 * Someone wants to reset their password:
146
-	 *
147
-	 * @PublicPage
148
-	 * @NoCSRFRequired
149
-	 *
150
-	 * @param string $token
151
-	 * @param string $userId
152
-	 * @return TemplateResponse
153
-	 */
154
-	public function resetform($token, $userId) {
155
-		if ($this->config->getSystemValue('lost_password_link', '') !== '') {
156
-			return new TemplateResponse('core', 'error', [
157
-					'errors' => [['error' => $this->l10n->t('Password reset is disabled')]]
158
-				],
159
-				'guest'
160
-			);
161
-		}
162
-
163
-		try {
164
-			$this->checkPasswordResetToken($token, $userId);
165
-		} catch (\Exception $e) {
166
-			return new TemplateResponse(
167
-				'core', 'error', [
168
-					"errors" => array(array("error" => $e->getMessage()))
169
-				],
170
-				'guest'
171
-			);
172
-		}
173
-		$this->initialStateService->provideInitialState('core', 'resetPasswordUser', $userId);
174
-		$this->initialStateService->provideInitialState('core', 'resetPasswordTarget',
175
-			$this->urlGenerator->linkToRouteAbsolute('core.lost.setPassword', ['userId' => $userId, 'token' => $token])
176
-		);
177
-
178
-		return new TemplateResponse(
179
-			'core',
180
-			'login',
181
-			[],
182
-			'guest'
183
-		);
184
-	}
185
-
186
-	/**
187
-	 * @param string $token
188
-	 * @param string $userId
189
-	 * @throws \Exception
190
-	 */
191
-	protected function checkPasswordResetToken($token, $userId) {
192
-		$user = $this->userManager->get($userId);
193
-		if($user === null || !$user->isEnabled()) {
194
-			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
195
-		}
196
-
197
-		try {
198
-			$encryptedToken = $this->config->getUserValue($userId, 'core', 'lostpassword', null);
199
-			$mailAddress = !is_null($user->getEMailAddress()) ? $user->getEMailAddress() : '';
200
-			$decryptedToken = $this->crypto->decrypt($encryptedToken, $mailAddress.$this->config->getSystemValue('secret'));
201
-		} catch (\Exception $e) {
202
-			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
203
-		}
204
-
205
-		$splittedToken = explode(':', $decryptedToken);
206
-		if(count($splittedToken) !== 2) {
207
-			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
208
-		}
209
-
210
-		if ($splittedToken[0] < ($this->timeFactory->getTime() - 60*60*24*7) ||
211
-			$user->getLastLogin() > $splittedToken[0]) {
212
-			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is expired'));
213
-		}
214
-
215
-		if (!hash_equals($splittedToken[1], $token)) {
216
-			throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
217
-		}
218
-	}
219
-
220
-	/**
221
-	 * @param $message
222
-	 * @param array $additional
223
-	 * @return array
224
-	 */
225
-	private function error($message, array $additional=array()) {
226
-		return array_merge(array('status' => 'error', 'msg' => $message), $additional);
227
-	}
228
-
229
-	/**
230
-	 * @param array $data
231
-	 * @return array
232
-	 */
233
-	private function success($data = []) {
234
-		return array_merge($data, ['status'=>'success']);
235
-	}
236
-
237
-	/**
238
-	 * @PublicPage
239
-	 * @BruteForceProtection(action=passwordResetEmail)
240
-	 * @AnonRateThrottle(limit=10, period=300)
241
-	 *
242
-	 * @param string $user
243
-	 * @return JSONResponse
244
-	 */
245
-	public function email($user){
246
-		if ($this->config->getSystemValue('lost_password_link', '') !== '') {
247
-			return new JSONResponse($this->error($this->l10n->t('Password reset is disabled')));
248
-		}
249
-
250
-		\OCP\Util::emitHook(
251
-			'\OCA\Files_Sharing\API\Server2Server',
252
-			'preLoginNameUsedAsUserName',
253
-			['uid' => &$user]
254
-		);
255
-
256
-		// FIXME: use HTTP error codes
257
-		try {
258
-			$this->sendEmail($user);
259
-		} catch (ResetPasswordException $e) {
260
-			// Ignore the error since we do not want to leak this info
261
-			$this->logger->warning('Could not send password reset email: ' . $e->getMessage());
262
-		} catch (\Exception $e) {
263
-			$this->logger->logException($e);
264
-		}
265
-
266
-		$response = new JSONResponse($this->success());
267
-		$response->throttle();
268
-		return $response;
269
-	}
270
-
271
-	/**
272
-	 * @PublicPage
273
-	 * @param string $token
274
-	 * @param string $userId
275
-	 * @param string $password
276
-	 * @param boolean $proceed
277
-	 * @return array
278
-	 */
279
-	public function setPassword($token, $userId, $password, $proceed) {
280
-		if ($this->config->getSystemValue('lost_password_link', '') !== '') {
281
-			return $this->error($this->l10n->t('Password reset is disabled'));
282
-		}
283
-
284
-		if ($this->encryptionManager->isEnabled() && !$proceed) {
285
-			$encryptionModules = $this->encryptionManager->getEncryptionModules();
286
-			foreach ($encryptionModules as $module) {
287
-				/** @var IEncryptionModule $instance */
288
-				$instance = call_user_func($module['callback']);
289
-				// this way we can find out whether per-user keys are used or a system wide encryption key
290
-				if ($instance->needDetailedAccessList()) {
291
-					return $this->error('', array('encryption' => true));
292
-				}
293
-			}
294
-		}
295
-
296
-		try {
297
-			$this->checkPasswordResetToken($token, $userId);
298
-			$user = $this->userManager->get($userId);
299
-
300
-			\OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'pre_passwordReset', array('uid' => $userId, 'password' => $password));
301
-
302
-			if (!$user->setPassword($password)) {
303
-				throw new \Exception();
304
-			}
305
-
306
-			\OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'post_passwordReset', array('uid' => $userId, 'password' => $password));
307
-
308
-			$this->twoFactorManager->clearTwoFactorPending($userId);
309
-
310
-			$this->config->deleteUserValue($userId, 'core', 'lostpassword');
311
-			@\OC::$server->getUserSession()->unsetMagicInCookie();
312
-		} catch (HintException $e){
313
-			return $this->error($e->getHint());
314
-		} catch (\Exception $e){
315
-			return $this->error($e->getMessage());
316
-		}
317
-
318
-		return $this->success(['user' => $userId]);
319
-	}
320
-
321
-	/**
322
-	 * @param string $input
323
-	 * @throws ResetPasswordException
324
-	 * @throws \OCP\PreConditionNotMetException
325
-	 */
326
-	protected function sendEmail($input) {
327
-		$user = $this->findUserByIdOrMail($input);
328
-		$email = $user->getEMailAddress();
329
-
330
-		if (empty($email)) {
331
-			throw new ResetPasswordException('Could not send reset e-mail since there is no email for username ' . $input);
332
-		}
333
-
334
-		// Generate the token. It is stored encrypted in the database with the
335
-		// secret being the users' email address appended with the system secret.
336
-		// This makes the token automatically invalidate once the user changes
337
-		// their email address.
338
-		$token = $this->secureRandom->generate(
339
-			21,
340
-			ISecureRandom::CHAR_DIGITS.
341
-			ISecureRandom::CHAR_LOWER.
342
-			ISecureRandom::CHAR_UPPER
343
-		);
344
-		$tokenValue = $this->timeFactory->getTime() .':'. $token;
345
-		$encryptedValue = $this->crypto->encrypt($tokenValue, $email . $this->config->getSystemValue('secret'));
346
-		$this->config->setUserValue($user->getUID(), 'core', 'lostpassword', $encryptedValue);
347
-
348
-		$link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', array('userId' => $user->getUID(), 'token' => $token));
349
-
350
-		$emailTemplate = $this->mailer->createEMailTemplate('core.ResetPassword', [
351
-			'link' => $link,
352
-		]);
353
-
354
-		$emailTemplate->setSubject($this->l10n->t('%s password reset', [$this->defaults->getName()]));
355
-		$emailTemplate->addHeader();
356
-		$emailTemplate->addHeading($this->l10n->t('Password reset'));
357
-
358
-		$emailTemplate->addBodyText(
359
-			htmlspecialchars($this->l10n->t('Click the following button to reset your password. If you have not requested the password reset, then ignore this email.')),
360
-			$this->l10n->t('Click the following link to reset your password. If you have not requested the password reset, then ignore this email.')
361
-		);
362
-
363
-		$emailTemplate->addBodyButton(
364
-			htmlspecialchars($this->l10n->t('Reset your password')),
365
-			$link,
366
-			false
367
-		);
368
-		$emailTemplate->addFooter();
369
-
370
-		try {
371
-			$message = $this->mailer->createMessage();
372
-			$message->setTo([$email => $user->getUID()]);
373
-			$message->setFrom([$this->from => $this->defaults->getName()]);
374
-			$message->useTemplate($emailTemplate);
375
-			$this->mailer->send($message);
376
-		} catch (\Exception $e) {
377
-			// Log the exception and continue
378
-			$this->logger->logException($e);
379
-		}
380
-	}
381
-
382
-	/**
383
-	 * @param string $input
384
-	 * @return IUser
385
-	 * @throws ResetPasswordException
386
-	 */
387
-	protected function findUserByIdOrMail($input) {
388
-		$user = $this->userManager->get($input);
389
-		if ($user instanceof IUser) {
390
-			if (!$user->isEnabled()) {
391
-				throw new ResetPasswordException('User is disabled');
392
-			}
393
-
394
-			return $user;
395
-		}
396
-
397
-		$users = array_filter($this->userManager->getByEmail($input), function (IUser $user) {
398
-			return $user->isEnabled();
399
-		});
400
-
401
-		if (count($users) === 1) {
402
-			return reset($users);
403
-		}
404
-
405
-		throw new ResetPasswordException('Could not find user');
406
-	}
67
+    /** @var IURLGenerator */
68
+    protected $urlGenerator;
69
+    /** @var IUserManager */
70
+    protected $userManager;
71
+    /** @var Defaults */
72
+    protected $defaults;
73
+    /** @var IL10N */
74
+    protected $l10n;
75
+    /** @var string */
76
+    protected $from;
77
+    /** @var IManager */
78
+    protected $encryptionManager;
79
+    /** @var IConfig */
80
+    protected $config;
81
+    /** @var ISecureRandom */
82
+    protected $secureRandom;
83
+    /** @var IMailer */
84
+    protected $mailer;
85
+    /** @var ITimeFactory */
86
+    protected $timeFactory;
87
+    /** @var ICrypto */
88
+    protected $crypto;
89
+    /** @var ILogger */
90
+    private $logger;
91
+    /** @var Manager */
92
+    private $twoFactorManager;
93
+    /** @var IInitialStateService */
94
+    private $initialStateService;
95
+
96
+    /**
97
+     * @param string $appName
98
+     * @param IRequest $request
99
+     * @param IURLGenerator $urlGenerator
100
+     * @param IUserManager $userManager
101
+     * @param Defaults $defaults
102
+     * @param IL10N $l10n
103
+     * @param IConfig $config
104
+     * @param ISecureRandom $secureRandom
105
+     * @param string $defaultMailAddress
106
+     * @param IManager $encryptionManager
107
+     * @param IMailer $mailer
108
+     * @param ITimeFactory $timeFactory
109
+     * @param ICrypto $crypto
110
+     */
111
+    public function __construct($appName,
112
+                                IRequest $request,
113
+                                IURLGenerator $urlGenerator,
114
+                                IUserManager $userManager,
115
+                                Defaults $defaults,
116
+                                IL10N $l10n,
117
+                                IConfig $config,
118
+                                ISecureRandom $secureRandom,
119
+                                $defaultMailAddress,
120
+                                IManager $encryptionManager,
121
+                                IMailer $mailer,
122
+                                ITimeFactory $timeFactory,
123
+                                ICrypto $crypto,
124
+                                ILogger $logger,
125
+                                Manager $twoFactorManager,
126
+                                IInitialStateService $initialStateService) {
127
+        parent::__construct($appName, $request);
128
+        $this->urlGenerator = $urlGenerator;
129
+        $this->userManager = $userManager;
130
+        $this->defaults = $defaults;
131
+        $this->l10n = $l10n;
132
+        $this->secureRandom = $secureRandom;
133
+        $this->from = $defaultMailAddress;
134
+        $this->encryptionManager = $encryptionManager;
135
+        $this->config = $config;
136
+        $this->mailer = $mailer;
137
+        $this->timeFactory = $timeFactory;
138
+        $this->crypto = $crypto;
139
+        $this->logger = $logger;
140
+        $this->twoFactorManager = $twoFactorManager;
141
+        $this->initialStateService = $initialStateService;
142
+    }
143
+
144
+    /**
145
+     * Someone wants to reset their password:
146
+     *
147
+     * @PublicPage
148
+     * @NoCSRFRequired
149
+     *
150
+     * @param string $token
151
+     * @param string $userId
152
+     * @return TemplateResponse
153
+     */
154
+    public function resetform($token, $userId) {
155
+        if ($this->config->getSystemValue('lost_password_link', '') !== '') {
156
+            return new TemplateResponse('core', 'error', [
157
+                    'errors' => [['error' => $this->l10n->t('Password reset is disabled')]]
158
+                ],
159
+                'guest'
160
+            );
161
+        }
162
+
163
+        try {
164
+            $this->checkPasswordResetToken($token, $userId);
165
+        } catch (\Exception $e) {
166
+            return new TemplateResponse(
167
+                'core', 'error', [
168
+                    "errors" => array(array("error" => $e->getMessage()))
169
+                ],
170
+                'guest'
171
+            );
172
+        }
173
+        $this->initialStateService->provideInitialState('core', 'resetPasswordUser', $userId);
174
+        $this->initialStateService->provideInitialState('core', 'resetPasswordTarget',
175
+            $this->urlGenerator->linkToRouteAbsolute('core.lost.setPassword', ['userId' => $userId, 'token' => $token])
176
+        );
177
+
178
+        return new TemplateResponse(
179
+            'core',
180
+            'login',
181
+            [],
182
+            'guest'
183
+        );
184
+    }
185
+
186
+    /**
187
+     * @param string $token
188
+     * @param string $userId
189
+     * @throws \Exception
190
+     */
191
+    protected function checkPasswordResetToken($token, $userId) {
192
+        $user = $this->userManager->get($userId);
193
+        if($user === null || !$user->isEnabled()) {
194
+            throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
195
+        }
196
+
197
+        try {
198
+            $encryptedToken = $this->config->getUserValue($userId, 'core', 'lostpassword', null);
199
+            $mailAddress = !is_null($user->getEMailAddress()) ? $user->getEMailAddress() : '';
200
+            $decryptedToken = $this->crypto->decrypt($encryptedToken, $mailAddress.$this->config->getSystemValue('secret'));
201
+        } catch (\Exception $e) {
202
+            throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
203
+        }
204
+
205
+        $splittedToken = explode(':', $decryptedToken);
206
+        if(count($splittedToken) !== 2) {
207
+            throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
208
+        }
209
+
210
+        if ($splittedToken[0] < ($this->timeFactory->getTime() - 60*60*24*7) ||
211
+            $user->getLastLogin() > $splittedToken[0]) {
212
+            throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is expired'));
213
+        }
214
+
215
+        if (!hash_equals($splittedToken[1], $token)) {
216
+            throw new \Exception($this->l10n->t('Couldn\'t reset password because the token is invalid'));
217
+        }
218
+    }
219
+
220
+    /**
221
+     * @param $message
222
+     * @param array $additional
223
+     * @return array
224
+     */
225
+    private function error($message, array $additional=array()) {
226
+        return array_merge(array('status' => 'error', 'msg' => $message), $additional);
227
+    }
228
+
229
+    /**
230
+     * @param array $data
231
+     * @return array
232
+     */
233
+    private function success($data = []) {
234
+        return array_merge($data, ['status'=>'success']);
235
+    }
236
+
237
+    /**
238
+     * @PublicPage
239
+     * @BruteForceProtection(action=passwordResetEmail)
240
+     * @AnonRateThrottle(limit=10, period=300)
241
+     *
242
+     * @param string $user
243
+     * @return JSONResponse
244
+     */
245
+    public function email($user){
246
+        if ($this->config->getSystemValue('lost_password_link', '') !== '') {
247
+            return new JSONResponse($this->error($this->l10n->t('Password reset is disabled')));
248
+        }
249
+
250
+        \OCP\Util::emitHook(
251
+            '\OCA\Files_Sharing\API\Server2Server',
252
+            'preLoginNameUsedAsUserName',
253
+            ['uid' => &$user]
254
+        );
255
+
256
+        // FIXME: use HTTP error codes
257
+        try {
258
+            $this->sendEmail($user);
259
+        } catch (ResetPasswordException $e) {
260
+            // Ignore the error since we do not want to leak this info
261
+            $this->logger->warning('Could not send password reset email: ' . $e->getMessage());
262
+        } catch (\Exception $e) {
263
+            $this->logger->logException($e);
264
+        }
265
+
266
+        $response = new JSONResponse($this->success());
267
+        $response->throttle();
268
+        return $response;
269
+    }
270
+
271
+    /**
272
+     * @PublicPage
273
+     * @param string $token
274
+     * @param string $userId
275
+     * @param string $password
276
+     * @param boolean $proceed
277
+     * @return array
278
+     */
279
+    public function setPassword($token, $userId, $password, $proceed) {
280
+        if ($this->config->getSystemValue('lost_password_link', '') !== '') {
281
+            return $this->error($this->l10n->t('Password reset is disabled'));
282
+        }
283
+
284
+        if ($this->encryptionManager->isEnabled() && !$proceed) {
285
+            $encryptionModules = $this->encryptionManager->getEncryptionModules();
286
+            foreach ($encryptionModules as $module) {
287
+                /** @var IEncryptionModule $instance */
288
+                $instance = call_user_func($module['callback']);
289
+                // this way we can find out whether per-user keys are used or a system wide encryption key
290
+                if ($instance->needDetailedAccessList()) {
291
+                    return $this->error('', array('encryption' => true));
292
+                }
293
+            }
294
+        }
295
+
296
+        try {
297
+            $this->checkPasswordResetToken($token, $userId);
298
+            $user = $this->userManager->get($userId);
299
+
300
+            \OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'pre_passwordReset', array('uid' => $userId, 'password' => $password));
301
+
302
+            if (!$user->setPassword($password)) {
303
+                throw new \Exception();
304
+            }
305
+
306
+            \OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'post_passwordReset', array('uid' => $userId, 'password' => $password));
307
+
308
+            $this->twoFactorManager->clearTwoFactorPending($userId);
309
+
310
+            $this->config->deleteUserValue($userId, 'core', 'lostpassword');
311
+            @\OC::$server->getUserSession()->unsetMagicInCookie();
312
+        } catch (HintException $e){
313
+            return $this->error($e->getHint());
314
+        } catch (\Exception $e){
315
+            return $this->error($e->getMessage());
316
+        }
317
+
318
+        return $this->success(['user' => $userId]);
319
+    }
320
+
321
+    /**
322
+     * @param string $input
323
+     * @throws ResetPasswordException
324
+     * @throws \OCP\PreConditionNotMetException
325
+     */
326
+    protected function sendEmail($input) {
327
+        $user = $this->findUserByIdOrMail($input);
328
+        $email = $user->getEMailAddress();
329
+
330
+        if (empty($email)) {
331
+            throw new ResetPasswordException('Could not send reset e-mail since there is no email for username ' . $input);
332
+        }
333
+
334
+        // Generate the token. It is stored encrypted in the database with the
335
+        // secret being the users' email address appended with the system secret.
336
+        // This makes the token automatically invalidate once the user changes
337
+        // their email address.
338
+        $token = $this->secureRandom->generate(
339
+            21,
340
+            ISecureRandom::CHAR_DIGITS.
341
+            ISecureRandom::CHAR_LOWER.
342
+            ISecureRandom::CHAR_UPPER
343
+        );
344
+        $tokenValue = $this->timeFactory->getTime() .':'. $token;
345
+        $encryptedValue = $this->crypto->encrypt($tokenValue, $email . $this->config->getSystemValue('secret'));
346
+        $this->config->setUserValue($user->getUID(), 'core', 'lostpassword', $encryptedValue);
347
+
348
+        $link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', array('userId' => $user->getUID(), 'token' => $token));
349
+
350
+        $emailTemplate = $this->mailer->createEMailTemplate('core.ResetPassword', [
351
+            'link' => $link,
352
+        ]);
353
+
354
+        $emailTemplate->setSubject($this->l10n->t('%s password reset', [$this->defaults->getName()]));
355
+        $emailTemplate->addHeader();
356
+        $emailTemplate->addHeading($this->l10n->t('Password reset'));
357
+
358
+        $emailTemplate->addBodyText(
359
+            htmlspecialchars($this->l10n->t('Click the following button to reset your password. If you have not requested the password reset, then ignore this email.')),
360
+            $this->l10n->t('Click the following link to reset your password. If you have not requested the password reset, then ignore this email.')
361
+        );
362
+
363
+        $emailTemplate->addBodyButton(
364
+            htmlspecialchars($this->l10n->t('Reset your password')),
365
+            $link,
366
+            false
367
+        );
368
+        $emailTemplate->addFooter();
369
+
370
+        try {
371
+            $message = $this->mailer->createMessage();
372
+            $message->setTo([$email => $user->getUID()]);
373
+            $message->setFrom([$this->from => $this->defaults->getName()]);
374
+            $message->useTemplate($emailTemplate);
375
+            $this->mailer->send($message);
376
+        } catch (\Exception $e) {
377
+            // Log the exception and continue
378
+            $this->logger->logException($e);
379
+        }
380
+    }
381
+
382
+    /**
383
+     * @param string $input
384
+     * @return IUser
385
+     * @throws ResetPasswordException
386
+     */
387
+    protected function findUserByIdOrMail($input) {
388
+        $user = $this->userManager->get($input);
389
+        if ($user instanceof IUser) {
390
+            if (!$user->isEnabled()) {
391
+                throw new ResetPasswordException('User is disabled');
392
+            }
393
+
394
+            return $user;
395
+        }
396
+
397
+        $users = array_filter($this->userManager->getByEmail($input), function (IUser $user) {
398
+            return $user->isEnabled();
399
+        });
400
+
401
+        if (count($users) === 1) {
402
+            return reset($users);
403
+        }
404
+
405
+        throw new ResetPasswordException('Could not find user');
406
+    }
407 407
 }
Please login to merge, or discard this patch.