Passed
Push — master ( 7db8e2...8ee52d )
by John
16:07 queued 15s
created
core/Controller/LoginController.php 1 patch
Indentation   +328 added lines, -328 removed lines patch added patch discarded remove patch
@@ -60,332 +60,332 @@
 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 IInitialStateService $initialStateService;
75
-	private WebAuthnManager $webAuthnManager;
76
-	private IManager $manager;
77
-	private IL10N $l10n;
78
-
79
-	public function __construct(?string $appName,
80
-								IRequest $request,
81
-								IUserManager $userManager,
82
-								IConfig $config,
83
-								ISession $session,
84
-								IUserSession $userSession,
85
-								IURLGenerator $urlGenerator,
86
-								Defaults $defaults,
87
-								Throttler $throttler,
88
-								IInitialStateService $initialStateService,
89
-								WebAuthnManager $webAuthnManager,
90
-								IManager $manager,
91
-								IL10N $l10n) {
92
-		parent::__construct($appName, $request);
93
-		$this->userManager = $userManager;
94
-		$this->config = $config;
95
-		$this->session = $session;
96
-		$this->userSession = $userSession;
97
-		$this->urlGenerator = $urlGenerator;
98
-		$this->defaults = $defaults;
99
-		$this->throttler = $throttler;
100
-		$this->initialStateService = $initialStateService;
101
-		$this->webAuthnManager = $webAuthnManager;
102
-		$this->manager = $manager;
103
-		$this->l10n = $l10n;
104
-	}
105
-
106
-	/**
107
-	 * @NoAdminRequired
108
-	 *
109
-	 * @return RedirectResponse
110
-	 */
111
-	#[UseSession]
112
-	public function logout() {
113
-		$loginToken = $this->request->getCookie('nc_token');
114
-		if (!is_null($loginToken)) {
115
-			$this->config->deleteUserValue($this->userSession->getUser()->getUID(), 'login_token', $loginToken);
116
-		}
117
-		$this->userSession->logout();
118
-
119
-		$response = new RedirectResponse($this->urlGenerator->linkToRouteAbsolute(
120
-			'core.login.showLoginForm',
121
-			['clear' => true] // this param the code in login.js may be removed when the "Clear-Site-Data" is working in the browsers
122
-		));
123
-
124
-		$this->session->set('clearingExecutionContexts', '1');
125
-		$this->session->close();
126
-
127
-		if ($this->request->getServerProtocol() === 'https') {
128
-			// This feature is available only in secure contexts
129
-			$response->addHeader('Clear-Site-Data', '"cache", "storage"');
130
-		}
131
-
132
-		return $response;
133
-	}
134
-
135
-	/**
136
-	 * @PublicPage
137
-	 * @NoCSRFRequired
138
-	 *
139
-	 * @param string $user
140
-	 * @param string $redirect_url
141
-	 *
142
-	 * @return TemplateResponse|RedirectResponse
143
-	 */
144
-	#[UseSession]
145
-	public function showLoginForm(string $user = null, string $redirect_url = null): Http\Response {
146
-		if ($this->userSession->isLoggedIn()) {
147
-			return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
148
-		}
149
-
150
-		$loginMessages = $this->session->get('loginMessages');
151
-		if (!$this->manager->isFairUseOfFreePushService()) {
152
-			if (!is_array($loginMessages)) {
153
-				$loginMessages = [[], []];
154
-			}
155
-			$loginMessages[1][] = $this->l10n->t('This community release of Nextcloud is unsupported and push notifications are limited.');
156
-		}
157
-		if (is_array($loginMessages)) {
158
-			[$errors, $messages] = $loginMessages;
159
-			$this->initialStateService->provideInitialState('core', 'loginMessages', $messages);
160
-			$this->initialStateService->provideInitialState('core', 'loginErrors', $errors);
161
-		}
162
-		$this->session->remove('loginMessages');
163
-
164
-		if ($user !== null && $user !== '') {
165
-			$this->initialStateService->provideInitialState('core', 'loginUsername', $user);
166
-		} else {
167
-			$this->initialStateService->provideInitialState('core', 'loginUsername', '');
168
-		}
169
-
170
-		$this->initialStateService->provideInitialState(
171
-			'core',
172
-			'loginAutocomplete',
173
-			$this->config->getSystemValue('login_form_autocomplete', true) === true
174
-		);
175
-
176
-		if (!empty($redirect_url)) {
177
-			[$url, ] = explode('?', $redirect_url);
178
-			if ($url !== $this->urlGenerator->linkToRoute('core.login.logout')) {
179
-				$this->initialStateService->provideInitialState('core', 'loginRedirectUrl', $redirect_url);
180
-			}
181
-		}
182
-
183
-		$this->initialStateService->provideInitialState(
184
-			'core',
185
-			'loginThrottleDelay',
186
-			$this->throttler->getDelay($this->request->getRemoteAddress())
187
-		);
188
-
189
-		$this->setPasswordResetInitialState($user);
190
-
191
-		$this->initialStateService->provideInitialState('core', 'webauthn-available', $this->webAuthnManager->isWebAuthnAvailable());
192
-
193
-		$this->initialStateService->provideInitialState('core', 'hideLoginForm', $this->config->getSystemValueBool('hide_login_form', false));
194
-
195
-		// OpenGraph Support: http://ogp.me/
196
-		Util::addHeader('meta', ['property' => 'og:title', 'content' => Util::sanitizeHTML($this->defaults->getName())]);
197
-		Util::addHeader('meta', ['property' => 'og:description', 'content' => Util::sanitizeHTML($this->defaults->getSlogan())]);
198
-		Util::addHeader('meta', ['property' => 'og:site_name', 'content' => Util::sanitizeHTML($this->defaults->getName())]);
199
-		Util::addHeader('meta', ['property' => 'og:url', 'content' => $this->urlGenerator->getAbsoluteURL('/')]);
200
-		Util::addHeader('meta', ['property' => 'og:type', 'content' => 'website']);
201
-		Util::addHeader('meta', ['property' => 'og:image', 'content' => $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'favicon-touch.png'))]);
202
-
203
-		$parameters = [
204
-			'alt_login' => OC_App::getAlternativeLogIns(),
205
-			'pageTitle' => $this->l10n->t('Login'),
206
-		];
207
-
208
-		$this->initialStateService->provideInitialState('core', 'countAlternativeLogins', count($parameters['alt_login']));
209
-		$this->initialStateService->provideInitialState('core', 'alternativeLogins', $parameters['alt_login']);
210
-
211
-		return new TemplateResponse(
212
-			$this->appName,
213
-			'login',
214
-			$parameters,
215
-			TemplateResponse::RENDER_AS_GUEST,
216
-		);
217
-	}
218
-
219
-	/**
220
-	 * Sets the password reset state
221
-	 *
222
-	 * @param string $username
223
-	 */
224
-	private function setPasswordResetInitialState(?string $username): void {
225
-		if ($username !== null && $username !== '') {
226
-			$user = $this->userManager->get($username);
227
-		} else {
228
-			$user = null;
229
-		}
230
-
231
-		$passwordLink = $this->config->getSystemValueString('lost_password_link', '');
232
-
233
-		$this->initialStateService->provideInitialState(
234
-			'core',
235
-			'loginResetPasswordLink',
236
-			$passwordLink
237
-		);
238
-
239
-		$this->initialStateService->provideInitialState(
240
-			'core',
241
-			'loginCanResetPassword',
242
-			$this->canResetPassword($passwordLink, $user)
243
-		);
244
-	}
245
-
246
-	/**
247
-	 * @param string|null $passwordLink
248
-	 * @param IUser|null $user
249
-	 *
250
-	 * Users may not change their passwords if:
251
-	 * - The account is disabled
252
-	 * - The backend doesn't support password resets
253
-	 * - The password reset function is disabled
254
-	 *
255
-	 * @return bool
256
-	 */
257
-	private function canResetPassword(?string $passwordLink, ?IUser $user): bool {
258
-		if ($passwordLink === 'disabled') {
259
-			return false;
260
-		}
261
-
262
-		if (!$passwordLink && $user !== null) {
263
-			return $user->canChangePassword();
264
-		}
265
-
266
-		if ($user !== null && $user->isEnabled() === false) {
267
-			return false;
268
-		}
269
-
270
-		return true;
271
-	}
272
-
273
-	private function generateRedirect(?string $redirectUrl): RedirectResponse {
274
-		if ($redirectUrl !== null && $this->userSession->isLoggedIn()) {
275
-			$location = $this->urlGenerator->getAbsoluteURL($redirectUrl);
276
-			// Deny the redirect if the URL contains a @
277
-			// This prevents unvalidated redirects like ?redirect_url=:[email protected]
278
-			if (strpos($location, '@') === false) {
279
-				return new RedirectResponse($location);
280
-			}
281
-		}
282
-		return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
283
-	}
284
-
285
-	/**
286
-	 * @PublicPage
287
-	 * @NoCSRFRequired
288
-	 * @BruteForceProtection(action=login)
289
-	 *
290
-	 * @return RedirectResponse
291
-	 */
292
-	#[UseSession]
293
-	public function tryLogin(Chain $loginChain,
294
-							 string $user = '',
295
-							 string $password = '',
296
-							 string $redirect_url = null,
297
-							 string $timezone = '',
298
-							 string $timezone_offset = ''): RedirectResponse {
299
-		if (!$this->request->passesCSRFCheck()) {
300
-			if ($this->userSession->isLoggedIn()) {
301
-				// If the user is already logged in and the CSRF check does not pass then
302
-				// simply redirect the user to the correct page as required. This is the
303
-				// case when a user has already logged-in, in another tab.
304
-				return $this->generateRedirect($redirect_url);
305
-			}
306
-
307
-			// Clear any auth remnants like cookies to ensure a clean login
308
-			// For the next attempt
309
-			$this->userSession->logout();
310
-			return $this->createLoginFailedResponse(
311
-				$user,
312
-				$user,
313
-				$redirect_url,
314
-				$this->l10n->t('Please try again')
315
-			);
316
-		}
317
-
318
-		$data = new LoginData(
319
-			$this->request,
320
-			trim($user),
321
-			$password,
322
-			$redirect_url,
323
-			$timezone,
324
-			$timezone_offset
325
-		);
326
-		$result = $loginChain->process($data);
327
-		if (!$result->isSuccess()) {
328
-			return $this->createLoginFailedResponse(
329
-				$data->getUsername(),
330
-				$user,
331
-				$redirect_url,
332
-				$result->getErrorMessage()
333
-			);
334
-		}
335
-
336
-		if ($result->getRedirectUrl() !== null) {
337
-			return new RedirectResponse($result->getRedirectUrl());
338
-		}
339
-		return $this->generateRedirect($redirect_url);
340
-	}
341
-
342
-	/**
343
-	 * Creates a login failed response.
344
-	 *
345
-	 * @param string $user
346
-	 * @param string $originalUser
347
-	 * @param string $redirect_url
348
-	 * @param string $loginMessage
349
-	 *
350
-	 * @return RedirectResponse
351
-	 */
352
-	private function createLoginFailedResponse(
353
-		$user, $originalUser, $redirect_url, string $loginMessage) {
354
-		// Read current user and append if possible we need to
355
-		// return the unmodified user otherwise we will leak the login name
356
-		$args = $user !== null ? ['user' => $originalUser, 'direct' => 1] : [];
357
-		if ($redirect_url !== null) {
358
-			$args['redirect_url'] = $redirect_url;
359
-		}
360
-		$response = new RedirectResponse(
361
-			$this->urlGenerator->linkToRoute('core.login.showLoginForm', $args)
362
-		);
363
-		$response->throttle(['user' => substr($user, 0, 64)]);
364
-		$this->session->set('loginMessages', [
365
-			[$loginMessage], []
366
-		]);
367
-		return $response;
368
-	}
369
-
370
-	/**
371
-	 * @NoAdminRequired
372
-	 * @BruteForceProtection(action=sudo)
373
-	 *
374
-	 * @license GNU AGPL version 3 or any later version
375
-	 *
376
-	 */
377
-	#[UseSession]
378
-	public function confirmPassword(string $password): DataResponse {
379
-		$loginName = $this->userSession->getLoginName();
380
-		$loginResult = $this->userManager->checkPassword($loginName, $password);
381
-		if ($loginResult === false) {
382
-			$response = new DataResponse([], Http::STATUS_FORBIDDEN);
383
-			$response->throttle();
384
-			return $response;
385
-		}
386
-
387
-		$confirmTimestamp = time();
388
-		$this->session->set('last-password-confirm', $confirmTimestamp);
389
-		return new DataResponse(['lastLogin' => $confirmTimestamp], Http::STATUS_OK);
390
-	}
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 IInitialStateService $initialStateService;
75
+    private WebAuthnManager $webAuthnManager;
76
+    private IManager $manager;
77
+    private IL10N $l10n;
78
+
79
+    public function __construct(?string $appName,
80
+                                IRequest $request,
81
+                                IUserManager $userManager,
82
+                                IConfig $config,
83
+                                ISession $session,
84
+                                IUserSession $userSession,
85
+                                IURLGenerator $urlGenerator,
86
+                                Defaults $defaults,
87
+                                Throttler $throttler,
88
+                                IInitialStateService $initialStateService,
89
+                                WebAuthnManager $webAuthnManager,
90
+                                IManager $manager,
91
+                                IL10N $l10n) {
92
+        parent::__construct($appName, $request);
93
+        $this->userManager = $userManager;
94
+        $this->config = $config;
95
+        $this->session = $session;
96
+        $this->userSession = $userSession;
97
+        $this->urlGenerator = $urlGenerator;
98
+        $this->defaults = $defaults;
99
+        $this->throttler = $throttler;
100
+        $this->initialStateService = $initialStateService;
101
+        $this->webAuthnManager = $webAuthnManager;
102
+        $this->manager = $manager;
103
+        $this->l10n = $l10n;
104
+    }
105
+
106
+    /**
107
+     * @NoAdminRequired
108
+     *
109
+     * @return RedirectResponse
110
+     */
111
+    #[UseSession]
112
+    public function logout() {
113
+        $loginToken = $this->request->getCookie('nc_token');
114
+        if (!is_null($loginToken)) {
115
+            $this->config->deleteUserValue($this->userSession->getUser()->getUID(), 'login_token', $loginToken);
116
+        }
117
+        $this->userSession->logout();
118
+
119
+        $response = new RedirectResponse($this->urlGenerator->linkToRouteAbsolute(
120
+            'core.login.showLoginForm',
121
+            ['clear' => true] // this param the code in login.js may be removed when the "Clear-Site-Data" is working in the browsers
122
+        ));
123
+
124
+        $this->session->set('clearingExecutionContexts', '1');
125
+        $this->session->close();
126
+
127
+        if ($this->request->getServerProtocol() === 'https') {
128
+            // This feature is available only in secure contexts
129
+            $response->addHeader('Clear-Site-Data', '"cache", "storage"');
130
+        }
131
+
132
+        return $response;
133
+    }
134
+
135
+    /**
136
+     * @PublicPage
137
+     * @NoCSRFRequired
138
+     *
139
+     * @param string $user
140
+     * @param string $redirect_url
141
+     *
142
+     * @return TemplateResponse|RedirectResponse
143
+     */
144
+    #[UseSession]
145
+    public function showLoginForm(string $user = null, string $redirect_url = null): Http\Response {
146
+        if ($this->userSession->isLoggedIn()) {
147
+            return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
148
+        }
149
+
150
+        $loginMessages = $this->session->get('loginMessages');
151
+        if (!$this->manager->isFairUseOfFreePushService()) {
152
+            if (!is_array($loginMessages)) {
153
+                $loginMessages = [[], []];
154
+            }
155
+            $loginMessages[1][] = $this->l10n->t('This community release of Nextcloud is unsupported and push notifications are limited.');
156
+        }
157
+        if (is_array($loginMessages)) {
158
+            [$errors, $messages] = $loginMessages;
159
+            $this->initialStateService->provideInitialState('core', 'loginMessages', $messages);
160
+            $this->initialStateService->provideInitialState('core', 'loginErrors', $errors);
161
+        }
162
+        $this->session->remove('loginMessages');
163
+
164
+        if ($user !== null && $user !== '') {
165
+            $this->initialStateService->provideInitialState('core', 'loginUsername', $user);
166
+        } else {
167
+            $this->initialStateService->provideInitialState('core', 'loginUsername', '');
168
+        }
169
+
170
+        $this->initialStateService->provideInitialState(
171
+            'core',
172
+            'loginAutocomplete',
173
+            $this->config->getSystemValue('login_form_autocomplete', true) === true
174
+        );
175
+
176
+        if (!empty($redirect_url)) {
177
+            [$url, ] = explode('?', $redirect_url);
178
+            if ($url !== $this->urlGenerator->linkToRoute('core.login.logout')) {
179
+                $this->initialStateService->provideInitialState('core', 'loginRedirectUrl', $redirect_url);
180
+            }
181
+        }
182
+
183
+        $this->initialStateService->provideInitialState(
184
+            'core',
185
+            'loginThrottleDelay',
186
+            $this->throttler->getDelay($this->request->getRemoteAddress())
187
+        );
188
+
189
+        $this->setPasswordResetInitialState($user);
190
+
191
+        $this->initialStateService->provideInitialState('core', 'webauthn-available', $this->webAuthnManager->isWebAuthnAvailable());
192
+
193
+        $this->initialStateService->provideInitialState('core', 'hideLoginForm', $this->config->getSystemValueBool('hide_login_form', false));
194
+
195
+        // OpenGraph Support: http://ogp.me/
196
+        Util::addHeader('meta', ['property' => 'og:title', 'content' => Util::sanitizeHTML($this->defaults->getName())]);
197
+        Util::addHeader('meta', ['property' => 'og:description', 'content' => Util::sanitizeHTML($this->defaults->getSlogan())]);
198
+        Util::addHeader('meta', ['property' => 'og:site_name', 'content' => Util::sanitizeHTML($this->defaults->getName())]);
199
+        Util::addHeader('meta', ['property' => 'og:url', 'content' => $this->urlGenerator->getAbsoluteURL('/')]);
200
+        Util::addHeader('meta', ['property' => 'og:type', 'content' => 'website']);
201
+        Util::addHeader('meta', ['property' => 'og:image', 'content' => $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'favicon-touch.png'))]);
202
+
203
+        $parameters = [
204
+            'alt_login' => OC_App::getAlternativeLogIns(),
205
+            'pageTitle' => $this->l10n->t('Login'),
206
+        ];
207
+
208
+        $this->initialStateService->provideInitialState('core', 'countAlternativeLogins', count($parameters['alt_login']));
209
+        $this->initialStateService->provideInitialState('core', 'alternativeLogins', $parameters['alt_login']);
210
+
211
+        return new TemplateResponse(
212
+            $this->appName,
213
+            'login',
214
+            $parameters,
215
+            TemplateResponse::RENDER_AS_GUEST,
216
+        );
217
+    }
218
+
219
+    /**
220
+     * Sets the password reset state
221
+     *
222
+     * @param string $username
223
+     */
224
+    private function setPasswordResetInitialState(?string $username): void {
225
+        if ($username !== null && $username !== '') {
226
+            $user = $this->userManager->get($username);
227
+        } else {
228
+            $user = null;
229
+        }
230
+
231
+        $passwordLink = $this->config->getSystemValueString('lost_password_link', '');
232
+
233
+        $this->initialStateService->provideInitialState(
234
+            'core',
235
+            'loginResetPasswordLink',
236
+            $passwordLink
237
+        );
238
+
239
+        $this->initialStateService->provideInitialState(
240
+            'core',
241
+            'loginCanResetPassword',
242
+            $this->canResetPassword($passwordLink, $user)
243
+        );
244
+    }
245
+
246
+    /**
247
+     * @param string|null $passwordLink
248
+     * @param IUser|null $user
249
+     *
250
+     * Users may not change their passwords if:
251
+     * - The account is disabled
252
+     * - The backend doesn't support password resets
253
+     * - The password reset function is disabled
254
+     *
255
+     * @return bool
256
+     */
257
+    private function canResetPassword(?string $passwordLink, ?IUser $user): bool {
258
+        if ($passwordLink === 'disabled') {
259
+            return false;
260
+        }
261
+
262
+        if (!$passwordLink && $user !== null) {
263
+            return $user->canChangePassword();
264
+        }
265
+
266
+        if ($user !== null && $user->isEnabled() === false) {
267
+            return false;
268
+        }
269
+
270
+        return true;
271
+    }
272
+
273
+    private function generateRedirect(?string $redirectUrl): RedirectResponse {
274
+        if ($redirectUrl !== null && $this->userSession->isLoggedIn()) {
275
+            $location = $this->urlGenerator->getAbsoluteURL($redirectUrl);
276
+            // Deny the redirect if the URL contains a @
277
+            // This prevents unvalidated redirects like ?redirect_url=:[email protected]
278
+            if (strpos($location, '@') === false) {
279
+                return new RedirectResponse($location);
280
+            }
281
+        }
282
+        return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
283
+    }
284
+
285
+    /**
286
+     * @PublicPage
287
+     * @NoCSRFRequired
288
+     * @BruteForceProtection(action=login)
289
+     *
290
+     * @return RedirectResponse
291
+     */
292
+    #[UseSession]
293
+    public function tryLogin(Chain $loginChain,
294
+                                string $user = '',
295
+                                string $password = '',
296
+                                string $redirect_url = null,
297
+                                string $timezone = '',
298
+                                string $timezone_offset = ''): RedirectResponse {
299
+        if (!$this->request->passesCSRFCheck()) {
300
+            if ($this->userSession->isLoggedIn()) {
301
+                // If the user is already logged in and the CSRF check does not pass then
302
+                // simply redirect the user to the correct page as required. This is the
303
+                // case when a user has already logged-in, in another tab.
304
+                return $this->generateRedirect($redirect_url);
305
+            }
306
+
307
+            // Clear any auth remnants like cookies to ensure a clean login
308
+            // For the next attempt
309
+            $this->userSession->logout();
310
+            return $this->createLoginFailedResponse(
311
+                $user,
312
+                $user,
313
+                $redirect_url,
314
+                $this->l10n->t('Please try again')
315
+            );
316
+        }
317
+
318
+        $data = new LoginData(
319
+            $this->request,
320
+            trim($user),
321
+            $password,
322
+            $redirect_url,
323
+            $timezone,
324
+            $timezone_offset
325
+        );
326
+        $result = $loginChain->process($data);
327
+        if (!$result->isSuccess()) {
328
+            return $this->createLoginFailedResponse(
329
+                $data->getUsername(),
330
+                $user,
331
+                $redirect_url,
332
+                $result->getErrorMessage()
333
+            );
334
+        }
335
+
336
+        if ($result->getRedirectUrl() !== null) {
337
+            return new RedirectResponse($result->getRedirectUrl());
338
+        }
339
+        return $this->generateRedirect($redirect_url);
340
+    }
341
+
342
+    /**
343
+     * Creates a login failed response.
344
+     *
345
+     * @param string $user
346
+     * @param string $originalUser
347
+     * @param string $redirect_url
348
+     * @param string $loginMessage
349
+     *
350
+     * @return RedirectResponse
351
+     */
352
+    private function createLoginFailedResponse(
353
+        $user, $originalUser, $redirect_url, string $loginMessage) {
354
+        // Read current user and append if possible we need to
355
+        // return the unmodified user otherwise we will leak the login name
356
+        $args = $user !== null ? ['user' => $originalUser, 'direct' => 1] : [];
357
+        if ($redirect_url !== null) {
358
+            $args['redirect_url'] = $redirect_url;
359
+        }
360
+        $response = new RedirectResponse(
361
+            $this->urlGenerator->linkToRoute('core.login.showLoginForm', $args)
362
+        );
363
+        $response->throttle(['user' => substr($user, 0, 64)]);
364
+        $this->session->set('loginMessages', [
365
+            [$loginMessage], []
366
+        ]);
367
+        return $response;
368
+    }
369
+
370
+    /**
371
+     * @NoAdminRequired
372
+     * @BruteForceProtection(action=sudo)
373
+     *
374
+     * @license GNU AGPL version 3 or any later version
375
+     *
376
+     */
377
+    #[UseSession]
378
+    public function confirmPassword(string $password): DataResponse {
379
+        $loginName = $this->userSession->getLoginName();
380
+        $loginResult = $this->userManager->checkPassword($loginName, $password);
381
+        if ($loginResult === false) {
382
+            $response = new DataResponse([], Http::STATUS_FORBIDDEN);
383
+            $response->throttle();
384
+            return $response;
385
+        }
386
+
387
+        $confirmTimestamp = time();
388
+        $this->session->set('last-password-confirm', $confirmTimestamp);
389
+        return new DataResponse(['lastLogin' => $confirmTimestamp], Http::STATUS_OK);
390
+    }
391 391
 }
Please login to merge, or discard this patch.