Passed
Push — master ( fa1d50...595788 )
by Joas
14:44 queued 12s
created
core/Controller/LostController.php 1 patch
Indentation   +276 added lines, -276 removed lines patch added patch discarded remove patch
@@ -73,280 +73,280 @@
 block discarded – undo
73 73
  * @package OC\Core\Controller
74 74
  */
75 75
 class LostController extends Controller {
76
-	protected IURLGenerator $urlGenerator;
77
-	protected IUserManager $userManager;
78
-	protected Defaults $defaults;
79
-	protected IL10N $l10n;
80
-	protected string $from;
81
-	protected IManager $encryptionManager;
82
-	protected IConfig $config;
83
-	protected IMailer $mailer;
84
-	private LoggerInterface $logger;
85
-	private Manager $twoFactorManager;
86
-	private IInitialState $initialState;
87
-	private IVerificationToken $verificationToken;
88
-	private IEventDispatcher $eventDispatcher;
89
-	private Limiter $limiter;
90
-
91
-	public function __construct(
92
-		string $appName,
93
-		IRequest $request,
94
-		IURLGenerator $urlGenerator,
95
-		IUserManager $userManager,
96
-		Defaults $defaults,
97
-		IL10N $l10n,
98
-		IConfig $config,
99
-		string $defaultMailAddress,
100
-		IManager $encryptionManager,
101
-		IMailer $mailer,
102
-		LoggerInterface $logger,
103
-		Manager $twoFactorManager,
104
-		IInitialState $initialState,
105
-		IVerificationToken $verificationToken,
106
-		IEventDispatcher $eventDispatcher,
107
-		Limiter $limiter
108
-	) {
109
-		parent::__construct($appName, $request);
110
-		$this->urlGenerator = $urlGenerator;
111
-		$this->userManager = $userManager;
112
-		$this->defaults = $defaults;
113
-		$this->l10n = $l10n;
114
-		$this->from = $defaultMailAddress;
115
-		$this->encryptionManager = $encryptionManager;
116
-		$this->config = $config;
117
-		$this->mailer = $mailer;
118
-		$this->logger = $logger;
119
-		$this->twoFactorManager = $twoFactorManager;
120
-		$this->initialState = $initialState;
121
-		$this->verificationToken = $verificationToken;
122
-		$this->eventDispatcher = $eventDispatcher;
123
-		$this->limiter = $limiter;
124
-	}
125
-
126
-	/**
127
-	 * Someone wants to reset their password:
128
-	 *
129
-	 * @PublicPage
130
-	 * @NoCSRFRequired
131
-	 * @BruteForceProtection(action=passwordResetEmail)
132
-	 * @AnonRateThrottle(limit=10, period=300)
133
-	 */
134
-	public function resetform(string $token, string $userId): TemplateResponse {
135
-		try {
136
-			$this->checkPasswordResetToken($token, $userId);
137
-		} catch (Exception $e) {
138
-			if ($this->config->getSystemValue('lost_password_link', '') !== 'disabled'
139
-				|| ($e instanceof InvalidTokenException
140
-					&& !in_array($e->getCode(), [InvalidTokenException::TOKEN_NOT_FOUND, InvalidTokenException::USER_UNKNOWN]))
141
-			) {
142
-				$response = new TemplateResponse(
143
-					'core', 'error', [
144
-						"errors" => [["error" => $e->getMessage()]]
145
-					],
146
-					TemplateResponse::RENDER_AS_GUEST
147
-				);
148
-				$response->throttle();
149
-				return $response;
150
-			}
151
-			return new TemplateResponse('core', 'error', [
152
-				'errors' => [['error' => $this->l10n->t('Password reset is disabled')]]
153
-			],
154
-				TemplateResponse::RENDER_AS_GUEST
155
-			);
156
-		}
157
-		$this->initialState->provideInitialState('resetPasswordUser', $userId);
158
-		$this->initialState->provideInitialState('resetPasswordTarget',
159
-			$this->urlGenerator->linkToRouteAbsolute('core.lost.setPassword', ['userId' => $userId, 'token' => $token])
160
-		);
161
-
162
-		return new TemplateResponse(
163
-			'core',
164
-			'login',
165
-			[],
166
-			'guest'
167
-		);
168
-	}
169
-
170
-	/**
171
-	 * @throws Exception
172
-	 */
173
-	protected function checkPasswordResetToken(string $token, string $userId): void {
174
-		try {
175
-			$user = $this->userManager->get($userId);
176
-			$this->verificationToken->check($token, $user, 'lostpassword', $user ? $user->getEMailAddress() : '', true);
177
-		} catch (InvalidTokenException $e) {
178
-			$error = $e->getCode() === InvalidTokenException::TOKEN_EXPIRED
179
-				? $this->l10n->t('Could not reset password because the token is expired')
180
-				: $this->l10n->t('Could not reset password because the token is invalid');
181
-			throw new Exception($error, (int)$e->getCode(), $e);
182
-		}
183
-	}
184
-
185
-	private function error(string $message, array $additional = []): array {
186
-		return array_merge(['status' => 'error', 'msg' => $message], $additional);
187
-	}
188
-
189
-	private function success(array $data = []): array {
190
-		return array_merge($data, ['status' => 'success']);
191
-	}
192
-
193
-	/**
194
-	 * @PublicPage
195
-	 * @BruteForceProtection(action=passwordResetEmail)
196
-	 * @AnonRateThrottle(limit=10, period=300)
197
-	 */
198
-	public function email(string $user): JSONResponse {
199
-		if ($this->config->getSystemValue('lost_password_link', '') !== '') {
200
-			return new JSONResponse($this->error($this->l10n->t('Password reset is disabled')));
201
-		}
202
-
203
-		\OCP\Util::emitHook(
204
-			'\OCA\Files_Sharing\API\Server2Server',
205
-			'preLoginNameUsedAsUserName',
206
-			['uid' => &$user]
207
-		);
208
-
209
-		// FIXME: use HTTP error codes
210
-		try {
211
-			$this->sendEmail($user);
212
-		} catch (ResetPasswordException $e) {
213
-			// Ignore the error since we do not want to leak this info
214
-			$this->logger->warning('Could not send password reset email: ' . $e->getMessage());
215
-		} catch (Exception $e) {
216
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
217
-		}
218
-
219
-		$response = new JSONResponse($this->success());
220
-		$response->throttle();
221
-		return $response;
222
-	}
223
-
224
-	/**
225
-	 * @PublicPage
226
-	 */
227
-	public function setPassword(string $token, string $userId, string $password, bool $proceed): array {
228
-		if ($this->encryptionManager->isEnabled() && !$proceed) {
229
-			$encryptionModules = $this->encryptionManager->getEncryptionModules();
230
-			foreach ($encryptionModules as $module) {
231
-				/** @var IEncryptionModule $instance */
232
-				$instance = call_user_func($module['callback']);
233
-				// this way we can find out whether per-user keys are used or a system wide encryption key
234
-				if ($instance->needDetailedAccessList()) {
235
-					return $this->error('', ['encryption' => true]);
236
-				}
237
-			}
238
-		}
239
-
240
-		try {
241
-			$this->checkPasswordResetToken($token, $userId);
242
-			$user = $this->userManager->get($userId);
243
-
244
-			$this->eventDispatcher->dispatchTyped(new BeforePasswordResetEvent($user, $password));
245
-			\OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'pre_passwordReset', ['uid' => $userId, 'password' => $password]);
246
-
247
-			if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) {
248
-				throw new HintException('Password too long', $this->l10n->t('Password is too long. Maximum allowed length is 469 characters.'));
249
-			}
250
-
251
-			if (!$user->setPassword($password)) {
252
-				throw new Exception();
253
-			}
254
-
255
-			$this->eventDispatcher->dispatchTyped(new PasswordResetEvent($user, $password));
256
-			\OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'post_passwordReset', ['uid' => $userId, 'password' => $password]);
257
-
258
-			$this->twoFactorManager->clearTwoFactorPending($userId);
259
-
260
-			$this->config->deleteUserValue($userId, 'core', 'lostpassword');
261
-			@\OC::$server->getUserSession()->unsetMagicInCookie();
262
-		} catch (HintException $e) {
263
-			return $this->error($e->getHint());
264
-		} catch (Exception $e) {
265
-			return $this->error($e->getMessage());
266
-		}
267
-
268
-		return $this->success(['user' => $userId]);
269
-	}
270
-
271
-	/**
272
-	 * @throws ResetPasswordException
273
-	 * @throws \OCP\PreConditionNotMetException
274
-	 */
275
-	protected function sendEmail(string $input): void {
276
-		$user = $this->findUserByIdOrMail($input);
277
-		$email = $user->getEMailAddress();
278
-
279
-		if (empty($email)) {
280
-			throw new ResetPasswordException('Could not send reset e-mail since there is no email for username ' . $input);
281
-		}
282
-
283
-		try {
284
-			$this->limiter->registerUserRequest('lostpasswordemail', 5, 1800, $user);
285
-		} catch (RateLimitExceededException $e) {
286
-			throw new ResetPasswordException('Could not send reset e-mail, 5 of them were already sent in the last 30 minutes', 0, $e);
287
-		}
288
-
289
-		// Generate the token. It is stored encrypted in the database with the
290
-		// secret being the users' email address appended with the system secret.
291
-		// This makes the token automatically invalidate once the user changes
292
-		// their email address.
293
-		$token = $this->verificationToken->create($user, 'lostpassword', $email);
294
-
295
-		$link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', ['userId' => $user->getUID(), 'token' => $token]);
296
-
297
-		$emailTemplate = $this->mailer->createEMailTemplate('core.ResetPassword', [
298
-			'link' => $link,
299
-		]);
300
-
301
-		$emailTemplate->setSubject($this->l10n->t('%s password reset', [$this->defaults->getName()]));
302
-		$emailTemplate->addHeader();
303
-		$emailTemplate->addHeading($this->l10n->t('Password reset'));
304
-
305
-		$emailTemplate->addBodyText(
306
-			htmlspecialchars($this->l10n->t('Click the following button to reset your password. If you have not requested the password reset, then ignore this email.')),
307
-			$this->l10n->t('Click the following link to reset your password. If you have not requested the password reset, then ignore this email.')
308
-		);
309
-
310
-		$emailTemplate->addBodyButton(
311
-			htmlspecialchars($this->l10n->t('Reset your password')),
312
-			$link,
313
-			false
314
-		);
315
-		$emailTemplate->addFooter();
316
-
317
-		try {
318
-			$message = $this->mailer->createMessage();
319
-			$message->setTo([$email => $user->getDisplayName()]);
320
-			$message->setFrom([$this->from => $this->defaults->getName()]);
321
-			$message->useTemplate($emailTemplate);
322
-			$this->mailer->send($message);
323
-		} catch (Exception $e) {
324
-			// Log the exception and continue
325
-			$this->logger->error($e->getMessage(), ['app' => 'core', 'exception' => $e]);
326
-		}
327
-	}
328
-
329
-	/**
330
-	 * @throws ResetPasswordException
331
-	 */
332
-	protected function findUserByIdOrMail(string $input): IUser {
333
-		$user = $this->userManager->get($input);
334
-		if ($user instanceof IUser) {
335
-			if (!$user->isEnabled()) {
336
-				throw new ResetPasswordException('User ' . $user->getUID() . ' is disabled');
337
-			}
338
-
339
-			return $user;
340
-		}
341
-
342
-		$users = array_filter($this->userManager->getByEmail($input), function (IUser $user) {
343
-			return $user->isEnabled();
344
-		});
345
-
346
-		if (count($users) === 1) {
347
-			return reset($users);
348
-		}
349
-
350
-		throw new ResetPasswordException('Could not find user ' . $input);
351
-	}
76
+    protected IURLGenerator $urlGenerator;
77
+    protected IUserManager $userManager;
78
+    protected Defaults $defaults;
79
+    protected IL10N $l10n;
80
+    protected string $from;
81
+    protected IManager $encryptionManager;
82
+    protected IConfig $config;
83
+    protected IMailer $mailer;
84
+    private LoggerInterface $logger;
85
+    private Manager $twoFactorManager;
86
+    private IInitialState $initialState;
87
+    private IVerificationToken $verificationToken;
88
+    private IEventDispatcher $eventDispatcher;
89
+    private Limiter $limiter;
90
+
91
+    public function __construct(
92
+        string $appName,
93
+        IRequest $request,
94
+        IURLGenerator $urlGenerator,
95
+        IUserManager $userManager,
96
+        Defaults $defaults,
97
+        IL10N $l10n,
98
+        IConfig $config,
99
+        string $defaultMailAddress,
100
+        IManager $encryptionManager,
101
+        IMailer $mailer,
102
+        LoggerInterface $logger,
103
+        Manager $twoFactorManager,
104
+        IInitialState $initialState,
105
+        IVerificationToken $verificationToken,
106
+        IEventDispatcher $eventDispatcher,
107
+        Limiter $limiter
108
+    ) {
109
+        parent::__construct($appName, $request);
110
+        $this->urlGenerator = $urlGenerator;
111
+        $this->userManager = $userManager;
112
+        $this->defaults = $defaults;
113
+        $this->l10n = $l10n;
114
+        $this->from = $defaultMailAddress;
115
+        $this->encryptionManager = $encryptionManager;
116
+        $this->config = $config;
117
+        $this->mailer = $mailer;
118
+        $this->logger = $logger;
119
+        $this->twoFactorManager = $twoFactorManager;
120
+        $this->initialState = $initialState;
121
+        $this->verificationToken = $verificationToken;
122
+        $this->eventDispatcher = $eventDispatcher;
123
+        $this->limiter = $limiter;
124
+    }
125
+
126
+    /**
127
+     * Someone wants to reset their password:
128
+     *
129
+     * @PublicPage
130
+     * @NoCSRFRequired
131
+     * @BruteForceProtection(action=passwordResetEmail)
132
+     * @AnonRateThrottle(limit=10, period=300)
133
+     */
134
+    public function resetform(string $token, string $userId): TemplateResponse {
135
+        try {
136
+            $this->checkPasswordResetToken($token, $userId);
137
+        } catch (Exception $e) {
138
+            if ($this->config->getSystemValue('lost_password_link', '') !== 'disabled'
139
+                || ($e instanceof InvalidTokenException
140
+                    && !in_array($e->getCode(), [InvalidTokenException::TOKEN_NOT_FOUND, InvalidTokenException::USER_UNKNOWN]))
141
+            ) {
142
+                $response = new TemplateResponse(
143
+                    'core', 'error', [
144
+                        "errors" => [["error" => $e->getMessage()]]
145
+                    ],
146
+                    TemplateResponse::RENDER_AS_GUEST
147
+                );
148
+                $response->throttle();
149
+                return $response;
150
+            }
151
+            return new TemplateResponse('core', 'error', [
152
+                'errors' => [['error' => $this->l10n->t('Password reset is disabled')]]
153
+            ],
154
+                TemplateResponse::RENDER_AS_GUEST
155
+            );
156
+        }
157
+        $this->initialState->provideInitialState('resetPasswordUser', $userId);
158
+        $this->initialState->provideInitialState('resetPasswordTarget',
159
+            $this->urlGenerator->linkToRouteAbsolute('core.lost.setPassword', ['userId' => $userId, 'token' => $token])
160
+        );
161
+
162
+        return new TemplateResponse(
163
+            'core',
164
+            'login',
165
+            [],
166
+            'guest'
167
+        );
168
+    }
169
+
170
+    /**
171
+     * @throws Exception
172
+     */
173
+    protected function checkPasswordResetToken(string $token, string $userId): void {
174
+        try {
175
+            $user = $this->userManager->get($userId);
176
+            $this->verificationToken->check($token, $user, 'lostpassword', $user ? $user->getEMailAddress() : '', true);
177
+        } catch (InvalidTokenException $e) {
178
+            $error = $e->getCode() === InvalidTokenException::TOKEN_EXPIRED
179
+                ? $this->l10n->t('Could not reset password because the token is expired')
180
+                : $this->l10n->t('Could not reset password because the token is invalid');
181
+            throw new Exception($error, (int)$e->getCode(), $e);
182
+        }
183
+    }
184
+
185
+    private function error(string $message, array $additional = []): array {
186
+        return array_merge(['status' => 'error', 'msg' => $message], $additional);
187
+    }
188
+
189
+    private function success(array $data = []): array {
190
+        return array_merge($data, ['status' => 'success']);
191
+    }
192
+
193
+    /**
194
+     * @PublicPage
195
+     * @BruteForceProtection(action=passwordResetEmail)
196
+     * @AnonRateThrottle(limit=10, period=300)
197
+     */
198
+    public function email(string $user): JSONResponse {
199
+        if ($this->config->getSystemValue('lost_password_link', '') !== '') {
200
+            return new JSONResponse($this->error($this->l10n->t('Password reset is disabled')));
201
+        }
202
+
203
+        \OCP\Util::emitHook(
204
+            '\OCA\Files_Sharing\API\Server2Server',
205
+            'preLoginNameUsedAsUserName',
206
+            ['uid' => &$user]
207
+        );
208
+
209
+        // FIXME: use HTTP error codes
210
+        try {
211
+            $this->sendEmail($user);
212
+        } catch (ResetPasswordException $e) {
213
+            // Ignore the error since we do not want to leak this info
214
+            $this->logger->warning('Could not send password reset email: ' . $e->getMessage());
215
+        } catch (Exception $e) {
216
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
217
+        }
218
+
219
+        $response = new JSONResponse($this->success());
220
+        $response->throttle();
221
+        return $response;
222
+    }
223
+
224
+    /**
225
+     * @PublicPage
226
+     */
227
+    public function setPassword(string $token, string $userId, string $password, bool $proceed): array {
228
+        if ($this->encryptionManager->isEnabled() && !$proceed) {
229
+            $encryptionModules = $this->encryptionManager->getEncryptionModules();
230
+            foreach ($encryptionModules as $module) {
231
+                /** @var IEncryptionModule $instance */
232
+                $instance = call_user_func($module['callback']);
233
+                // this way we can find out whether per-user keys are used or a system wide encryption key
234
+                if ($instance->needDetailedAccessList()) {
235
+                    return $this->error('', ['encryption' => true]);
236
+                }
237
+            }
238
+        }
239
+
240
+        try {
241
+            $this->checkPasswordResetToken($token, $userId);
242
+            $user = $this->userManager->get($userId);
243
+
244
+            $this->eventDispatcher->dispatchTyped(new BeforePasswordResetEvent($user, $password));
245
+            \OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'pre_passwordReset', ['uid' => $userId, 'password' => $password]);
246
+
247
+            if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) {
248
+                throw new HintException('Password too long', $this->l10n->t('Password is too long. Maximum allowed length is 469 characters.'));
249
+            }
250
+
251
+            if (!$user->setPassword($password)) {
252
+                throw new Exception();
253
+            }
254
+
255
+            $this->eventDispatcher->dispatchTyped(new PasswordResetEvent($user, $password));
256
+            \OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'post_passwordReset', ['uid' => $userId, 'password' => $password]);
257
+
258
+            $this->twoFactorManager->clearTwoFactorPending($userId);
259
+
260
+            $this->config->deleteUserValue($userId, 'core', 'lostpassword');
261
+            @\OC::$server->getUserSession()->unsetMagicInCookie();
262
+        } catch (HintException $e) {
263
+            return $this->error($e->getHint());
264
+        } catch (Exception $e) {
265
+            return $this->error($e->getMessage());
266
+        }
267
+
268
+        return $this->success(['user' => $userId]);
269
+    }
270
+
271
+    /**
272
+     * @throws ResetPasswordException
273
+     * @throws \OCP\PreConditionNotMetException
274
+     */
275
+    protected function sendEmail(string $input): void {
276
+        $user = $this->findUserByIdOrMail($input);
277
+        $email = $user->getEMailAddress();
278
+
279
+        if (empty($email)) {
280
+            throw new ResetPasswordException('Could not send reset e-mail since there is no email for username ' . $input);
281
+        }
282
+
283
+        try {
284
+            $this->limiter->registerUserRequest('lostpasswordemail', 5, 1800, $user);
285
+        } catch (RateLimitExceededException $e) {
286
+            throw new ResetPasswordException('Could not send reset e-mail, 5 of them were already sent in the last 30 minutes', 0, $e);
287
+        }
288
+
289
+        // Generate the token. It is stored encrypted in the database with the
290
+        // secret being the users' email address appended with the system secret.
291
+        // This makes the token automatically invalidate once the user changes
292
+        // their email address.
293
+        $token = $this->verificationToken->create($user, 'lostpassword', $email);
294
+
295
+        $link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', ['userId' => $user->getUID(), 'token' => $token]);
296
+
297
+        $emailTemplate = $this->mailer->createEMailTemplate('core.ResetPassword', [
298
+            'link' => $link,
299
+        ]);
300
+
301
+        $emailTemplate->setSubject($this->l10n->t('%s password reset', [$this->defaults->getName()]));
302
+        $emailTemplate->addHeader();
303
+        $emailTemplate->addHeading($this->l10n->t('Password reset'));
304
+
305
+        $emailTemplate->addBodyText(
306
+            htmlspecialchars($this->l10n->t('Click the following button to reset your password. If you have not requested the password reset, then ignore this email.')),
307
+            $this->l10n->t('Click the following link to reset your password. If you have not requested the password reset, then ignore this email.')
308
+        );
309
+
310
+        $emailTemplate->addBodyButton(
311
+            htmlspecialchars($this->l10n->t('Reset your password')),
312
+            $link,
313
+            false
314
+        );
315
+        $emailTemplate->addFooter();
316
+
317
+        try {
318
+            $message = $this->mailer->createMessage();
319
+            $message->setTo([$email => $user->getDisplayName()]);
320
+            $message->setFrom([$this->from => $this->defaults->getName()]);
321
+            $message->useTemplate($emailTemplate);
322
+            $this->mailer->send($message);
323
+        } catch (Exception $e) {
324
+            // Log the exception and continue
325
+            $this->logger->error($e->getMessage(), ['app' => 'core', 'exception' => $e]);
326
+        }
327
+    }
328
+
329
+    /**
330
+     * @throws ResetPasswordException
331
+     */
332
+    protected function findUserByIdOrMail(string $input): IUser {
333
+        $user = $this->userManager->get($input);
334
+        if ($user instanceof IUser) {
335
+            if (!$user->isEnabled()) {
336
+                throw new ResetPasswordException('User ' . $user->getUID() . ' is disabled');
337
+            }
338
+
339
+            return $user;
340
+        }
341
+
342
+        $users = array_filter($this->userManager->getByEmail($input), function (IUser $user) {
343
+            return $user->isEnabled();
344
+        });
345
+
346
+        if (count($users) === 1) {
347
+            return reset($users);
348
+        }
349
+
350
+        throw new ResetPasswordException('Could not find user ' . $input);
351
+    }
352 352
 }
Please login to merge, or discard this patch.