Passed
Push — master ( c8160a...ce50ac )
by Joas
30:30 queued 14:29
created
core/Controller/LostController.php 1 patch
Indentation   +272 added lines, -272 removed lines patch added patch discarded remove patch
@@ -73,276 +73,276 @@
 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
-	 */
132
-	public function resetform(string $token, string $userId): TemplateResponse {
133
-		try {
134
-			$this->checkPasswordResetToken($token, $userId);
135
-		} catch (Exception $e) {
136
-			if ($this->config->getSystemValue('lost_password_link', '') !== 'disabled'
137
-				|| ($e instanceof InvalidTokenException
138
-					&& !in_array($e->getCode(), [InvalidTokenException::TOKEN_NOT_FOUND, InvalidTokenException::USER_UNKNOWN]))
139
-			) {
140
-				return new TemplateResponse(
141
-					'core', 'error', [
142
-						"errors" => [["error" => $e->getMessage()]]
143
-					],
144
-					TemplateResponse::RENDER_AS_GUEST
145
-				);
146
-			}
147
-			return new TemplateResponse('core', 'error', [
148
-				'errors' => [['error' => $this->l10n->t('Password reset is disabled')]]
149
-			],
150
-				TemplateResponse::RENDER_AS_GUEST
151
-			);
152
-		}
153
-		$this->initialState->provideInitialState('resetPasswordUser', $userId);
154
-		$this->initialState->provideInitialState('resetPasswordTarget',
155
-			$this->urlGenerator->linkToRouteAbsolute('core.lost.setPassword', ['userId' => $userId, 'token' => $token])
156
-		);
157
-
158
-		return new TemplateResponse(
159
-			'core',
160
-			'login',
161
-			[],
162
-			'guest'
163
-		);
164
-	}
165
-
166
-	/**
167
-	 * @throws Exception
168
-	 */
169
-	protected function checkPasswordResetToken(string $token, string $userId): void {
170
-		try {
171
-			$user = $this->userManager->get($userId);
172
-			$this->verificationToken->check($token, $user, 'lostpassword', $user ? $user->getEMailAddress() : '', true);
173
-		} catch (InvalidTokenException $e) {
174
-			$error = $e->getCode() === InvalidTokenException::TOKEN_EXPIRED
175
-				? $this->l10n->t('Could not reset password because the token is expired')
176
-				: $this->l10n->t('Could not reset password because the token is invalid');
177
-			throw new Exception($error, (int)$e->getCode(), $e);
178
-		}
179
-	}
180
-
181
-	private function error(string $message, array $additional = []): array {
182
-		return array_merge(['status' => 'error', 'msg' => $message], $additional);
183
-	}
184
-
185
-	private function success(array $data = []): array {
186
-		return array_merge($data, ['status' => 'success']);
187
-	}
188
-
189
-	/**
190
-	 * @PublicPage
191
-	 * @BruteForceProtection(action=passwordResetEmail)
192
-	 * @AnonRateThrottle(limit=10, period=300)
193
-	 */
194
-	public function email(string $user): JSONResponse {
195
-		if ($this->config->getSystemValue('lost_password_link', '') !== '') {
196
-			return new JSONResponse($this->error($this->l10n->t('Password reset is disabled')));
197
-		}
198
-
199
-		\OCP\Util::emitHook(
200
-			'\OCA\Files_Sharing\API\Server2Server',
201
-			'preLoginNameUsedAsUserName',
202
-			['uid' => &$user]
203
-		);
204
-
205
-		// FIXME: use HTTP error codes
206
-		try {
207
-			$this->sendEmail($user);
208
-		} catch (ResetPasswordException $e) {
209
-			// Ignore the error since we do not want to leak this info
210
-			$this->logger->warning('Could not send password reset email: ' . $e->getMessage());
211
-		} catch (Exception $e) {
212
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
213
-		}
214
-
215
-		$response = new JSONResponse($this->success());
216
-		$response->throttle();
217
-		return $response;
218
-	}
219
-
220
-	/**
221
-	 * @PublicPage
222
-	 */
223
-	public function setPassword(string $token, string $userId, string $password, bool $proceed): array {
224
-		if ($this->encryptionManager->isEnabled() && !$proceed) {
225
-			$encryptionModules = $this->encryptionManager->getEncryptionModules();
226
-			foreach ($encryptionModules as $module) {
227
-				/** @var IEncryptionModule $instance */
228
-				$instance = call_user_func($module['callback']);
229
-				// this way we can find out whether per-user keys are used or a system wide encryption key
230
-				if ($instance->needDetailedAccessList()) {
231
-					return $this->error('', ['encryption' => true]);
232
-				}
233
-			}
234
-		}
235
-
236
-		try {
237
-			$this->checkPasswordResetToken($token, $userId);
238
-			$user = $this->userManager->get($userId);
239
-
240
-			$this->eventDispatcher->dispatchTyped(new BeforePasswordResetEvent($user, $password));
241
-			\OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'pre_passwordReset', ['uid' => $userId, 'password' => $password]);
242
-
243
-			if (strlen($password) > 469) {
244
-				throw new HintException('Password too long', $this->l10n->t('Password is too long. Maximum allowed length is 469 characters.'));
245
-			}
246
-
247
-			if (!$user->setPassword($password)) {
248
-				throw new Exception();
249
-			}
250
-
251
-			$this->eventDispatcher->dispatchTyped(new PasswordResetEvent($user, $password));
252
-			\OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'post_passwordReset', ['uid' => $userId, 'password' => $password]);
253
-
254
-			$this->twoFactorManager->clearTwoFactorPending($userId);
255
-
256
-			$this->config->deleteUserValue($userId, 'core', 'lostpassword');
257
-			@\OC::$server->getUserSession()->unsetMagicInCookie();
258
-		} catch (HintException $e) {
259
-			return $this->error($e->getHint());
260
-		} catch (Exception $e) {
261
-			return $this->error($e->getMessage());
262
-		}
263
-
264
-		return $this->success(['user' => $userId]);
265
-	}
266
-
267
-	/**
268
-	 * @throws ResetPasswordException
269
-	 * @throws \OCP\PreConditionNotMetException
270
-	 */
271
-	protected function sendEmail(string $input): void {
272
-		$user = $this->findUserByIdOrMail($input);
273
-		$email = $user->getEMailAddress();
274
-
275
-		if (empty($email)) {
276
-			throw new ResetPasswordException('Could not send reset e-mail since there is no email for username ' . $input);
277
-		}
278
-
279
-		try {
280
-			$this->limiter->registerUserRequest('lostpasswordemail', 5, 1800, $user);
281
-		} catch (RateLimitExceededException $e) {
282
-			throw new ResetPasswordException('Could not send reset e-mail, 5 of them were already sent in the last 30 minutes', 0, $e);
283
-		}
284
-
285
-		// Generate the token. It is stored encrypted in the database with the
286
-		// secret being the users' email address appended with the system secret.
287
-		// This makes the token automatically invalidate once the user changes
288
-		// their email address.
289
-		$token = $this->verificationToken->create($user, 'lostpassword', $email);
290
-
291
-		$link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', ['userId' => $user->getUID(), 'token' => $token]);
292
-
293
-		$emailTemplate = $this->mailer->createEMailTemplate('core.ResetPassword', [
294
-			'link' => $link,
295
-		]);
296
-
297
-		$emailTemplate->setSubject($this->l10n->t('%s password reset', [$this->defaults->getName()]));
298
-		$emailTemplate->addHeader();
299
-		$emailTemplate->addHeading($this->l10n->t('Password reset'));
300
-
301
-		$emailTemplate->addBodyText(
302
-			htmlspecialchars($this->l10n->t('Click the following button to reset your password. If you have not requested the password reset, then ignore this email.')),
303
-			$this->l10n->t('Click the following link to reset your password. If you have not requested the password reset, then ignore this email.')
304
-		);
305
-
306
-		$emailTemplate->addBodyButton(
307
-			htmlspecialchars($this->l10n->t('Reset your password')),
308
-			$link,
309
-			false
310
-		);
311
-		$emailTemplate->addFooter();
312
-
313
-		try {
314
-			$message = $this->mailer->createMessage();
315
-			$message->setTo([$email => $user->getDisplayName()]);
316
-			$message->setFrom([$this->from => $this->defaults->getName()]);
317
-			$message->useTemplate($emailTemplate);
318
-			$this->mailer->send($message);
319
-		} catch (Exception $e) {
320
-			// Log the exception and continue
321
-			$this->logger->error($e->getMessage(), ['app' => 'core', 'exception' => $e]);
322
-		}
323
-	}
324
-
325
-	/**
326
-	 * @throws ResetPasswordException
327
-	 */
328
-	protected function findUserByIdOrMail(string $input): IUser {
329
-		$user = $this->userManager->get($input);
330
-		if ($user instanceof IUser) {
331
-			if (!$user->isEnabled()) {
332
-				throw new ResetPasswordException('User ' . $user->getUID() . ' is disabled');
333
-			}
334
-
335
-			return $user;
336
-		}
337
-
338
-		$users = array_filter($this->userManager->getByEmail($input), function (IUser $user) {
339
-			return $user->isEnabled();
340
-		});
341
-
342
-		if (count($users) === 1) {
343
-			return reset($users);
344
-		}
345
-
346
-		throw new ResetPasswordException('Could not find user ' . $input);
347
-	}
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
+     */
132
+    public function resetform(string $token, string $userId): TemplateResponse {
133
+        try {
134
+            $this->checkPasswordResetToken($token, $userId);
135
+        } catch (Exception $e) {
136
+            if ($this->config->getSystemValue('lost_password_link', '') !== 'disabled'
137
+                || ($e instanceof InvalidTokenException
138
+                    && !in_array($e->getCode(), [InvalidTokenException::TOKEN_NOT_FOUND, InvalidTokenException::USER_UNKNOWN]))
139
+            ) {
140
+                return new TemplateResponse(
141
+                    'core', 'error', [
142
+                        "errors" => [["error" => $e->getMessage()]]
143
+                    ],
144
+                    TemplateResponse::RENDER_AS_GUEST
145
+                );
146
+            }
147
+            return new TemplateResponse('core', 'error', [
148
+                'errors' => [['error' => $this->l10n->t('Password reset is disabled')]]
149
+            ],
150
+                TemplateResponse::RENDER_AS_GUEST
151
+            );
152
+        }
153
+        $this->initialState->provideInitialState('resetPasswordUser', $userId);
154
+        $this->initialState->provideInitialState('resetPasswordTarget',
155
+            $this->urlGenerator->linkToRouteAbsolute('core.lost.setPassword', ['userId' => $userId, 'token' => $token])
156
+        );
157
+
158
+        return new TemplateResponse(
159
+            'core',
160
+            'login',
161
+            [],
162
+            'guest'
163
+        );
164
+    }
165
+
166
+    /**
167
+     * @throws Exception
168
+     */
169
+    protected function checkPasswordResetToken(string $token, string $userId): void {
170
+        try {
171
+            $user = $this->userManager->get($userId);
172
+            $this->verificationToken->check($token, $user, 'lostpassword', $user ? $user->getEMailAddress() : '', true);
173
+        } catch (InvalidTokenException $e) {
174
+            $error = $e->getCode() === InvalidTokenException::TOKEN_EXPIRED
175
+                ? $this->l10n->t('Could not reset password because the token is expired')
176
+                : $this->l10n->t('Could not reset password because the token is invalid');
177
+            throw new Exception($error, (int)$e->getCode(), $e);
178
+        }
179
+    }
180
+
181
+    private function error(string $message, array $additional = []): array {
182
+        return array_merge(['status' => 'error', 'msg' => $message], $additional);
183
+    }
184
+
185
+    private function success(array $data = []): array {
186
+        return array_merge($data, ['status' => 'success']);
187
+    }
188
+
189
+    /**
190
+     * @PublicPage
191
+     * @BruteForceProtection(action=passwordResetEmail)
192
+     * @AnonRateThrottle(limit=10, period=300)
193
+     */
194
+    public function email(string $user): JSONResponse {
195
+        if ($this->config->getSystemValue('lost_password_link', '') !== '') {
196
+            return new JSONResponse($this->error($this->l10n->t('Password reset is disabled')));
197
+        }
198
+
199
+        \OCP\Util::emitHook(
200
+            '\OCA\Files_Sharing\API\Server2Server',
201
+            'preLoginNameUsedAsUserName',
202
+            ['uid' => &$user]
203
+        );
204
+
205
+        // FIXME: use HTTP error codes
206
+        try {
207
+            $this->sendEmail($user);
208
+        } catch (ResetPasswordException $e) {
209
+            // Ignore the error since we do not want to leak this info
210
+            $this->logger->warning('Could not send password reset email: ' . $e->getMessage());
211
+        } catch (Exception $e) {
212
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
213
+        }
214
+
215
+        $response = new JSONResponse($this->success());
216
+        $response->throttle();
217
+        return $response;
218
+    }
219
+
220
+    /**
221
+     * @PublicPage
222
+     */
223
+    public function setPassword(string $token, string $userId, string $password, bool $proceed): array {
224
+        if ($this->encryptionManager->isEnabled() && !$proceed) {
225
+            $encryptionModules = $this->encryptionManager->getEncryptionModules();
226
+            foreach ($encryptionModules as $module) {
227
+                /** @var IEncryptionModule $instance */
228
+                $instance = call_user_func($module['callback']);
229
+                // this way we can find out whether per-user keys are used or a system wide encryption key
230
+                if ($instance->needDetailedAccessList()) {
231
+                    return $this->error('', ['encryption' => true]);
232
+                }
233
+            }
234
+        }
235
+
236
+        try {
237
+            $this->checkPasswordResetToken($token, $userId);
238
+            $user = $this->userManager->get($userId);
239
+
240
+            $this->eventDispatcher->dispatchTyped(new BeforePasswordResetEvent($user, $password));
241
+            \OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'pre_passwordReset', ['uid' => $userId, 'password' => $password]);
242
+
243
+            if (strlen($password) > 469) {
244
+                throw new HintException('Password too long', $this->l10n->t('Password is too long. Maximum allowed length is 469 characters.'));
245
+            }
246
+
247
+            if (!$user->setPassword($password)) {
248
+                throw new Exception();
249
+            }
250
+
251
+            $this->eventDispatcher->dispatchTyped(new PasswordResetEvent($user, $password));
252
+            \OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'post_passwordReset', ['uid' => $userId, 'password' => $password]);
253
+
254
+            $this->twoFactorManager->clearTwoFactorPending($userId);
255
+
256
+            $this->config->deleteUserValue($userId, 'core', 'lostpassword');
257
+            @\OC::$server->getUserSession()->unsetMagicInCookie();
258
+        } catch (HintException $e) {
259
+            return $this->error($e->getHint());
260
+        } catch (Exception $e) {
261
+            return $this->error($e->getMessage());
262
+        }
263
+
264
+        return $this->success(['user' => $userId]);
265
+    }
266
+
267
+    /**
268
+     * @throws ResetPasswordException
269
+     * @throws \OCP\PreConditionNotMetException
270
+     */
271
+    protected function sendEmail(string $input): void {
272
+        $user = $this->findUserByIdOrMail($input);
273
+        $email = $user->getEMailAddress();
274
+
275
+        if (empty($email)) {
276
+            throw new ResetPasswordException('Could not send reset e-mail since there is no email for username ' . $input);
277
+        }
278
+
279
+        try {
280
+            $this->limiter->registerUserRequest('lostpasswordemail', 5, 1800, $user);
281
+        } catch (RateLimitExceededException $e) {
282
+            throw new ResetPasswordException('Could not send reset e-mail, 5 of them were already sent in the last 30 minutes', 0, $e);
283
+        }
284
+
285
+        // Generate the token. It is stored encrypted in the database with the
286
+        // secret being the users' email address appended with the system secret.
287
+        // This makes the token automatically invalidate once the user changes
288
+        // their email address.
289
+        $token = $this->verificationToken->create($user, 'lostpassword', $email);
290
+
291
+        $link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', ['userId' => $user->getUID(), 'token' => $token]);
292
+
293
+        $emailTemplate = $this->mailer->createEMailTemplate('core.ResetPassword', [
294
+            'link' => $link,
295
+        ]);
296
+
297
+        $emailTemplate->setSubject($this->l10n->t('%s password reset', [$this->defaults->getName()]));
298
+        $emailTemplate->addHeader();
299
+        $emailTemplate->addHeading($this->l10n->t('Password reset'));
300
+
301
+        $emailTemplate->addBodyText(
302
+            htmlspecialchars($this->l10n->t('Click the following button to reset your password. If you have not requested the password reset, then ignore this email.')),
303
+            $this->l10n->t('Click the following link to reset your password. If you have not requested the password reset, then ignore this email.')
304
+        );
305
+
306
+        $emailTemplate->addBodyButton(
307
+            htmlspecialchars($this->l10n->t('Reset your password')),
308
+            $link,
309
+            false
310
+        );
311
+        $emailTemplate->addFooter();
312
+
313
+        try {
314
+            $message = $this->mailer->createMessage();
315
+            $message->setTo([$email => $user->getDisplayName()]);
316
+            $message->setFrom([$this->from => $this->defaults->getName()]);
317
+            $message->useTemplate($emailTemplate);
318
+            $this->mailer->send($message);
319
+        } catch (Exception $e) {
320
+            // Log the exception and continue
321
+            $this->logger->error($e->getMessage(), ['app' => 'core', 'exception' => $e]);
322
+        }
323
+    }
324
+
325
+    /**
326
+     * @throws ResetPasswordException
327
+     */
328
+    protected function findUserByIdOrMail(string $input): IUser {
329
+        $user = $this->userManager->get($input);
330
+        if ($user instanceof IUser) {
331
+            if (!$user->isEnabled()) {
332
+                throw new ResetPasswordException('User ' . $user->getUID() . ' is disabled');
333
+            }
334
+
335
+            return $user;
336
+        }
337
+
338
+        $users = array_filter($this->userManager->getByEmail($input), function (IUser $user) {
339
+            return $user->isEnabled();
340
+        });
341
+
342
+        if (count($users) === 1) {
343
+            return reset($users);
344
+        }
345
+
346
+        throw new ResetPasswordException('Could not find user ' . $input);
347
+    }
348 348
 }
Please login to merge, or discard this patch.
core/Controller/LoginController.php 1 patch
Indentation   +323 added lines, -323 removed lines patch added patch discarded remove patch
@@ -60,327 +60,327 @@
 block discarded – undo
60 60
 use OCP\Util;
61 61
 
62 62
 class LoginController extends Controller {
63
-	public const LOGIN_MSG_INVALIDPASSWORD = 'invalidpassword';
64
-	public const LOGIN_MSG_USERDISABLED = 'userdisabled';
65
-
66
-	private IUserManager $userManager;
67
-	private IConfig $config;
68
-	private ISession $session;
69
-	/** @var IUserSession|Session */
70
-	private $userSession;
71
-	private IURLGenerator $urlGenerator;
72
-	private Defaults $defaults;
73
-	private Throttler $throttler;
74
-	private Chain $loginChain;
75
-	private IInitialStateService $initialStateService;
76
-	private WebAuthnManager $webAuthnManager;
77
-	private IManager $manager;
78
-	private IL10N $l10n;
79
-
80
-	public function __construct(?string $appName,
81
-								IRequest $request,
82
-								IUserManager $userManager,
83
-								IConfig $config,
84
-								ISession $session,
85
-								IUserSession $userSession,
86
-								IURLGenerator $urlGenerator,
87
-								Defaults $defaults,
88
-								Throttler $throttler,
89
-								Chain $loginChain,
90
-								IInitialStateService $initialStateService,
91
-								WebAuthnManager $webAuthnManager,
92
-								IManager $manager,
93
-								IL10N $l10n) {
94
-		parent::__construct($appName, $request);
95
-		$this->userManager = $userManager;
96
-		$this->config = $config;
97
-		$this->session = $session;
98
-		$this->userSession = $userSession;
99
-		$this->urlGenerator = $urlGenerator;
100
-		$this->defaults = $defaults;
101
-		$this->throttler = $throttler;
102
-		$this->loginChain = $loginChain;
103
-		$this->initialStateService = $initialStateService;
104
-		$this->webAuthnManager = $webAuthnManager;
105
-		$this->manager = $manager;
106
-		$this->l10n = $l10n;
107
-	}
108
-
109
-	/**
110
-	 * @NoAdminRequired
111
-	 * @UseSession
112
-	 *
113
-	 * @return RedirectResponse
114
-	 */
115
-	public function logout() {
116
-		$loginToken = $this->request->getCookie('nc_token');
117
-		if (!is_null($loginToken)) {
118
-			$this->config->deleteUserValue($this->userSession->getUser()->getUID(), 'login_token', $loginToken);
119
-		}
120
-		$this->userSession->logout();
121
-
122
-		$response = new RedirectResponse($this->urlGenerator->linkToRouteAbsolute(
123
-			'core.login.showLoginForm',
124
-			['clear' => true] // this param the code in login.js may be removed when the "Clear-Site-Data" is working in the browsers
125
-		));
126
-
127
-		$this->session->set('clearingExecutionContexts', '1');
128
-		$this->session->close();
129
-
130
-		if (!$this->request->isUserAgent([Request::USER_AGENT_CHROME, Request::USER_AGENT_ANDROID_MOBILE_CHROME])) {
131
-			$response->addHeader('Clear-Site-Data', '"cache", "storage"');
132
-		}
133
-
134
-		return $response;
135
-	}
136
-
137
-	/**
138
-	 * @PublicPage
139
-	 * @NoCSRFRequired
140
-	 * @UseSession
141
-	 *
142
-	 * @param string $user
143
-	 * @param string $redirect_url
144
-	 *
145
-	 * @return TemplateResponse|RedirectResponse
146
-	 */
147
-	public function showLoginForm(string $user = null, string $redirect_url = null): Http\Response {
148
-		if ($this->userSession->isLoggedIn()) {
149
-			return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
150
-		}
151
-
152
-		$loginMessages = $this->session->get('loginMessages');
153
-		if (!$this->manager->isFairUseOfFreePushService()) {
154
-			if (!is_array($loginMessages)) {
155
-				$loginMessages = [[], []];
156
-			}
157
-			$loginMessages[1][] = $this->l10n->t('This community release of Nextcloud is unsupported and push notifications are limited.');
158
-		}
159
-		if (is_array($loginMessages)) {
160
-			[$errors, $messages] = $loginMessages;
161
-			$this->initialStateService->provideInitialState('core', 'loginMessages', $messages);
162
-			$this->initialStateService->provideInitialState('core', 'loginErrors', $errors);
163
-		}
164
-		$this->session->remove('loginMessages');
165
-
166
-		if ($user !== null && $user !== '') {
167
-			$this->initialStateService->provideInitialState('core', 'loginUsername', $user);
168
-		} else {
169
-			$this->initialStateService->provideInitialState('core', 'loginUsername', '');
170
-		}
171
-
172
-		$this->initialStateService->provideInitialState(
173
-			'core',
174
-			'loginAutocomplete',
175
-			$this->config->getSystemValue('login_form_autocomplete', true) === true
176
-		);
177
-
178
-		if (!empty($redirect_url)) {
179
-			[$url, ] = explode('?', $redirect_url);
180
-			if ($url !== $this->urlGenerator->linkToRoute('core.login.logout')) {
181
-				$this->initialStateService->provideInitialState('core', 'loginRedirectUrl', $redirect_url);
182
-			}
183
-		}
184
-
185
-		$this->initialStateService->provideInitialState(
186
-			'core',
187
-			'loginThrottleDelay',
188
-			$this->throttler->getDelay($this->request->getRemoteAddress())
189
-		);
190
-
191
-		$this->setPasswordResetInitialState($user);
192
-
193
-		$this->initialStateService->provideInitialState('core', 'webauthn-available', $this->webAuthnManager->isWebAuthnAvailable());
194
-
195
-		$this->initialStateService->provideInitialState('core', 'hideLoginForm', $this->config->getSystemValueBool('hide_login_form', false));
196
-
197
-		// OpenGraph Support: http://ogp.me/
198
-		Util::addHeader('meta', ['property' => 'og:title', 'content' => Util::sanitizeHTML($this->defaults->getName())]);
199
-		Util::addHeader('meta', ['property' => 'og:description', 'content' => Util::sanitizeHTML($this->defaults->getSlogan())]);
200
-		Util::addHeader('meta', ['property' => 'og:site_name', 'content' => Util::sanitizeHTML($this->defaults->getName())]);
201
-		Util::addHeader('meta', ['property' => 'og:url', 'content' => $this->urlGenerator->getAbsoluteURL('/')]);
202
-		Util::addHeader('meta', ['property' => 'og:type', 'content' => 'website']);
203
-		Util::addHeader('meta', ['property' => 'og:image', 'content' => $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'favicon-touch.png'))]);
204
-
205
-		$parameters = [
206
-			'alt_login' => OC_App::getAlternativeLogIns(),
207
-			'pageTitle' => $this->l10n->t('Login'),
208
-		];
209
-
210
-		$this->initialStateService->provideInitialState('core', 'countAlternativeLogins', count($parameters['alt_login']));
211
-		$this->initialStateService->provideInitialState('core', 'alternativeLogins', $parameters['alt_login']);
212
-
213
-		return new TemplateResponse(
214
-			$this->appName,
215
-			'login',
216
-			$parameters,
217
-			TemplateResponse::RENDER_AS_GUEST,
218
-		);
219
-	}
220
-
221
-	/**
222
-	 * Sets the password reset state
223
-	 *
224
-	 * @param string $username
225
-	 */
226
-	private function setPasswordResetInitialState(?string $username): void {
227
-		if ($username !== null && $username !== '') {
228
-			$user = $this->userManager->get($username);
229
-		} else {
230
-			$user = null;
231
-		}
232
-
233
-		$passwordLink = $this->config->getSystemValueString('lost_password_link', '');
234
-
235
-		$this->initialStateService->provideInitialState(
236
-			'core',
237
-			'loginResetPasswordLink',
238
-			$passwordLink
239
-		);
240
-
241
-		$this->initialStateService->provideInitialState(
242
-			'core',
243
-			'loginCanResetPassword',
244
-			$this->canResetPassword($passwordLink, $user)
245
-		);
246
-	}
247
-
248
-	/**
249
-	 * @param string|null $passwordLink
250
-	 * @param IUser|null $user
251
-	 *
252
-	 * Users may not change their passwords if:
253
-	 * - The account is disabled
254
-	 * - The backend doesn't support password resets
255
-	 * - The password reset function is disabled
256
-	 *
257
-	 * @return bool
258
-	 */
259
-	private function canResetPassword(?string $passwordLink, ?IUser $user): bool {
260
-		if ($passwordLink === 'disabled') {
261
-			return false;
262
-		}
263
-
264
-		if (!$passwordLink && $user !== null) {
265
-			return $user->canChangePassword();
266
-		}
267
-
268
-		if ($user !== null && $user->isEnabled() === false) {
269
-			return false;
270
-		}
271
-
272
-		return true;
273
-	}
274
-
275
-	private function generateRedirect(?string $redirectUrl): RedirectResponse {
276
-		if ($redirectUrl !== null && $this->userSession->isLoggedIn()) {
277
-			$location = $this->urlGenerator->getAbsoluteURL($redirectUrl);
278
-			// Deny the redirect if the URL contains a @
279
-			// This prevents unvalidated redirects like ?redirect_url=:[email protected]
280
-			if (strpos($location, '@') === false) {
281
-				return new RedirectResponse($location);
282
-			}
283
-		}
284
-		return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
285
-	}
286
-
287
-	/**
288
-	 * @PublicPage
289
-	 * @UseSession
290
-	 * @NoCSRFRequired
291
-	 * @BruteForceProtection(action=login)
292
-	 *
293
-	 * @param string $user
294
-	 * @param string $password
295
-	 * @param string $redirect_url
296
-	 * @param string $timezone
297
-	 * @param string $timezone_offset
298
-	 *
299
-	 * @return RedirectResponse
300
-	 */
301
-	public function tryLogin(string $user,
302
-							 string $password,
303
-							 string $redirect_url = null,
304
-							 string $timezone = '',
305
-							 string $timezone_offset = ''): RedirectResponse {
306
-		// If the user is already logged in and the CSRF check does not pass then
307
-		// simply redirect the user to the correct page as required. This is the
308
-		// case when an user has already logged-in, in another tab.
309
-		if (!$this->request->passesCSRFCheck()) {
310
-			return $this->generateRedirect($redirect_url);
311
-		}
312
-
313
-		$data = new LoginData(
314
-			$this->request,
315
-			trim($user),
316
-			$password,
317
-			$redirect_url,
318
-			$timezone,
319
-			$timezone_offset
320
-		);
321
-		$result = $this->loginChain->process($data);
322
-		if (!$result->isSuccess()) {
323
-			return $this->createLoginFailedResponse(
324
-				$data->getUsername(),
325
-				$user,
326
-				$redirect_url,
327
-				$result->getErrorMessage()
328
-			);
329
-		}
330
-
331
-		if ($result->getRedirectUrl() !== null) {
332
-			return new RedirectResponse($result->getRedirectUrl());
333
-		}
334
-		return $this->generateRedirect($redirect_url);
335
-	}
336
-
337
-	/**
338
-	 * Creates a login failed response.
339
-	 *
340
-	 * @param string $user
341
-	 * @param string $originalUser
342
-	 * @param string $redirect_url
343
-	 * @param string $loginMessage
344
-	 *
345
-	 * @return RedirectResponse
346
-	 */
347
-	private function createLoginFailedResponse(
348
-		$user, $originalUser, $redirect_url, string $loginMessage) {
349
-		// Read current user and append if possible we need to
350
-		// return the unmodified user otherwise we will leak the login name
351
-		$args = $user !== null ? ['user' => $originalUser, 'direct' => 1] : [];
352
-		if ($redirect_url !== null) {
353
-			$args['redirect_url'] = $redirect_url;
354
-		}
355
-		$response = new RedirectResponse(
356
-			$this->urlGenerator->linkToRoute('core.login.showLoginForm', $args)
357
-		);
358
-		$response->throttle(['user' => substr($user, 0, 64)]);
359
-		$this->session->set('loginMessages', [
360
-			[$loginMessage], []
361
-		]);
362
-		return $response;
363
-	}
364
-
365
-	/**
366
-	 * @NoAdminRequired
367
-	 * @UseSession
368
-	 * @BruteForceProtection(action=sudo)
369
-	 *
370
-	 * @license GNU AGPL version 3 or any later version
371
-	 *
372
-	 */
373
-	public function confirmPassword(string $password): DataResponse {
374
-		$loginName = $this->userSession->getLoginName();
375
-		$loginResult = $this->userManager->checkPassword($loginName, $password);
376
-		if ($loginResult === false) {
377
-			$response = new DataResponse([], Http::STATUS_FORBIDDEN);
378
-			$response->throttle();
379
-			return $response;
380
-		}
381
-
382
-		$confirmTimestamp = time();
383
-		$this->session->set('last-password-confirm', $confirmTimestamp);
384
-		return new DataResponse(['lastLogin' => $confirmTimestamp], Http::STATUS_OK);
385
-	}
63
+    public const LOGIN_MSG_INVALIDPASSWORD = 'invalidpassword';
64
+    public const LOGIN_MSG_USERDISABLED = 'userdisabled';
65
+
66
+    private IUserManager $userManager;
67
+    private IConfig $config;
68
+    private ISession $session;
69
+    /** @var IUserSession|Session */
70
+    private $userSession;
71
+    private IURLGenerator $urlGenerator;
72
+    private Defaults $defaults;
73
+    private Throttler $throttler;
74
+    private Chain $loginChain;
75
+    private IInitialStateService $initialStateService;
76
+    private WebAuthnManager $webAuthnManager;
77
+    private IManager $manager;
78
+    private IL10N $l10n;
79
+
80
+    public function __construct(?string $appName,
81
+                                IRequest $request,
82
+                                IUserManager $userManager,
83
+                                IConfig $config,
84
+                                ISession $session,
85
+                                IUserSession $userSession,
86
+                                IURLGenerator $urlGenerator,
87
+                                Defaults $defaults,
88
+                                Throttler $throttler,
89
+                                Chain $loginChain,
90
+                                IInitialStateService $initialStateService,
91
+                                WebAuthnManager $webAuthnManager,
92
+                                IManager $manager,
93
+                                IL10N $l10n) {
94
+        parent::__construct($appName, $request);
95
+        $this->userManager = $userManager;
96
+        $this->config = $config;
97
+        $this->session = $session;
98
+        $this->userSession = $userSession;
99
+        $this->urlGenerator = $urlGenerator;
100
+        $this->defaults = $defaults;
101
+        $this->throttler = $throttler;
102
+        $this->loginChain = $loginChain;
103
+        $this->initialStateService = $initialStateService;
104
+        $this->webAuthnManager = $webAuthnManager;
105
+        $this->manager = $manager;
106
+        $this->l10n = $l10n;
107
+    }
108
+
109
+    /**
110
+     * @NoAdminRequired
111
+     * @UseSession
112
+     *
113
+     * @return RedirectResponse
114
+     */
115
+    public function logout() {
116
+        $loginToken = $this->request->getCookie('nc_token');
117
+        if (!is_null($loginToken)) {
118
+            $this->config->deleteUserValue($this->userSession->getUser()->getUID(), 'login_token', $loginToken);
119
+        }
120
+        $this->userSession->logout();
121
+
122
+        $response = new RedirectResponse($this->urlGenerator->linkToRouteAbsolute(
123
+            'core.login.showLoginForm',
124
+            ['clear' => true] // this param the code in login.js may be removed when the "Clear-Site-Data" is working in the browsers
125
+        ));
126
+
127
+        $this->session->set('clearingExecutionContexts', '1');
128
+        $this->session->close();
129
+
130
+        if (!$this->request->isUserAgent([Request::USER_AGENT_CHROME, Request::USER_AGENT_ANDROID_MOBILE_CHROME])) {
131
+            $response->addHeader('Clear-Site-Data', '"cache", "storage"');
132
+        }
133
+
134
+        return $response;
135
+    }
136
+
137
+    /**
138
+     * @PublicPage
139
+     * @NoCSRFRequired
140
+     * @UseSession
141
+     *
142
+     * @param string $user
143
+     * @param string $redirect_url
144
+     *
145
+     * @return TemplateResponse|RedirectResponse
146
+     */
147
+    public function showLoginForm(string $user = null, string $redirect_url = null): Http\Response {
148
+        if ($this->userSession->isLoggedIn()) {
149
+            return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
150
+        }
151
+
152
+        $loginMessages = $this->session->get('loginMessages');
153
+        if (!$this->manager->isFairUseOfFreePushService()) {
154
+            if (!is_array($loginMessages)) {
155
+                $loginMessages = [[], []];
156
+            }
157
+            $loginMessages[1][] = $this->l10n->t('This community release of Nextcloud is unsupported and push notifications are limited.');
158
+        }
159
+        if (is_array($loginMessages)) {
160
+            [$errors, $messages] = $loginMessages;
161
+            $this->initialStateService->provideInitialState('core', 'loginMessages', $messages);
162
+            $this->initialStateService->provideInitialState('core', 'loginErrors', $errors);
163
+        }
164
+        $this->session->remove('loginMessages');
165
+
166
+        if ($user !== null && $user !== '') {
167
+            $this->initialStateService->provideInitialState('core', 'loginUsername', $user);
168
+        } else {
169
+            $this->initialStateService->provideInitialState('core', 'loginUsername', '');
170
+        }
171
+
172
+        $this->initialStateService->provideInitialState(
173
+            'core',
174
+            'loginAutocomplete',
175
+            $this->config->getSystemValue('login_form_autocomplete', true) === true
176
+        );
177
+
178
+        if (!empty($redirect_url)) {
179
+            [$url, ] = explode('?', $redirect_url);
180
+            if ($url !== $this->urlGenerator->linkToRoute('core.login.logout')) {
181
+                $this->initialStateService->provideInitialState('core', 'loginRedirectUrl', $redirect_url);
182
+            }
183
+        }
184
+
185
+        $this->initialStateService->provideInitialState(
186
+            'core',
187
+            'loginThrottleDelay',
188
+            $this->throttler->getDelay($this->request->getRemoteAddress())
189
+        );
190
+
191
+        $this->setPasswordResetInitialState($user);
192
+
193
+        $this->initialStateService->provideInitialState('core', 'webauthn-available', $this->webAuthnManager->isWebAuthnAvailable());
194
+
195
+        $this->initialStateService->provideInitialState('core', 'hideLoginForm', $this->config->getSystemValueBool('hide_login_form', false));
196
+
197
+        // OpenGraph Support: http://ogp.me/
198
+        Util::addHeader('meta', ['property' => 'og:title', 'content' => Util::sanitizeHTML($this->defaults->getName())]);
199
+        Util::addHeader('meta', ['property' => 'og:description', 'content' => Util::sanitizeHTML($this->defaults->getSlogan())]);
200
+        Util::addHeader('meta', ['property' => 'og:site_name', 'content' => Util::sanitizeHTML($this->defaults->getName())]);
201
+        Util::addHeader('meta', ['property' => 'og:url', 'content' => $this->urlGenerator->getAbsoluteURL('/')]);
202
+        Util::addHeader('meta', ['property' => 'og:type', 'content' => 'website']);
203
+        Util::addHeader('meta', ['property' => 'og:image', 'content' => $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'favicon-touch.png'))]);
204
+
205
+        $parameters = [
206
+            'alt_login' => OC_App::getAlternativeLogIns(),
207
+            'pageTitle' => $this->l10n->t('Login'),
208
+        ];
209
+
210
+        $this->initialStateService->provideInitialState('core', 'countAlternativeLogins', count($parameters['alt_login']));
211
+        $this->initialStateService->provideInitialState('core', 'alternativeLogins', $parameters['alt_login']);
212
+
213
+        return new TemplateResponse(
214
+            $this->appName,
215
+            'login',
216
+            $parameters,
217
+            TemplateResponse::RENDER_AS_GUEST,
218
+        );
219
+    }
220
+
221
+    /**
222
+     * Sets the password reset state
223
+     *
224
+     * @param string $username
225
+     */
226
+    private function setPasswordResetInitialState(?string $username): void {
227
+        if ($username !== null && $username !== '') {
228
+            $user = $this->userManager->get($username);
229
+        } else {
230
+            $user = null;
231
+        }
232
+
233
+        $passwordLink = $this->config->getSystemValueString('lost_password_link', '');
234
+
235
+        $this->initialStateService->provideInitialState(
236
+            'core',
237
+            'loginResetPasswordLink',
238
+            $passwordLink
239
+        );
240
+
241
+        $this->initialStateService->provideInitialState(
242
+            'core',
243
+            'loginCanResetPassword',
244
+            $this->canResetPassword($passwordLink, $user)
245
+        );
246
+    }
247
+
248
+    /**
249
+     * @param string|null $passwordLink
250
+     * @param IUser|null $user
251
+     *
252
+     * Users may not change their passwords if:
253
+     * - The account is disabled
254
+     * - The backend doesn't support password resets
255
+     * - The password reset function is disabled
256
+     *
257
+     * @return bool
258
+     */
259
+    private function canResetPassword(?string $passwordLink, ?IUser $user): bool {
260
+        if ($passwordLink === 'disabled') {
261
+            return false;
262
+        }
263
+
264
+        if (!$passwordLink && $user !== null) {
265
+            return $user->canChangePassword();
266
+        }
267
+
268
+        if ($user !== null && $user->isEnabled() === false) {
269
+            return false;
270
+        }
271
+
272
+        return true;
273
+    }
274
+
275
+    private function generateRedirect(?string $redirectUrl): RedirectResponse {
276
+        if ($redirectUrl !== null && $this->userSession->isLoggedIn()) {
277
+            $location = $this->urlGenerator->getAbsoluteURL($redirectUrl);
278
+            // Deny the redirect if the URL contains a @
279
+            // This prevents unvalidated redirects like ?redirect_url=:[email protected]
280
+            if (strpos($location, '@') === false) {
281
+                return new RedirectResponse($location);
282
+            }
283
+        }
284
+        return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
285
+    }
286
+
287
+    /**
288
+     * @PublicPage
289
+     * @UseSession
290
+     * @NoCSRFRequired
291
+     * @BruteForceProtection(action=login)
292
+     *
293
+     * @param string $user
294
+     * @param string $password
295
+     * @param string $redirect_url
296
+     * @param string $timezone
297
+     * @param string $timezone_offset
298
+     *
299
+     * @return RedirectResponse
300
+     */
301
+    public function tryLogin(string $user,
302
+                                string $password,
303
+                                string $redirect_url = null,
304
+                                string $timezone = '',
305
+                                string $timezone_offset = ''): RedirectResponse {
306
+        // If the user is already logged in and the CSRF check does not pass then
307
+        // simply redirect the user to the correct page as required. This is the
308
+        // case when an user has already logged-in, in another tab.
309
+        if (!$this->request->passesCSRFCheck()) {
310
+            return $this->generateRedirect($redirect_url);
311
+        }
312
+
313
+        $data = new LoginData(
314
+            $this->request,
315
+            trim($user),
316
+            $password,
317
+            $redirect_url,
318
+            $timezone,
319
+            $timezone_offset
320
+        );
321
+        $result = $this->loginChain->process($data);
322
+        if (!$result->isSuccess()) {
323
+            return $this->createLoginFailedResponse(
324
+                $data->getUsername(),
325
+                $user,
326
+                $redirect_url,
327
+                $result->getErrorMessage()
328
+            );
329
+        }
330
+
331
+        if ($result->getRedirectUrl() !== null) {
332
+            return new RedirectResponse($result->getRedirectUrl());
333
+        }
334
+        return $this->generateRedirect($redirect_url);
335
+    }
336
+
337
+    /**
338
+     * Creates a login failed response.
339
+     *
340
+     * @param string $user
341
+     * @param string $originalUser
342
+     * @param string $redirect_url
343
+     * @param string $loginMessage
344
+     *
345
+     * @return RedirectResponse
346
+     */
347
+    private function createLoginFailedResponse(
348
+        $user, $originalUser, $redirect_url, string $loginMessage) {
349
+        // Read current user and append if possible we need to
350
+        // return the unmodified user otherwise we will leak the login name
351
+        $args = $user !== null ? ['user' => $originalUser, 'direct' => 1] : [];
352
+        if ($redirect_url !== null) {
353
+            $args['redirect_url'] = $redirect_url;
354
+        }
355
+        $response = new RedirectResponse(
356
+            $this->urlGenerator->linkToRoute('core.login.showLoginForm', $args)
357
+        );
358
+        $response->throttle(['user' => substr($user, 0, 64)]);
359
+        $this->session->set('loginMessages', [
360
+            [$loginMessage], []
361
+        ]);
362
+        return $response;
363
+    }
364
+
365
+    /**
366
+     * @NoAdminRequired
367
+     * @UseSession
368
+     * @BruteForceProtection(action=sudo)
369
+     *
370
+     * @license GNU AGPL version 3 or any later version
371
+     *
372
+     */
373
+    public function confirmPassword(string $password): DataResponse {
374
+        $loginName = $this->userSession->getLoginName();
375
+        $loginResult = $this->userManager->checkPassword($loginName, $password);
376
+        if ($loginResult === false) {
377
+            $response = new DataResponse([], Http::STATUS_FORBIDDEN);
378
+            $response->throttle();
379
+            return $response;
380
+        }
381
+
382
+        $confirmTimestamp = time();
383
+        $this->session->set('last-password-confirm', $confirmTimestamp);
384
+        return new DataResponse(['lastLogin' => $confirmTimestamp], Http::STATUS_OK);
385
+    }
386 386
 }
Please login to merge, or discard this patch.